diff options
Diffstat (limited to 'drivers/iio/light')
| -rw-r--r-- | drivers/iio/light/Kconfig | 83 | ||||
| -rw-r--r-- | drivers/iio/light/Makefile | 7 | ||||
| -rw-r--r-- | drivers/iio/light/adjd_s311.c | 87 | ||||
| -rw-r--r-- | drivers/iio/light/apds9300.c | 45 | ||||
| -rw-r--r-- | drivers/iio/light/cm32181.c | 380 | ||||
| -rw-r--r-- | drivers/iio/light/cm36651.c | 750 | ||||
| -rw-r--r-- | drivers/iio/light/gp2ap020a00f.c | 1654 | ||||
| -rw-r--r-- | drivers/iio/light/hid-sensor-als.c | 78 | ||||
| -rw-r--r-- | drivers/iio/light/hid-sensor-prox.c | 385 | ||||
| -rw-r--r-- | drivers/iio/light/ltr501.c | 445 | ||||
| -rw-r--r-- | drivers/iio/light/tcs3472.c | 379 | ||||
| -rw-r--r-- | drivers/iio/light/tsl2563.c | 65 | ||||
| -rw-r--r-- | drivers/iio/light/tsl4531.c | 258 | ||||
| -rw-r--r-- | drivers/iio/light/vcnl4000.c | 15 | 
14 files changed, 4502 insertions, 129 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index bf9fa0d7aff..c89740d4748 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -27,6 +27,41 @@ config APDS9300  	 To compile this driver as a module, choose M here: the  	 module will be called apds9300. +config CM32181 +	depends on I2C +	tristate "CM32181 driver" +	help +	 Say Y here if you use cm32181. +	 This option enables ambient light sensor using +	 Capella cm32181 device driver. + +	 To compile this driver as a module, choose M here: +	 the module will be called cm32181. + +config CM36651 +	depends on I2C +	tristate "CM36651 driver" +	help +	 Say Y here if you use cm36651. +	 This option enables proximity & RGB sensor using +	 Capella cm36651 device driver. + +	 To compile this driver as a module, choose M here: +	 the module will be called cm36651. + +config GP2AP020A00F +	tristate "Sharp GP2AP020A00F Proximity/ALS sensor" +	depends on I2C +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	select IRQ_WORK +	help +	  Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip +	  hooked to an I2C bus. + +	  To compile this driver as a module, choose M here: the +	  module will be called gp2ap020a00f. +  config HID_SENSOR_ALS  	depends on HID_SENSOR_HUB  	select IIO_BUFFER @@ -38,6 +73,20 @@ config HID_SENSOR_ALS  	  Say yes here to build support for the HID SENSOR  	  Ambient light sensor. +config HID_SENSOR_PROX +	depends on HID_SENSOR_HUB +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	select HID_SENSOR_IIO_COMMON +	select HID_SENSOR_IIO_TRIGGER +	tristate "HID PROX" +	help +	  Say yes here to build support for the HID SENSOR +	  Proximity sensor. + +	  To compile this driver as a module, choose M here: the +	  module will be called hid-sensor-prox. +  config SENSORS_LM3533  	tristate "LM3533 ambient light sensor"  	depends on MFD_LM3533 @@ -55,6 +104,30 @@ config SENSORS_LM3533  	  changes. The ALS-control output values can be set per zone for the  	  three current output channels. +config LTR501 +	tristate "LTR-501ALS-01 light sensor" +	depends on I2C +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	help +	 If you say yes here you get support for the Lite-On LTR-501ALS-01 +	 ambient light and proximity sensor. + +	 This driver can also be built as a module.  If so, the module +         will be called ltr501. + +config TCS3472 +	tristate "TAOS TCS3472 color light-to-digital converter" +	depends on I2C +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	help +	 If you say yes here you get support for the TAOS TCS3472 +	 family of color light-to-digital converters with IR filter. + +	 This driver can also be built as a module.  If so, the module +	 will be called tcs3472. +  config SENSORS_TSL2563  	tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors"  	depends on I2C @@ -65,6 +138,16 @@ config SENSORS_TSL2563  	 This driver can also be built as a module.  If so, the module  	 will be called tsl2563. +config TSL4531 +	tristate "TAOS TSL4531 ambient light sensors" +	depends on I2C +	help +	 Say Y here if you want to build a driver for the TAOS TSL4531 family +	 of ambient light sensors with direct lux output. + +	 To compile this driver as a module, choose M here: the +	 module will be called tsl4531. +  config VCNL4000  	tristate "VCNL4000 combined ALS and proximity sensor"  	depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 354ee9ab237..3eb36e5151f 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -5,7 +5,14 @@  # When adding new entries keep the list in alphabetical order  obj-$(CONFIG_ADJD_S311)		+= adjd_s311.o  obj-$(CONFIG_APDS9300)		+= apds9300.o +obj-$(CONFIG_CM32181)		+= cm32181.o +obj-$(CONFIG_CM36651)		+= cm36651.o +obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o  obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o +obj-$(CONFIG_HID_SENSOR_PROX)	+= hid-sensor-prox.o  obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o +obj-$(CONFIG_LTR501)		+= ltr501.o  obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o +obj-$(CONFIG_TCS3472)		+= tcs3472.o +obj-$(CONFIG_TSL4531)		+= tsl4531.o  obj-$(CONFIG_VCNL4000)		+= vcnl4000.o diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c index 23cff798598..09ad5f1ce53 100644 --- a/drivers/iio/light/adjd_s311.c +++ b/drivers/iio/light/adjd_s311.c @@ -14,7 +14,6 @@   */  #include <linux/module.h> -#include <linux/init.h>  #include <linux/interrupt.h>  #include <linux/i2c.h>  #include <linux/slab.h> @@ -114,50 +113,12 @@ static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val)  	return 0;  } -static ssize_t adjd_s311_read_int_time(struct iio_dev *indio_dev, -	uintptr_t private, const struct iio_chan_spec *chan, char *buf) -{ -	struct adjd_s311_data *data = iio_priv(indio_dev); -	s32 ret; - -	ret = i2c_smbus_read_word_data(data->client, -		ADJD_S311_INT_REG(chan->address)); -	if (ret < 0) -		return ret; - -	return sprintf(buf, "%d\n", ret & ADJD_S311_INT_MASK); -} - -static ssize_t adjd_s311_write_int_time(struct iio_dev *indio_dev, -	 uintptr_t private, const struct iio_chan_spec *chan, const char *buf, -	 size_t len) -{ -	struct adjd_s311_data *data = iio_priv(indio_dev); -	unsigned long int_time; -	int ret; - -	ret = kstrtoul(buf, 10, &int_time); -	if (ret) -		return ret; - -	if (int_time > ADJD_S311_INT_MASK) -		return -EINVAL; - -	ret = i2c_smbus_write_word_data(data->client, -		ADJD_S311_INT_REG(chan->address), int_time); -	if (ret < 0) -		return ret; - -	return len; -} -  static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)  {  	struct iio_poll_func *pf = p;  	struct iio_dev *indio_dev = pf->indio_dev;  	struct adjd_s311_data *data = iio_priv(indio_dev);  	s64 time_ns = iio_get_time_ns(); -	int len = 0;  	int i, j = 0;  	int ret = adjd_s311_req_data(indio_dev); @@ -172,13 +133,9 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)  			goto done;  		data->buffer[j++] = ret & ADJD_S311_DATA_MASK; -		len += 2;  	} -	if (indio_dev->scan_timestamp) -		*(s64 *)((u8 *)data->buffer + ALIGN(len, sizeof(s64))) -			= time_ns; -	iio_push_to_buffers(indio_dev, (u8 *)data->buffer); +	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, time_ns);  done:  	iio_trigger_notify_done(indio_dev->trig); @@ -186,25 +143,21 @@ done:  	return IRQ_HANDLED;  } -static const struct iio_chan_spec_ext_info adjd_s311_ext_info[] = { -	{ -		.name = "integration_time", -		.read = adjd_s311_read_int_time, -		.write = adjd_s311_write_int_time, -	}, -	{ } -}; -  #define ADJD_S311_CHANNEL(_color, _scan_idx) { \  	.type = IIO_INTENSITY, \  	.modified = 1, \  	.address = (IDX_##_color), \  	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ -		BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ +		BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ +		BIT(IIO_CHAN_INFO_INT_TIME), \  	.channel2 = (IIO_MOD_LIGHT_##_color), \  	.scan_index = (_scan_idx), \ -	.scan_type = IIO_ST('u', 10, 16, 0), \ -	.ext_info = adjd_s311_ext_info, \ +	.scan_type = { \ +		.sign = 'u', \ +		.realbits = 10, \ +		.storagebits = 16, \ +		.endianness = IIO_CPU, \ +	}, \  }  static const struct iio_chan_spec adjd_s311_channels[] = { @@ -236,6 +189,18 @@ static int adjd_s311_read_raw(struct iio_dev *indio_dev,  			return ret;  		*val = ret & ADJD_S311_CAP_MASK;  		return IIO_VAL_INT; +	case IIO_CHAN_INFO_INT_TIME: +		ret = i2c_smbus_read_word_data(data->client, +			ADJD_S311_INT_REG(chan->address)); +		if (ret < 0) +			return ret; +		*val = 0; +		/* +		 * not documented, based on measurement: +		 * 4095 LSBs correspond to roughly 4 ms +		 */ +		*val2 = ret & ADJD_S311_INT_MASK; +		return IIO_VAL_INT_PLUS_MICRO;  	}  	return -EINVAL;  } @@ -245,16 +210,20 @@ static int adjd_s311_write_raw(struct iio_dev *indio_dev,  			       int val, int val2, long mask)  {  	struct adjd_s311_data *data = iio_priv(indio_dev); -	int ret;  	switch (mask) {  	case IIO_CHAN_INFO_HARDWAREGAIN:  		if (val < 0 || val > ADJD_S311_CAP_MASK)  			return -EINVAL; -		ret = i2c_smbus_write_byte_data(data->client, +		return i2c_smbus_write_byte_data(data->client,  			ADJD_S311_CAP_REG(chan->address), val); -		return ret; +	case IIO_CHAN_INFO_INT_TIME: +		if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK) +			return -EINVAL; + +		return i2c_smbus_write_word_data(data->client, +			ADJD_S311_INT_REG(chan->address), val2);  	}  	return -EINVAL;  } diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c index 66a58bda6dc..9ddde0ca9c3 100644 --- a/drivers/iio/light/apds9300.c +++ b/drivers/iio/light/apds9300.c @@ -273,12 +273,14 @@ static int apds9300_read_raw(struct iio_dev *indio_dev,  	return ret;  } -static int apds9300_read_thresh(struct iio_dev *indio_dev, u64 event_code, -		int *val) +static int apds9300_read_thresh(struct iio_dev *indio_dev, +		const struct iio_chan_spec *chan, enum iio_event_type type, +		enum iio_event_direction dir, enum iio_event_info info, +		int *val, int *val2)  {  	struct apds9300_data *data = iio_priv(indio_dev); -	switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { +	switch (dir) {  	case IIO_EV_DIR_RISING:  		*val = data->thresh_hi;  		break; @@ -289,17 +291,19 @@ static int apds9300_read_thresh(struct iio_dev *indio_dev, u64 event_code,  		return -EINVAL;  	} -	return 0; +	return IIO_VAL_INT;  } -static int apds9300_write_thresh(struct iio_dev *indio_dev, u64 event_code, -		int val) +static int apds9300_write_thresh(struct iio_dev *indio_dev, +		const struct iio_chan_spec *chan, enum iio_event_type type, +		enum iio_event_direction dir, enum iio_event_info info, int val, +		int val2)  {  	struct apds9300_data *data = iio_priv(indio_dev);  	int ret;  	mutex_lock(&data->mutex); -	if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) +	if (dir == IIO_EV_DIR_RISING)  		ret = apds9300_set_thresh_hi(data, val);  	else  		ret = apds9300_set_thresh_low(data, val); @@ -309,7 +313,9 @@ static int apds9300_write_thresh(struct iio_dev *indio_dev, u64 event_code,  }  static int apds9300_read_interrupt_config(struct iio_dev *indio_dev, -		u64 event_code) +		const struct iio_chan_spec *chan, +		enum iio_event_type type, +		enum iio_event_direction dir)  {  	struct apds9300_data *data = iio_priv(indio_dev); @@ -317,7 +323,8 @@ static int apds9300_read_interrupt_config(struct iio_dev *indio_dev,  }  static int apds9300_write_interrupt_config(struct iio_dev *indio_dev, -		u64 event_code, int state) +		const struct iio_chan_spec *chan, enum iio_event_type type, +		enum iio_event_direction dir, int state)  {  	struct apds9300_data *data = iio_priv(indio_dev);  	int ret; @@ -343,6 +350,20 @@ static const struct iio_info apds9300_info = {  	.write_event_config	= apds9300_write_interrupt_config,  }; +static const struct iio_event_spec apds9300_event_spec[] = { +	{ +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_RISING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, { +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_FALLING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, +}; +  static const struct iio_chan_spec apds9300_channels[] = {  	{  		.type = IIO_LIGHT, @@ -355,10 +376,8 @@ static const struct iio_chan_spec apds9300_channels[] = {  		.channel2 = IIO_MOD_LIGHT_BOTH,  		.indexed = true,  		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), -		.event_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH, -					  IIO_EV_DIR_RISING) | -			       IIO_EV_BIT(IIO_EV_TYPE_THRESH, -					  IIO_EV_DIR_FALLING)), +		.event_spec = apds9300_event_spec, +		.num_event_specs = ARRAY_SIZE(apds9300_event_spec),  	}, {  		.type = IIO_INTENSITY,  		.channel = 1, diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c new file mode 100644 index 00000000000..d976e6ce60d --- /dev/null +++ b/drivers/iio/light/cm32181.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2013 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM32181_REG_ADDR_CMD		0x00 +#define CM32181_REG_ADDR_ALS		0x04 +#define CM32181_REG_ADDR_STATUS		0x06 +#define CM32181_REG_ADDR_ID		0x07 + +/* Number of Configurable Registers */ +#define CM32181_CONF_REG_NUM		0x01 + +/* CMD register */ +#define CM32181_CMD_ALS_ENABLE		0x00 +#define CM32181_CMD_ALS_DISABLE		0x01 +#define CM32181_CMD_ALS_INT_EN		0x02 + +#define CM32181_CMD_ALS_IT_SHIFT	6 +#define CM32181_CMD_ALS_IT_MASK		(0x0F << CM32181_CMD_ALS_IT_SHIFT) +#define CM32181_CMD_ALS_IT_DEFAULT	(0x00 << CM32181_CMD_ALS_IT_SHIFT) + +#define CM32181_CMD_ALS_SM_SHIFT	11 +#define CM32181_CMD_ALS_SM_MASK		(0x03 << CM32181_CMD_ALS_SM_SHIFT) +#define CM32181_CMD_ALS_SM_DEFAULT	(0x01 << CM32181_CMD_ALS_SM_SHIFT) + +#define CM32181_MLUX_PER_BIT		5	/* ALS_SM=01 IT=800ms */ +#define CM32181_MLUX_PER_BIT_BASE_IT	800000	/* Based on IT=800ms */ +#define	CM32181_CALIBSCALE_DEFAULT	1000 +#define CM32181_CALIBSCALE_RESOLUTION	1000 +#define MLUX_PER_LUX			1000 + +static const u8 cm32181_reg[CM32181_CONF_REG_NUM] = { +	CM32181_REG_ADDR_CMD, +}; + +static const int als_it_bits[] = {12, 8, 0, 1, 2, 3}; +static const int als_it_value[] = {25000, 50000, 100000, 200000, 400000, +	800000}; + +struct cm32181_chip { +	struct i2c_client *client; +	struct mutex lock; +	u16 conf_regs[CM32181_CONF_REG_NUM]; +	int calibscale; +}; + +/** + * cm32181_reg_init() - Initialize CM32181 registers + * @cm32181:	pointer of struct cm32181. + * + * Initialize CM32181 ambient light sensor register to default values. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm32181_reg_init(struct cm32181_chip *cm32181) +{ +	struct i2c_client *client = cm32181->client; +	int i; +	s32 ret; + +	ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID); +	if (ret < 0) +		return ret; + +	/* check device ID */ +	if ((ret & 0xFF) != 0x81) +		return -ENODEV; + +	/* Default Values */ +	cm32181->conf_regs[CM32181_REG_ADDR_CMD] = CM32181_CMD_ALS_ENABLE | +			CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT; +	cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT; + +	/* Initialize registers*/ +	for (i = 0; i < CM32181_CONF_REG_NUM; i++) { +		ret = i2c_smbus_write_word_data(client, cm32181_reg[i], +			cm32181->conf_regs[i]); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +/** + *  cm32181_read_als_it() - Get sensor integration time (ms) + *  @cm32181:	pointer of struct cm32181 + *  @val2:	pointer of int to load the als_it value. + * + *  Report the current integartion time by millisecond. + * + *  Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2) +{ +	u16 als_it; +	int i; + +	als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD]; +	als_it &= CM32181_CMD_ALS_IT_MASK; +	als_it >>= CM32181_CMD_ALS_IT_SHIFT; +	for (i = 0; i < ARRAY_SIZE(als_it_bits); i++) { +		if (als_it == als_it_bits[i]) { +			*val2 = als_it_value[i]; +			return IIO_VAL_INT_PLUS_MICRO; +		} +	} + +	return -EINVAL; +} + +/** + * cm32181_write_als_it() - Write sensor integration time + * @cm32181:	pointer of struct cm32181. + * @val:	integration time by millisecond. + * + * Convert integration time (ms) to sensor value. + * + * Return: i2c_smbus_write_word_data command return value. + */ +static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) +{ +	struct i2c_client *client = cm32181->client; +	u16 als_it; +	int ret, i, n; + +	n = ARRAY_SIZE(als_it_value); +	for (i = 0; i < n; i++) +		if (val <= als_it_value[i]) +			break; +	if (i >= n) +		i = n - 1; + +	als_it = als_it_bits[i]; +	als_it <<= CM32181_CMD_ALS_IT_SHIFT; + +	mutex_lock(&cm32181->lock); +	cm32181->conf_regs[CM32181_REG_ADDR_CMD] &= +		~CM32181_CMD_ALS_IT_MASK; +	cm32181->conf_regs[CM32181_REG_ADDR_CMD] |= +		als_it; +	ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, +			cm32181->conf_regs[CM32181_REG_ADDR_CMD]); +	mutex_unlock(&cm32181->lock); + +	return ret; +} + +/** + * cm32181_get_lux() - report current lux value + * @cm32181:	pointer of struct cm32181. + * + * Convert sensor raw data to lux.  It depends on integration + * time and claibscale variable. + * + * Return: Positive value is lux, otherwise is error code. + */ +static int cm32181_get_lux(struct cm32181_chip *cm32181) +{ +	struct i2c_client *client = cm32181->client; +	int ret; +	int als_it; +	unsigned long lux; + +	ret = cm32181_read_als_it(cm32181, &als_it); +	if (ret < 0) +		return -EINVAL; + +	lux = CM32181_MLUX_PER_BIT; +	lux *= CM32181_MLUX_PER_BIT_BASE_IT; +	lux /= als_it; + +	ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS); +	if (ret < 0) +		return ret; + +	lux *= ret; +	lux *= cm32181->calibscale; +	lux /= CM32181_CALIBSCALE_RESOLUTION; +	lux /= MLUX_PER_LUX; + +	if (lux > 0xFFFF) +		lux = 0xFFFF; + +	return lux; +} + +static int cm32181_read_raw(struct iio_dev *indio_dev, +			    struct iio_chan_spec const *chan, +			    int *val, int *val2, long mask) +{ +	struct cm32181_chip *cm32181 = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_PROCESSED: +		ret = cm32181_get_lux(cm32181); +		if (ret < 0) +			return ret; +		*val = ret; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_CALIBSCALE: +		*val = cm32181->calibscale; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_INT_TIME: +		*val = 0; +		ret = cm32181_read_als_it(cm32181, val2); +		return ret; +	} + +	return -EINVAL; +} + +static int cm32181_write_raw(struct iio_dev *indio_dev, +			     struct iio_chan_spec const *chan, +			     int val, int val2, long mask) +{ +	struct cm32181_chip *cm32181 = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_CALIBSCALE: +		cm32181->calibscale = val; +		return val; +	case IIO_CHAN_INFO_INT_TIME: +		ret = cm32181_write_als_it(cm32181, val2); +		return ret; +	} + +	return -EINVAL; +} + +/** + * cm32181_get_it_available() - Get available ALS IT value + * @dev:	pointer of struct device. + * @attr:	pointer of struct device_attribute. + * @buf:	pointer of return string buffer. + * + * Display the available integration time values by millisecond. + * + * Return: string length. + */ +static ssize_t cm32181_get_it_available(struct device *dev, +			struct device_attribute *attr, char *buf) +{ +	int i, n, len; + +	n = ARRAY_SIZE(als_it_value); +	for (i = 0, len = 0; i < n; i++) +		len += sprintf(buf + len, "0.%06u ", als_it_value[i]); +	return len + sprintf(buf + len, "\n"); +} + +static const struct iio_chan_spec cm32181_channels[] = { +	{ +		.type = IIO_LIGHT, +		.info_mask_separate = +			BIT(IIO_CHAN_INFO_PROCESSED) | +			BIT(IIO_CHAN_INFO_CALIBSCALE) | +			BIT(IIO_CHAN_INFO_INT_TIME), +	} +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, +			S_IRUGO, cm32181_get_it_available, NULL, 0); + +static struct attribute *cm32181_attributes[] = { +	&iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, +	NULL, +}; + +static const struct attribute_group cm32181_attribute_group = { +	.attrs = cm32181_attributes +}; + +static const struct iio_info cm32181_info = { +	.driver_module		= THIS_MODULE, +	.read_raw		= &cm32181_read_raw, +	.write_raw		= &cm32181_write_raw, +	.attrs			= &cm32181_attribute_group, +}; + +static int cm32181_probe(struct i2c_client *client, +			const struct i2c_device_id *id) +{ +	struct cm32181_chip *cm32181; +	struct iio_dev *indio_dev; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm32181)); +	if (!indio_dev) { +		dev_err(&client->dev, "devm_iio_device_alloc failed\n"); +		return -ENOMEM; +	} + +	cm32181 = iio_priv(indio_dev); +	i2c_set_clientdata(client, indio_dev); +	cm32181->client = client; + +	mutex_init(&cm32181->lock); +	indio_dev->dev.parent = &client->dev; +	indio_dev->channels = cm32181_channels; +	indio_dev->num_channels = ARRAY_SIZE(cm32181_channels); +	indio_dev->info = &cm32181_info; +	indio_dev->name = id->name; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = cm32181_reg_init(cm32181); +	if (ret) { +		dev_err(&client->dev, +			"%s: register init failed\n", +			__func__); +		return ret; +	} + +	ret = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&client->dev, +			"%s: regist device failed\n", +			__func__); +		return ret; +	} + +	return 0; +} + +static int cm32181_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); + +	iio_device_unregister(indio_dev); +	return 0; +} + +static const struct i2c_device_id cm32181_id[] = { +	{ "cm32181", 0 }, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, cm32181_id); + +static const struct of_device_id cm32181_of_match[] = { +	{ .compatible = "capella,cm32181" }, +	{ } +}; + +static struct i2c_driver cm32181_driver = { +	.driver = { +		.name	= "cm32181", +		.of_match_table = of_match_ptr(cm32181_of_match), +		.owner	= THIS_MODULE, +	}, +	.id_table       = cm32181_id, +	.probe		= cm32181_probe, +	.remove		= cm32181_remove, +}; + +module_i2c_driver(cm32181_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM32181 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c new file mode 100644 index 00000000000..39fc67e8213 --- /dev/null +++ b/drivers/iio/light/cm36651.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.com> + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General Public License version 2, as published + * by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */ +#define CM36651_I2C_ADDR_PS		0x19 +/* Alert Response Address */ +#define CM36651_ARA			0x0C + +/* Ambient light sensor */ +#define CM36651_CS_CONF1		0x00 +#define CM36651_CS_CONF2		0x01 +#define CM36651_ALS_WH_M		0x02 +#define CM36651_ALS_WH_L		0x03 +#define CM36651_ALS_WL_M		0x04 +#define CM36651_ALS_WL_L		0x05 +#define CM36651_CS_CONF3		0x06 +#define CM36651_CS_CONF_REG_NUM		0x02 + +/* Proximity sensor */ +#define CM36651_PS_CONF1		0x00 +#define CM36651_PS_THD			0x01 +#define CM36651_PS_CANC			0x02 +#define CM36651_PS_CONF2		0x03 +#define CM36651_PS_REG_NUM		0x04 + +/* CS_CONF1 command code */ +#define CM36651_ALS_ENABLE		0x00 +#define CM36651_ALS_DISABLE		0x01 +#define CM36651_ALS_INT_EN		0x02 +#define CM36651_ALS_THRES		0x04 + +/* CS_CONF2 command code */ +#define CM36651_CS_CONF2_DEFAULT_BIT	0x08 + +/* CS_CONF3 channel integration time */ +#define CM36651_CS_IT1			0x00 /* Integration time 80 msec */ +#define CM36651_CS_IT2			0x40 /* Integration time 160 msec */ +#define CM36651_CS_IT3			0x80 /* Integration time 320 msec */ +#define CM36651_CS_IT4			0xC0 /* Integration time 640 msec */ + +/* PS_CONF1 command code */ +#define CM36651_PS_ENABLE		0x00 +#define CM36651_PS_DISABLE		0x01 +#define CM36651_PS_INT_EN		0x02 +#define CM36651_PS_PERS2		0x04 +#define CM36651_PS_PERS3		0x08 +#define CM36651_PS_PERS4		0x0C + +/* PS_CONF1 command code: integration time */ +#define CM36651_PS_IT1			0x00 /* Integration time 0.32 msec */ +#define CM36651_PS_IT2			0x10 /* Integration time 0.42 msec */ +#define CM36651_PS_IT3			0x20 /* Integration time 0.52 msec */ +#define CM36651_PS_IT4			0x30 /* Integration time 0.64 msec */ + +/* PS_CONF1 command code: duty ratio */ +#define CM36651_PS_DR1			0x00 /* Duty ratio 1/80 */ +#define CM36651_PS_DR2			0x40 /* Duty ratio 1/160 */ +#define CM36651_PS_DR3			0x80 /* Duty ratio 1/320 */ +#define CM36651_PS_DR4			0xC0 /* Duty ratio 1/640 */ + +/* PS_THD command code */ +#define CM36651_PS_INITIAL_THD		0x05 + +/* PS_CANC command code */ +#define CM36651_PS_CANC_DEFAULT		0x00 + +/* PS_CONF2 command code */ +#define CM36651_PS_HYS1			0x00 +#define CM36651_PS_HYS2			0x01 +#define CM36651_PS_SMART_PERS_EN	0x02 +#define CM36651_PS_DIR_INT		0x04 +#define CM36651_PS_MS			0x10 + +#define CM36651_CS_COLOR_NUM		4 + +#define CM36651_CLOSE_PROXIMITY		0x32 +#define CM36651_FAR_PROXIMITY			0x33 + +#define CM36651_CS_INT_TIME_AVAIL	"0.08 0.16 0.32 0.64" +#define CM36651_PS_INT_TIME_AVAIL	"0.000320 0.000420 0.000520 0.000640" + +enum cm36651_operation_mode { +	CM36651_LIGHT_EN, +	CM36651_PROXIMITY_EN, +	CM36651_PROXIMITY_EV_EN, +}; + +enum cm36651_light_channel_idx { +	CM36651_LIGHT_CHANNEL_IDX_RED, +	CM36651_LIGHT_CHANNEL_IDX_GREEN, +	CM36651_LIGHT_CHANNEL_IDX_BLUE, +	CM36651_LIGHT_CHANNEL_IDX_CLEAR, +}; + +enum cm36651_command { +	CM36651_CMD_READ_RAW_LIGHT, +	CM36651_CMD_READ_RAW_PROXIMITY, +	CM36651_CMD_PROX_EV_EN, +	CM36651_CMD_PROX_EV_DIS, +}; + +static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = { +	CM36651_CS_CONF1, +	CM36651_CS_CONF2, +}; + +static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = { +	CM36651_PS_CONF1, +	CM36651_PS_THD, +	CM36651_PS_CANC, +	CM36651_PS_CONF2, +}; + +struct cm36651_data { +	const struct cm36651_platform_data *pdata; +	struct i2c_client *client; +	struct i2c_client *ps_client; +	struct i2c_client *ara_client; +	struct mutex lock; +	struct regulator *vled_reg; +	unsigned long flags; +	int cs_int_time[CM36651_CS_COLOR_NUM]; +	int ps_int_time; +	u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM]; +	u8 ps_ctrl_regs[CM36651_PS_REG_NUM]; +	u16 color[CM36651_CS_COLOR_NUM]; +}; + +static int cm36651_setup_reg(struct cm36651_data *cm36651) +{ +	struct i2c_client *client = cm36651->client; +	struct i2c_client *ps_client = cm36651->ps_client; +	int i, ret; + +	/* CS initialization */ +	cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE | +							     CM36651_ALS_THRES; +	cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT; + +	for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) { +		ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i], +						     cm36651->cs_ctrl_regs[i]); +		if (ret < 0) +			return ret; +	} + +	/* PS initialization */ +	cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE | +								CM36651_PS_IT2; +	cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD; +	cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT; +	cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 | +				CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN; + +	for (i = 0; i < CM36651_PS_REG_NUM; i++) { +		ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i], +						     cm36651->ps_ctrl_regs[i]); +		if (ret < 0) +			return ret; +	} + +	/* Set shutdown mode */ +	ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, +							  CM36651_ALS_DISABLE); +	if (ret < 0) +		return ret; + +	ret = i2c_smbus_write_byte_data(cm36651->ps_client, +					 CM36651_PS_CONF1, CM36651_PS_DISABLE); +	if (ret < 0) +		return ret; + +	return 0; +} + +static int cm36651_read_output(struct cm36651_data *cm36651, +				struct iio_chan_spec const *chan, int *val) +{ +	struct i2c_client *client = cm36651->client; +	int ret = -EINVAL; + +	switch (chan->type) { +	case IIO_LIGHT: +		*val = i2c_smbus_read_word_data(client, chan->address); +		if (*val < 0) +			return ret; + +		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, +							CM36651_ALS_DISABLE); +		if (ret < 0) +			return ret; + +		ret = IIO_VAL_INT; +		break; +	case IIO_PROXIMITY: +		*val = i2c_smbus_read_byte(cm36651->ps_client); +		if (*val < 0) +			return ret; + +		if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { +			ret = i2c_smbus_write_byte_data(cm36651->ps_client, +					CM36651_PS_CONF1, CM36651_PS_DISABLE); +			if (ret < 0) +				return ret; +		} + +		ret = IIO_VAL_INT; +		break; +	default: +		break; +	} + +	return ret; +} + +static irqreturn_t cm36651_irq_handler(int irq, void *data) +{ +	struct iio_dev *indio_dev = data; +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	struct i2c_client *client = cm36651->client; +	int ev_dir, ret; +	u64 ev_code; + +	/* +	 * The PS INT pin is an active low signal that PS INT move logic low +	 * when the object is detect. Once the MCU host received the PS INT +	 * "LOW" signal, the Host needs to read the data at Alert Response +	 * Address(ARA) to clear the PS INT signal. After clearing the PS +	 * INT pin, the PS INT signal toggles from low to high. +	 */ +	ret = i2c_smbus_read_byte(cm36651->ara_client); +	if (ret < 0) { +		dev_err(&client->dev, +				"%s: Data read failed: %d\n", __func__, ret); +		return IRQ_HANDLED; +	} +	switch (ret) { +	case CM36651_CLOSE_PROXIMITY: +		ev_dir = IIO_EV_DIR_RISING; +		break; +	case CM36651_FAR_PROXIMITY: +		ev_dir = IIO_EV_DIR_FALLING; +		break; +	default: +		dev_err(&client->dev, +			"%s: Data read wrong: %d\n", __func__, ret); +		return IRQ_HANDLED; +	} + +	ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, +				CM36651_CMD_READ_RAW_PROXIMITY, +				IIO_EV_TYPE_THRESH, ev_dir); + +	iio_push_event(indio_dev, ev_code, iio_get_time_ns()); + +	return IRQ_HANDLED; +} + +static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd) +{ +	struct i2c_client *client = cm36651->client; +	struct i2c_client *ps_client = cm36651->ps_client; +	int ret = -EINVAL; + +	switch (cmd) { +	case CM36651_CMD_READ_RAW_LIGHT: +		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, +				cm36651->cs_ctrl_regs[CM36651_CS_CONF1]); +		break; +	case CM36651_CMD_READ_RAW_PROXIMITY: +		if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) +			return CM36651_PROXIMITY_EV_EN; + +		ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1, +				cm36651->ps_ctrl_regs[CM36651_PS_CONF1]); +		break; +	case CM36651_CMD_PROX_EV_EN: +		if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { +			dev_err(&client->dev, +				"Already proximity event enable state\n"); +			return ret; +		} +		set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + +		ret = i2c_smbus_write_byte_data(ps_client, +			cm36651_ps_reg[CM36651_PS_CONF1], +			CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2); + +		if (ret < 0) { +			dev_err(&client->dev, "Proximity enable event failed\n"); +			return ret; +		} +		break; +	case CM36651_CMD_PROX_EV_DIS: +		if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { +			dev_err(&client->dev, +				"Already proximity event disable state\n"); +			return ret; +		} +		clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); +		ret = i2c_smbus_write_byte_data(ps_client, +					CM36651_PS_CONF1, CM36651_PS_DISABLE); +		break; +	} + +	if (ret < 0) +		dev_err(&client->dev, "Write register failed\n"); + +	return ret; +} + +static int cm36651_read_channel(struct cm36651_data *cm36651, +				struct iio_chan_spec const *chan, int *val) +{ +	struct i2c_client *client = cm36651->client; +	int cmd, ret; + +	if (chan->type == IIO_LIGHT) +		cmd = CM36651_CMD_READ_RAW_LIGHT; +	else if (chan->type == IIO_PROXIMITY) +		cmd = CM36651_CMD_READ_RAW_PROXIMITY; +	else +		return -EINVAL; + +	ret = cm36651_set_operation_mode(cm36651, cmd); +	if (ret < 0) { +		dev_err(&client->dev, "CM36651 set operation mode failed\n"); +		return ret; +	} +	/* Delay for work after enable operation */ +	msleep(50); +	ret = cm36651_read_output(cm36651, chan, val); +	if (ret < 0) { +		dev_err(&client->dev, "CM36651 read output failed\n"); +		return ret; +	} + +	return ret; +} + +static int cm36651_read_int_time(struct cm36651_data *cm36651, +				struct iio_chan_spec const *chan, int *val2) +{ +	switch (chan->type) { +	case IIO_LIGHT: +		if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1) +			*val2 = 80000; +		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2) +			*val2 = 160000; +		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3) +			*val2 = 320000; +		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4) +			*val2 = 640000; +		else +			return -EINVAL; +		break; +	case IIO_PROXIMITY: +		if (cm36651->ps_int_time == CM36651_PS_IT1) +			*val2 = 320; +		else if (cm36651->ps_int_time == CM36651_PS_IT2) +			*val2 = 420; +		else if (cm36651->ps_int_time == CM36651_PS_IT3) +			*val2 = 520; +		else if (cm36651->ps_int_time == CM36651_PS_IT4) +			*val2 = 640; +		else +			return -EINVAL; +		break; +	default: +		return -EINVAL; +	} + +	return IIO_VAL_INT_PLUS_MICRO; +} + +static int cm36651_write_int_time(struct cm36651_data *cm36651, +				struct iio_chan_spec const *chan, int val) +{ +	struct i2c_client *client = cm36651->client; +	struct i2c_client *ps_client = cm36651->ps_client; +	int int_time, ret; + +	switch (chan->type) { +	case IIO_LIGHT: +		if (val == 80000) +			int_time = CM36651_CS_IT1; +		else if (val == 160000) +			int_time = CM36651_CS_IT2; +		else if (val == 320000) +			int_time = CM36651_CS_IT3; +		else if (val == 640000) +			int_time = CM36651_CS_IT4; +		else +			return -EINVAL; + +		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3, +					   int_time >> 2 * (chan->address)); +		if (ret < 0) { +			dev_err(&client->dev, "CS integration time write failed\n"); +			return ret; +		} +		cm36651->cs_int_time[chan->address] = int_time; +		break; +	case IIO_PROXIMITY: +		if (val == 320) +			int_time = CM36651_PS_IT1; +		else if (val == 420) +			int_time = CM36651_PS_IT2; +		else if (val == 520) +			int_time = CM36651_PS_IT3; +		else if (val == 640) +			int_time = CM36651_PS_IT4; +		else +			return -EINVAL; + +		ret = i2c_smbus_write_byte_data(ps_client, +						CM36651_PS_CONF1, int_time); +		if (ret < 0) { +			dev_err(&client->dev, "PS integration time write failed\n"); +			return ret; +		} +		cm36651->ps_int_time = int_time; +		break; +	default: +		return -EINVAL; +	} + +	return ret; +} + +static int cm36651_read_raw(struct iio_dev *indio_dev, +			    struct iio_chan_spec const *chan, +			    int *val, int *val2, long mask) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	int ret; + +	mutex_lock(&cm36651->lock); + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		ret = cm36651_read_channel(cm36651, chan, val); +		break; +	case IIO_CHAN_INFO_INT_TIME: +		*val = 0; +		ret = cm36651_read_int_time(cm36651, chan, val2); +		break; +	default: +		ret = -EINVAL; +	} + +	mutex_unlock(&cm36651->lock); + +	return ret; +} + +static int cm36651_write_raw(struct iio_dev *indio_dev, +			     struct iio_chan_spec const *chan, +			     int val, int val2, long mask) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	struct i2c_client *client = cm36651->client; +	int ret = -EINVAL; + +	if (mask == IIO_CHAN_INFO_INT_TIME) { +		ret = cm36651_write_int_time(cm36651, chan, val2); +		if (ret < 0) +			dev_err(&client->dev, "Integration time write failed\n"); +	} + +	return ret; +} + +static int cm36651_read_prox_thresh(struct iio_dev *indio_dev, +					const struct iio_chan_spec *chan, +					enum iio_event_type type, +					enum iio_event_direction dir, +					enum iio_event_info info, +					int *val, int *val2) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); + +	*val = cm36651->ps_ctrl_regs[CM36651_PS_THD]; + +	return 0; +} + +static int cm36651_write_prox_thresh(struct iio_dev *indio_dev, +					const struct iio_chan_spec *chan, +					enum iio_event_type type, +					enum iio_event_direction dir, +					enum iio_event_info info, +					int val, int val2) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	struct i2c_client *client = cm36651->client; +	int ret; + +	if (val < 3 || val > 255) +		return -EINVAL; + +	cm36651->ps_ctrl_regs[CM36651_PS_THD] = val; +	ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD, +					cm36651->ps_ctrl_regs[CM36651_PS_THD]); + +	if (ret < 0) { +		dev_err(&client->dev, "PS threshold write failed: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static int cm36651_write_prox_event_config(struct iio_dev *indio_dev, +					const struct iio_chan_spec *chan, +					enum iio_event_type type, +					enum iio_event_direction dir, +					int state) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	int cmd, ret = -EINVAL; + +	mutex_lock(&cm36651->lock); + +	cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS; +	ret = cm36651_set_operation_mode(cm36651, cmd); + +	mutex_unlock(&cm36651->lock); + +	return ret; +} + +static int cm36651_read_prox_event_config(struct iio_dev *indio_dev, +					const struct iio_chan_spec *chan, +					enum iio_event_type type, +					enum iio_event_direction dir) +{ +	struct cm36651_data *cm36651 = iio_priv(indio_dev); +	int event_en; + +	mutex_lock(&cm36651->lock); + +	event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + +	mutex_unlock(&cm36651->lock); + +	return event_en; +} + +#define CM36651_LIGHT_CHANNEL(_color, _idx) {		\ +	.type = IIO_LIGHT,				\ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\ +			BIT(IIO_CHAN_INFO_INT_TIME),	\ +	.address = _idx,				\ +	.modified = 1,					\ +	.channel2 = IIO_MOD_LIGHT_##_color,		\ +}							\ + +static const struct iio_event_spec cm36651_event_spec[] = { +	{ +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_EITHER, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +				BIT(IIO_EV_INFO_ENABLE), +	} +}; + +static const struct iio_chan_spec cm36651_channels[] = { +	{ +		.type = IIO_PROXIMITY, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | +				BIT(IIO_CHAN_INFO_INT_TIME), +		.event_spec = cm36651_event_spec, +		.num_event_specs = ARRAY_SIZE(cm36651_event_spec), +	}, +	CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED), +	CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN), +	CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE), +	CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR), +}; + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, +					CM36651_CS_INT_TIME_AVAIL); +static IIO_CONST_ATTR(in_proximity_integration_time_available, +					CM36651_PS_INT_TIME_AVAIL); + +static struct attribute *cm36651_attributes[] = { +	&iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, +	&iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, +	NULL, +}; + +static const struct attribute_group cm36651_attribute_group = { +	.attrs = cm36651_attributes +}; + +static const struct iio_info cm36651_info = { +	.driver_module		= THIS_MODULE, +	.read_raw		= &cm36651_read_raw, +	.write_raw		= &cm36651_write_raw, +	.read_event_value	= &cm36651_read_prox_thresh, +	.write_event_value	= &cm36651_write_prox_thresh, +	.read_event_config	= &cm36651_read_prox_event_config, +	.write_event_config	= &cm36651_write_prox_event_config, +	.attrs			= &cm36651_attribute_group, +}; + +static int cm36651_probe(struct i2c_client *client, +			     const struct i2c_device_id *id) +{ +	struct cm36651_data *cm36651; +	struct iio_dev *indio_dev; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651)); +	if (!indio_dev) +		return -ENOMEM; + +	cm36651 = iio_priv(indio_dev); + +	cm36651->vled_reg = devm_regulator_get(&client->dev, "vled"); +	if (IS_ERR(cm36651->vled_reg)) { +		dev_err(&client->dev, "get regulator vled failed\n"); +		return PTR_ERR(cm36651->vled_reg); +	} + +	ret = regulator_enable(cm36651->vled_reg); +	if (ret) { +		dev_err(&client->dev, "enable regulator vled failed\n"); +		return ret; +	} + +	i2c_set_clientdata(client, indio_dev); + +	cm36651->client = client; +	cm36651->ps_client = i2c_new_dummy(client->adapter, +						     CM36651_I2C_ADDR_PS); +	if (!cm36651->ps_client) { +		dev_err(&client->dev, "%s: new i2c device failed\n", __func__); +		ret = -ENODEV; +		goto error_disable_reg; +	} + +	cm36651->ara_client = i2c_new_dummy(client->adapter, CM36651_ARA); +	if (!cm36651->ara_client) { +		dev_err(&client->dev, "%s: new i2c device failed\n", __func__); +		ret = -ENODEV; +		goto error_i2c_unregister_ps; +	} + +	mutex_init(&cm36651->lock); +	indio_dev->dev.parent = &client->dev; +	indio_dev->channels = cm36651_channels; +	indio_dev->num_channels = ARRAY_SIZE(cm36651_channels); +	indio_dev->info = &cm36651_info; +	indio_dev->name = id->name; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = cm36651_setup_reg(cm36651); +	if (ret) { +		dev_err(&client->dev, "%s: register setup failed\n", __func__); +		goto error_i2c_unregister_ara; +	} + +	ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler, +					IRQF_TRIGGER_FALLING | IRQF_ONESHOT, +							"cm36651", indio_dev); +	if (ret) { +		dev_err(&client->dev, "%s: request irq failed\n", __func__); +		goto error_i2c_unregister_ara; +	} + +	ret = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&client->dev, "%s: regist device failed\n", __func__); +		goto error_free_irq; +	} + +	return 0; + +error_free_irq: +	free_irq(client->irq, indio_dev); +error_i2c_unregister_ara: +	i2c_unregister_device(cm36651->ara_client); +error_i2c_unregister_ps: +	i2c_unregister_device(cm36651->ps_client); +error_disable_reg: +	regulator_disable(cm36651->vled_reg); +	return ret; +} + +static int cm36651_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); +	struct cm36651_data *cm36651 = iio_priv(indio_dev); + +	iio_device_unregister(indio_dev); +	regulator_disable(cm36651->vled_reg); +	free_irq(client->irq, indio_dev); +	i2c_unregister_device(cm36651->ps_client); +	i2c_unregister_device(cm36651->ara_client); + +	return 0; +} + +static const struct i2c_device_id cm36651_id[] = { +	{ "cm36651", 0 }, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, cm36651_id); + +static const struct of_device_id cm36651_of_match[] = { +	{ .compatible = "capella,cm36651" }, +	{ } +}; + +static struct i2c_driver cm36651_driver = { +	.driver = { +		.name	= "cm36651", +		.of_match_table = cm36651_of_match, +		.owner	= THIS_MODULE, +	}, +	.probe		= cm36651_probe, +	.remove		= cm36651_remove, +	.id_table	= cm36651_id, +}; + +module_i2c_driver(cm36651_driver); + +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c new file mode 100644 index 00000000000..04bdb85d2d9 --- /dev/null +++ b/drivers/iio/light/gp2ap020a00f.c @@ -0,0 +1,1654 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + * + * IIO features supported by the driver: + * + * Read-only raw channels: + *   - illuminance_clear [lux] + *   - illuminance_ir + *   - proximity + * + * Triggered buffer: + *   - illuminance_clear + *   - illuminance_ir + *   - proximity + * + * Events: + *   - illuminance_clear (rising and falling) + *   - proximity (rising and falling) + *     - both falling and rising thresholds for the proximity events + *       must be set to the values greater than 0. + * + * The driver supports triggered buffers for all the three + * channels as well as high and low threshold events for the + * illuminance_clear and proxmimity channels. Triggers + * can be enabled simultaneously with both illuminance_clear + * events. Proximity events cannot be enabled simultaneously + * with any triggers or illuminance events. Enabling/disabling + * one of the proximity events automatically enables/disables + * the other one. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irq_work.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define GP2A_I2C_NAME "gp2ap020a00f" + +/* Registers */ +#define GP2AP020A00F_OP_REG	0x00 /* Basic operations */ +#define GP2AP020A00F_ALS_REG	0x01 /* ALS related settings */ +#define GP2AP020A00F_PS_REG	0x02 /* PS related settings */ +#define GP2AP020A00F_LED_REG	0x03 /* LED reg */ +#define GP2AP020A00F_TL_L_REG	0x04 /* ALS: Threshold low LSB */ +#define GP2AP020A00F_TL_H_REG	0x05 /* ALS: Threshold low MSB */ +#define GP2AP020A00F_TH_L_REG	0x06 /* ALS: Threshold high LSB */ +#define GP2AP020A00F_TH_H_REG	0x07 /* ALS: Threshold high MSB */ +#define GP2AP020A00F_PL_L_REG	0x08 /* PS: Threshold low LSB */ +#define GP2AP020A00F_PL_H_REG	0x09 /* PS: Threshold low MSB */ +#define GP2AP020A00F_PH_L_REG	0x0a /* PS: Threshold high LSB */ +#define GP2AP020A00F_PH_H_REG	0x0b /* PS: Threshold high MSB */ +#define GP2AP020A00F_D0_L_REG	0x0c /* ALS result: Clear/Illuminance LSB */ +#define GP2AP020A00F_D0_H_REG	0x0d /* ALS result: Clear/Illuminance MSB */ +#define GP2AP020A00F_D1_L_REG	0x0e /* ALS result: IR LSB */ +#define GP2AP020A00F_D1_H_REG	0x0f /* ALS result: IR LSB */ +#define GP2AP020A00F_D2_L_REG	0x10 /* PS result LSB */ +#define GP2AP020A00F_D2_H_REG	0x11 /* PS result MSB */ +#define GP2AP020A00F_NUM_REGS	0x12 /* Number of registers */ + +/* OP_REG bits */ +#define GP2AP020A00F_OP3_MASK		0x80 /* Software shutdown */ +#define GP2AP020A00F_OP3_SHUTDOWN	0x00 +#define GP2AP020A00F_OP3_OPERATION	0x80 +#define GP2AP020A00F_OP2_MASK		0x40 /* Auto shutdown/Continuous mode */ +#define GP2AP020A00F_OP2_AUTO_SHUTDOWN	0x00 +#define GP2AP020A00F_OP2_CONT_OPERATION	0x40 +#define GP2AP020A00F_OP_MASK		0x30 /* Operating mode selection  */ +#define GP2AP020A00F_OP_ALS_AND_PS	0x00 +#define GP2AP020A00F_OP_ALS		0x10 +#define GP2AP020A00F_OP_PS		0x20 +#define GP2AP020A00F_OP_DEBUG		0x30 +#define GP2AP020A00F_PROX_MASK		0x08 /* PS: detection/non-detection */ +#define GP2AP020A00F_PROX_NON_DETECT	0x00 +#define GP2AP020A00F_PROX_DETECT	0x08 +#define GP2AP020A00F_FLAG_P		0x04 /* PS: interrupt result  */ +#define GP2AP020A00F_FLAG_A		0x02 /* ALS: interrupt result  */ +#define GP2AP020A00F_TYPE_MASK		0x01 /* Output data type selection */ +#define GP2AP020A00F_TYPE_MANUAL_CALC	0x00 +#define GP2AP020A00F_TYPE_AUTO_CALC	0x01 + +/* ALS_REG bits */ +#define GP2AP020A00F_PRST_MASK		0xc0 /* Number of measurement cycles */ +#define GP2AP020A00F_PRST_ONCE		0x00 +#define GP2AP020A00F_PRST_4_CYCLES	0x40 +#define GP2AP020A00F_PRST_8_CYCLES	0x80 +#define GP2AP020A00F_PRST_16_CYCLES	0xc0 +#define GP2AP020A00F_RES_A_MASK		0x38 /* ALS: Resolution */ +#define GP2AP020A00F_RES_A_800ms	0x00 +#define GP2AP020A00F_RES_A_400ms	0x08 +#define GP2AP020A00F_RES_A_200ms	0x10 +#define GP2AP020A00F_RES_A_100ms	0x18 +#define GP2AP020A00F_RES_A_25ms		0x20 +#define GP2AP020A00F_RES_A_6_25ms	0x28 +#define GP2AP020A00F_RES_A_1_56ms	0x30 +#define GP2AP020A00F_RES_A_0_39ms	0x38 +#define GP2AP020A00F_RANGE_A_MASK	0x07 /* ALS: Max measurable range */ +#define GP2AP020A00F_RANGE_A_x1		0x00 +#define GP2AP020A00F_RANGE_A_x2		0x01 +#define GP2AP020A00F_RANGE_A_x4		0x02 +#define GP2AP020A00F_RANGE_A_x8		0x03 +#define GP2AP020A00F_RANGE_A_x16	0x04 +#define GP2AP020A00F_RANGE_A_x32	0x05 +#define GP2AP020A00F_RANGE_A_x64	0x06 +#define GP2AP020A00F_RANGE_A_x128	0x07 + +/* PS_REG bits */ +#define GP2AP020A00F_ALC_MASK		0x80 /* Auto light cancel */ +#define GP2AP020A00F_ALC_ON		0x80 +#define GP2AP020A00F_ALC_OFF		0x00 +#define GP2AP020A00F_INTTYPE_MASK	0x40 /* Interrupt type setting */ +#define GP2AP020A00F_INTTYPE_LEVEL	0x00 +#define GP2AP020A00F_INTTYPE_PULSE	0x40 +#define GP2AP020A00F_RES_P_MASK		0x38 /* PS: Resolution */ +#define GP2AP020A00F_RES_P_800ms_x2	0x00 +#define GP2AP020A00F_RES_P_400ms_x2	0x08 +#define GP2AP020A00F_RES_P_200ms_x2	0x10 +#define GP2AP020A00F_RES_P_100ms_x2	0x18 +#define GP2AP020A00F_RES_P_25ms_x2	0x20 +#define GP2AP020A00F_RES_P_6_25ms_x2	0x28 +#define GP2AP020A00F_RES_P_1_56ms_x2	0x30 +#define GP2AP020A00F_RES_P_0_39ms_x2	0x38 +#define GP2AP020A00F_RANGE_P_MASK	0x07 /* PS: Max measurable range */ +#define GP2AP020A00F_RANGE_P_x1		0x00 +#define GP2AP020A00F_RANGE_P_x2		0x01 +#define GP2AP020A00F_RANGE_P_x4		0x02 +#define GP2AP020A00F_RANGE_P_x8		0x03 +#define GP2AP020A00F_RANGE_P_x16	0x04 +#define GP2AP020A00F_RANGE_P_x32	0x05 +#define GP2AP020A00F_RANGE_P_x64	0x06 +#define GP2AP020A00F_RANGE_P_x128	0x07 + +/* LED reg bits */ +#define GP2AP020A00F_INTVAL_MASK	0xc0 /* Intermittent operating */ +#define GP2AP020A00F_INTVAL_0		0x00 +#define GP2AP020A00F_INTVAL_4		0x40 +#define GP2AP020A00F_INTVAL_8		0x80 +#define GP2AP020A00F_INTVAL_16		0xc0 +#define GP2AP020A00F_IS_MASK		0x30 /* ILED drive peak current */ +#define GP2AP020A00F_IS_13_8mA		0x00 +#define GP2AP020A00F_IS_27_5mA		0x10 +#define GP2AP020A00F_IS_55mA		0x20 +#define GP2AP020A00F_IS_110mA		0x30 +#define GP2AP020A00F_PIN_MASK		0x0c /* INT terminal setting */ +#define GP2AP020A00F_PIN_ALS_OR_PS	0x00 +#define GP2AP020A00F_PIN_ALS		0x04 +#define GP2AP020A00F_PIN_PS		0x08 +#define GP2AP020A00F_PIN_PS_DETECT	0x0c +#define GP2AP020A00F_FREQ_MASK		0x02 /* LED modulation frequency */ +#define GP2AP020A00F_FREQ_327_5kHz	0x00 +#define GP2AP020A00F_FREQ_81_8kHz	0x02 +#define GP2AP020A00F_RST		0x01 /* Software reset */ + +#define GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR	0 +#define GP2AP020A00F_SCAN_MODE_LIGHT_IR		1 +#define GP2AP020A00F_SCAN_MODE_PROXIMITY	2 +#define GP2AP020A00F_CHAN_TIMESTAMP		3 + +#define GP2AP020A00F_DATA_READY_TIMEOUT		msecs_to_jiffies(1000) +#define GP2AP020A00F_DATA_REG(chan)		(GP2AP020A00F_D0_L_REG + \ +							(chan) * 2) +#define GP2AP020A00F_THRESH_REG(th_val_id)	(GP2AP020A00F_TL_L_REG + \ +							(th_val_id) * 2) +#define GP2AP020A00F_THRESH_VAL_ID(reg_addr)	((reg_addr - 4) / 2) + +#define GP2AP020A00F_SUBTRACT_MODE	0 +#define GP2AP020A00F_ADD_MODE		1 + +#define GP2AP020A00F_MAX_CHANNELS	3 + +enum gp2ap020a00f_opmode { +	GP2AP020A00F_OPMODE_READ_RAW_CLEAR, +	GP2AP020A00F_OPMODE_READ_RAW_IR, +	GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY, +	GP2AP020A00F_OPMODE_ALS, +	GP2AP020A00F_OPMODE_PS, +	GP2AP020A00F_OPMODE_ALS_AND_PS, +	GP2AP020A00F_OPMODE_PROX_DETECT, +	GP2AP020A00F_OPMODE_SHUTDOWN, +	GP2AP020A00F_NUM_OPMODES, +}; + +enum gp2ap020a00f_cmd { +	GP2AP020A00F_CMD_READ_RAW_CLEAR, +	GP2AP020A00F_CMD_READ_RAW_IR, +	GP2AP020A00F_CMD_READ_RAW_PROXIMITY, +	GP2AP020A00F_CMD_TRIGGER_CLEAR_EN, +	GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS, +	GP2AP020A00F_CMD_TRIGGER_IR_EN, +	GP2AP020A00F_CMD_TRIGGER_IR_DIS, +	GP2AP020A00F_CMD_TRIGGER_PROX_EN, +	GP2AP020A00F_CMD_TRIGGER_PROX_DIS, +	GP2AP020A00F_CMD_ALS_HIGH_EV_EN, +	GP2AP020A00F_CMD_ALS_HIGH_EV_DIS, +	GP2AP020A00F_CMD_ALS_LOW_EV_EN, +	GP2AP020A00F_CMD_ALS_LOW_EV_DIS, +	GP2AP020A00F_CMD_PROX_HIGH_EV_EN, +	GP2AP020A00F_CMD_PROX_HIGH_EV_DIS, +	GP2AP020A00F_CMD_PROX_LOW_EV_EN, +	GP2AP020A00F_CMD_PROX_LOW_EV_DIS, +}; + +enum gp2ap020a00f_flags { +	GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, +	GP2AP020A00F_FLAG_ALS_IR_TRIGGER, +	GP2AP020A00F_FLAG_PROX_TRIGGER, +	GP2AP020A00F_FLAG_PROX_RISING_EV, +	GP2AP020A00F_FLAG_PROX_FALLING_EV, +	GP2AP020A00F_FLAG_ALS_RISING_EV, +	GP2AP020A00F_FLAG_ALS_FALLING_EV, +	GP2AP020A00F_FLAG_LUX_MODE_HI, +	GP2AP020A00F_FLAG_DATA_READY, +}; + +enum gp2ap020a00f_thresh_val_id { +	GP2AP020A00F_THRESH_TL, +	GP2AP020A00F_THRESH_TH, +	GP2AP020A00F_THRESH_PL, +	GP2AP020A00F_THRESH_PH, +}; + +struct gp2ap020a00f_data { +	const struct gp2ap020a00f_platform_data *pdata; +	struct i2c_client *client; +	struct mutex lock; +	char *buffer; +	struct regulator *vled_reg; +	unsigned long flags; +	enum gp2ap020a00f_opmode cur_opmode; +	struct iio_trigger *trig; +	struct regmap *regmap; +	unsigned int thresh_val[4]; +	u8 debug_reg_addr; +	struct irq_work work; +	wait_queue_head_t data_ready_queue; +}; + +static const u8 gp2ap020a00f_reg_init_tab[] = { +	[GP2AP020A00F_OP_REG] = GP2AP020A00F_OP3_SHUTDOWN, +	[GP2AP020A00F_ALS_REG] = GP2AP020A00F_RES_A_25ms | +				 GP2AP020A00F_RANGE_A_x8, +	[GP2AP020A00F_PS_REG] = GP2AP020A00F_ALC_ON | +				GP2AP020A00F_RES_P_1_56ms_x2 | +				GP2AP020A00F_RANGE_P_x4, +	[GP2AP020A00F_LED_REG] = GP2AP020A00F_INTVAL_0 | +				 GP2AP020A00F_IS_110mA | +				 GP2AP020A00F_FREQ_327_5kHz, +	[GP2AP020A00F_TL_L_REG] = 0, +	[GP2AP020A00F_TL_H_REG] = 0, +	[GP2AP020A00F_TH_L_REG] = 0, +	[GP2AP020A00F_TH_H_REG] = 0, +	[GP2AP020A00F_PL_L_REG] = 0, +	[GP2AP020A00F_PL_H_REG] = 0, +	[GP2AP020A00F_PH_L_REG] = 0, +	[GP2AP020A00F_PH_H_REG] = 0, +}; + +static bool gp2ap020a00f_is_volatile_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case GP2AP020A00F_OP_REG: +	case GP2AP020A00F_D0_L_REG: +	case GP2AP020A00F_D0_H_REG: +	case GP2AP020A00F_D1_L_REG: +	case GP2AP020A00F_D1_H_REG: +	case GP2AP020A00F_D2_L_REG: +	case GP2AP020A00F_D2_H_REG: +		return true; +	default: +		return false; +	} +} + +static const struct regmap_config gp2ap020a00f_regmap_config = { +	.reg_bits = 8, +	.val_bits = 8, + +	.max_register = GP2AP020A00F_D2_H_REG, +	.cache_type = REGCACHE_RBTREE, + +	.volatile_reg = gp2ap020a00f_is_volatile_reg, +}; + +static const struct gp2ap020a00f_mutable_config_regs { +	u8 op_reg; +	u8 als_reg; +	u8 ps_reg; +	u8 led_reg; +} opmode_regs_settings[GP2AP020A00F_NUM_OPMODES] = { +	[GP2AP020A00F_OPMODE_READ_RAW_CLEAR] = { +		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_AUTO_CALC, +		GP2AP020A00F_PRST_ONCE, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_ALS +	}, +	[GP2AP020A00F_OPMODE_READ_RAW_IR] = { +		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_MANUAL_CALC, +		GP2AP020A00F_PRST_ONCE, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_ALS +	}, +	[GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY] = { +		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_MANUAL_CALC, +		GP2AP020A00F_PRST_ONCE, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_PS +	}, +	[GP2AP020A00F_OPMODE_PROX_DETECT] = { +		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_MANUAL_CALC, +		GP2AP020A00F_PRST_4_CYCLES, +		GP2AP020A00F_INTTYPE_PULSE, +		GP2AP020A00F_PIN_PS_DETECT +	}, +	[GP2AP020A00F_OPMODE_ALS] = { +		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_AUTO_CALC, +		GP2AP020A00F_PRST_ONCE, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_ALS +	}, +	[GP2AP020A00F_OPMODE_PS] = { +		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_MANUAL_CALC, +		GP2AP020A00F_PRST_4_CYCLES, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_PS +	}, +	[GP2AP020A00F_OPMODE_ALS_AND_PS] = { +		GP2AP020A00F_OP_ALS_AND_PS +		| GP2AP020A00F_OP2_CONT_OPERATION +		| GP2AP020A00F_OP3_OPERATION +		| GP2AP020A00F_TYPE_AUTO_CALC, +		GP2AP020A00F_PRST_4_CYCLES, +		GP2AP020A00F_INTTYPE_LEVEL, +		GP2AP020A00F_PIN_ALS_OR_PS +	}, +	[GP2AP020A00F_OPMODE_SHUTDOWN] = { GP2AP020A00F_OP3_SHUTDOWN, }, +}; + +static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data, +					enum gp2ap020a00f_opmode op) +{ +	unsigned int op_reg_val; +	int err; + +	if (op != GP2AP020A00F_OPMODE_SHUTDOWN) { +		err = regmap_read(data->regmap, GP2AP020A00F_OP_REG, +					&op_reg_val); +		if (err < 0) +			return err; +		/* +		 * Shutdown the device if the operation being executed entails +		 * mode transition. +		 */ +		if ((opmode_regs_settings[op].op_reg & GP2AP020A00F_OP_MASK) != +		    (op_reg_val & GP2AP020A00F_OP_MASK)) { +			/* set shutdown mode */ +			err = regmap_update_bits(data->regmap, +				GP2AP020A00F_OP_REG, GP2AP020A00F_OP3_MASK, +				GP2AP020A00F_OP3_SHUTDOWN); +			if (err < 0) +				return err; +		} + +		err = regmap_update_bits(data->regmap, GP2AP020A00F_ALS_REG, +			GP2AP020A00F_PRST_MASK, opmode_regs_settings[op] +								.als_reg); +		if (err < 0) +			return err; + +		err = regmap_update_bits(data->regmap, GP2AP020A00F_PS_REG, +			GP2AP020A00F_INTTYPE_MASK, opmode_regs_settings[op] +								.ps_reg); +		if (err < 0) +			return err; + +		err = regmap_update_bits(data->regmap, GP2AP020A00F_LED_REG, +			GP2AP020A00F_PIN_MASK, opmode_regs_settings[op] +								.led_reg); +		if (err < 0) +			return err; +	} + +	/* Set OP_REG and apply operation mode (power on / off) */ +	err = regmap_update_bits(data->regmap, +				 GP2AP020A00F_OP_REG, +				 GP2AP020A00F_OP_MASK | GP2AP020A00F_OP2_MASK | +				 GP2AP020A00F_OP3_MASK | GP2AP020A00F_TYPE_MASK, +				 opmode_regs_settings[op].op_reg); +	if (err < 0) +		return err; + +	data->cur_opmode = op; + +	return 0; +} + +static bool gp2ap020a00f_als_enabled(struct gp2ap020a00f_data *data) +{ +	return test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags) || +	       test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags) || +	       test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags) || +	       test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +} + +static bool gp2ap020a00f_prox_detect_enabled(struct gp2ap020a00f_data *data) +{ +	return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags) || +	       test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +} + +static int gp2ap020a00f_write_event_threshold(struct gp2ap020a00f_data *data, +				enum gp2ap020a00f_thresh_val_id th_val_id, +				bool enable) +{ +	__le16 thresh_buf = 0; +	unsigned int thresh_reg_val; + +	if (!enable) +		thresh_reg_val = 0; +	else if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags) && +		 th_val_id != GP2AP020A00F_THRESH_PL && +		 th_val_id != GP2AP020A00F_THRESH_PH) +		/* +		 * For the high lux mode ALS threshold has to be scaled down +		 * to allow for proper comparison with the output value. +		 */ +		thresh_reg_val = data->thresh_val[th_val_id] / 16; +	else +		thresh_reg_val = data->thresh_val[th_val_id] > 16000 ? +					16000 : +					data->thresh_val[th_val_id]; + +	thresh_buf = cpu_to_le16(thresh_reg_val); + +	return regmap_bulk_write(data->regmap, +				 GP2AP020A00F_THRESH_REG(th_val_id), +				 (u8 *)&thresh_buf, 2); +} + +static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data, +			enum gp2ap020a00f_opmode diff_mode, int add_sub) +{ +	enum gp2ap020a00f_opmode new_mode; + +	if (diff_mode != GP2AP020A00F_OPMODE_ALS && +	    diff_mode != GP2AP020A00F_OPMODE_PS) +		return -EINVAL; + +	if (add_sub == GP2AP020A00F_ADD_MODE) { +		if (data->cur_opmode == GP2AP020A00F_OPMODE_SHUTDOWN) +			new_mode =  diff_mode; +		else +			new_mode = GP2AP020A00F_OPMODE_ALS_AND_PS; +	} else { +		if (data->cur_opmode == GP2AP020A00F_OPMODE_ALS_AND_PS) +			new_mode = (diff_mode == GP2AP020A00F_OPMODE_ALS) ? +					GP2AP020A00F_OPMODE_PS : +					GP2AP020A00F_OPMODE_ALS; +		else +			new_mode = GP2AP020A00F_OPMODE_SHUTDOWN; +	} + +	return gp2ap020a00f_set_operation_mode(data, new_mode); +} + +static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data, +					enum gp2ap020a00f_cmd cmd) +{ +	int err = 0; + +	switch (cmd) { +	case GP2AP020A00F_CMD_READ_RAW_CLEAR: +		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) +			return -EBUSY; +		err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_READ_RAW_CLEAR); +		break; +	case GP2AP020A00F_CMD_READ_RAW_IR: +		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) +			return -EBUSY; +		err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_READ_RAW_IR); +		break; +	case GP2AP020A00F_CMD_READ_RAW_PROXIMITY: +		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) +			return -EBUSY; +		err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY); +		break; +	case GP2AP020A00F_CMD_TRIGGER_CLEAR_EN: +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) +			return -EBUSY; +		if (!gp2ap020a00f_als_enabled(data)) +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_ADD_MODE); +		set_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); +		break; +	case GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS: +		clear_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); +		if (gp2ap020a00f_als_enabled(data)) +			break; +		err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_SUBTRACT_MODE); +		break; +	case GP2AP020A00F_CMD_TRIGGER_IR_EN: +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) +			return -EBUSY; +		if (!gp2ap020a00f_als_enabled(data)) +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_ADD_MODE); +		set_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); +		break; +	case GP2AP020A00F_CMD_TRIGGER_IR_DIS: +		clear_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); +		if (gp2ap020a00f_als_enabled(data)) +			break; +		err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_SUBTRACT_MODE); +		break; +	case GP2AP020A00F_CMD_TRIGGER_PROX_EN: +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) +			return -EBUSY; +		err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_PS, +						GP2AP020A00F_ADD_MODE); +		set_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); +		break; +	case GP2AP020A00F_CMD_TRIGGER_PROX_DIS: +		clear_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); +		err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_PS, +						GP2AP020A00F_SUBTRACT_MODE); +		break; +	case GP2AP020A00F_CMD_ALS_HIGH_EV_EN: +		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) +			return 0; +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) +			return -EBUSY; +		if (!gp2ap020a00f_als_enabled(data)) { +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_ADD_MODE); +			if (err < 0) +				return err; +		} +		set_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TH, true); +		break; +	case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS: +		if (!test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) +			return 0; +		clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); +		if (!gp2ap020a00f_als_enabled(data)) { +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_SUBTRACT_MODE); +			if (err < 0) +				return err; +		} +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TH, false); +		break; +	case GP2AP020A00F_CMD_ALS_LOW_EV_EN: +		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) +			return 0; +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) +			return -EBUSY; +		if (!gp2ap020a00f_als_enabled(data)) { +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_ADD_MODE); +			if (err < 0) +				return err; +		} +		set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TL, true); +		break; +	case GP2AP020A00F_CMD_ALS_LOW_EV_DIS: +		if (!test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) +			return 0; +		clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +		if (!gp2ap020a00f_als_enabled(data)) { +			err = gp2ap020a00f_alter_opmode(data, +						GP2AP020A00F_OPMODE_ALS, +						GP2AP020A00F_SUBTRACT_MODE); +			if (err < 0) +				return err; +		} +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TL, false); +		break; +	case GP2AP020A00F_CMD_PROX_HIGH_EV_EN: +		if (test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) +			return 0; +		if (gp2ap020a00f_als_enabled(data) || +		    data->cur_opmode == GP2AP020A00F_OPMODE_PS) +			return -EBUSY; +		if (!gp2ap020a00f_prox_detect_enabled(data)) { +			err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_PROX_DETECT); +			if (err < 0) +				return err; +		} +		set_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_PH, true); +		break; +	case GP2AP020A00F_CMD_PROX_HIGH_EV_DIS: +		if (!test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) +			return 0; +		clear_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); +		err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_SHUTDOWN); +		if (err < 0) +			return err; +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_PH, false); +		break; +	case GP2AP020A00F_CMD_PROX_LOW_EV_EN: +		if (test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) +			return 0; +		if (gp2ap020a00f_als_enabled(data) || +		    data->cur_opmode == GP2AP020A00F_OPMODE_PS) +			return -EBUSY; +		if (!gp2ap020a00f_prox_detect_enabled(data)) { +			err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_PROX_DETECT); +			if (err < 0) +				return err; +		} +		set_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_PL, true); +		break; +	case GP2AP020A00F_CMD_PROX_LOW_EV_DIS: +		if (!test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) +			return 0; +		clear_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +		err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_SHUTDOWN); +		if (err < 0) +			return err; +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_PL, false); +		break; +	} + +	return err; +} + +static int wait_conversion_complete_irq(struct gp2ap020a00f_data *data) +{ +	int ret; + +	ret = wait_event_timeout(data->data_ready_queue, +				 test_bit(GP2AP020A00F_FLAG_DATA_READY, +					  &data->flags), +				 GP2AP020A00F_DATA_READY_TIMEOUT); +	clear_bit(GP2AP020A00F_FLAG_DATA_READY, &data->flags); + +	return ret > 0 ? 0 : -ETIME; +} + +static int gp2ap020a00f_read_output(struct gp2ap020a00f_data *data, +					unsigned int output_reg, int *val) +{ +	u8 reg_buf[2]; +	int err; + +	err = wait_conversion_complete_irq(data); +	if (err < 0) +		dev_dbg(&data->client->dev, "data ready timeout\n"); + +	err = regmap_bulk_read(data->regmap, output_reg, reg_buf, 2); +	if (err < 0) +		return err; + +	*val = le16_to_cpup((__le16 *)reg_buf); + +	return err; +} + +static bool gp2ap020a00f_adjust_lux_mode(struct gp2ap020a00f_data *data, +				 int output_val) +{ +	u8 new_range = 0xff; +	int err; + +	if (!test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) { +		if (output_val > 16000) { +			set_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); +			new_range = GP2AP020A00F_RANGE_A_x128; +		} +	} else { +		if (output_val < 1000) { +			clear_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); +			new_range = GP2AP020A00F_RANGE_A_x8; +		} +	} + +	if (new_range != 0xff) { +		/* Clear als threshold registers to avoid spurious +		 * events caused by lux mode transition. +		 */ +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TH, false); +		if (err < 0) { +			dev_err(&data->client->dev, +				"Clearing als threshold register failed.\n"); +			return false; +		} + +		err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TL, false); +		if (err < 0) { +			dev_err(&data->client->dev, +				"Clearing als threshold register failed.\n"); +			return false; +		} + +		/* Change lux mode */ +		err = regmap_update_bits(data->regmap, +			GP2AP020A00F_OP_REG, +			GP2AP020A00F_OP3_MASK, +			GP2AP020A00F_OP3_SHUTDOWN); + +		if (err < 0) { +			dev_err(&data->client->dev, +				"Shutting down the device failed.\n"); +			return false; +		} + +		err = regmap_update_bits(data->regmap, +			GP2AP020A00F_ALS_REG, +			GP2AP020A00F_RANGE_A_MASK, +			new_range); + +		if (err < 0) { +			dev_err(&data->client->dev, +				"Adjusting device lux mode failed.\n"); +			return false; +		} + +		err = regmap_update_bits(data->regmap, +			GP2AP020A00F_OP_REG, +			GP2AP020A00F_OP3_MASK, +			GP2AP020A00F_OP3_OPERATION); + +		if (err < 0) { +			dev_err(&data->client->dev, +				"Powering up the device failed.\n"); +			return false; +		} + +		/* Adjust als threshold register values to the new lux mode */ +		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) { +			err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TH, true); +			if (err < 0) { +				dev_err(&data->client->dev, +				"Adjusting als threshold value failed.\n"); +				return false; +			} +		} + +		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) { +			err =  gp2ap020a00f_write_event_threshold(data, +					GP2AP020A00F_THRESH_TL, true); +			if (err < 0) { +				dev_err(&data->client->dev, +				"Adjusting als threshold value failed.\n"); +				return false; +			} +		} + +		return true; +	} + +	return false; +} + +static void gp2ap020a00f_output_to_lux(struct gp2ap020a00f_data *data, +						int *output_val) +{ +	if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) +		*output_val *= 16; +} + +static void gp2ap020a00f_iio_trigger_work(struct irq_work *work) +{ +	struct gp2ap020a00f_data *data = +		container_of(work, struct gp2ap020a00f_data, work); + +	iio_trigger_poll(data->trig, 0); +} + +static irqreturn_t gp2ap020a00f_prox_sensing_handler(int irq, void *data) +{ +	struct iio_dev *indio_dev = data; +	struct gp2ap020a00f_data *priv = iio_priv(indio_dev); +	unsigned int op_reg_val; +	int ret; + +	/* Read interrupt flags */ +	ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); +	if (ret < 0) +		return IRQ_HANDLED; + +	if (gp2ap020a00f_prox_detect_enabled(priv)) { +		if (op_reg_val & GP2AP020A00F_PROX_DETECT) { +			iio_push_event(indio_dev, +			       IIO_UNMOD_EVENT_CODE( +				    IIO_PROXIMITY, +				    GP2AP020A00F_SCAN_MODE_PROXIMITY, +				    IIO_EV_TYPE_ROC, +				    IIO_EV_DIR_RISING), +			       iio_get_time_ns()); +		} else { +			iio_push_event(indio_dev, +			       IIO_UNMOD_EVENT_CODE( +				    IIO_PROXIMITY, +				    GP2AP020A00F_SCAN_MODE_PROXIMITY, +				    IIO_EV_TYPE_ROC, +				    IIO_EV_DIR_FALLING), +			       iio_get_time_ns()); +		} +	} + +	return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_thresh_event_handler(int irq, void *data) +{ +	struct iio_dev *indio_dev = data; +	struct gp2ap020a00f_data *priv = iio_priv(indio_dev); +	u8 op_reg_flags, d0_reg_buf[2]; +	unsigned int output_val, op_reg_val; +	int thresh_val_id, ret; + +	/* Read interrupt flags */ +	ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, +							&op_reg_val); +	if (ret < 0) +		goto done; + +	op_reg_flags = op_reg_val & (GP2AP020A00F_FLAG_A | GP2AP020A00F_FLAG_P +					| GP2AP020A00F_PROX_DETECT); + +	op_reg_val &= (~GP2AP020A00F_FLAG_A & ~GP2AP020A00F_FLAG_P +					& ~GP2AP020A00F_PROX_DETECT); + +	/* Clear interrupt flags (if not in INTTYPE_PULSE mode) */ +	if (priv->cur_opmode != GP2AP020A00F_OPMODE_PROX_DETECT) { +		ret = regmap_write(priv->regmap, GP2AP020A00F_OP_REG, +								op_reg_val); +		if (ret < 0) +			goto done; +	} + +	if (op_reg_flags & GP2AP020A00F_FLAG_A) { +		/* Check D0 register to assess if the lux mode +		 * transition is required. +		 */ +		ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_D0_L_REG, +							d0_reg_buf, 2); +		if (ret < 0) +			goto done; + +		output_val = le16_to_cpup((__le16 *)d0_reg_buf); + +		if (gp2ap020a00f_adjust_lux_mode(priv, output_val)) +			goto done; + +		gp2ap020a00f_output_to_lux(priv, &output_val); + +		/* +		 * We need to check output value to distinguish +		 * between high and low ambient light threshold event. +		 */ +		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &priv->flags)) { +			thresh_val_id = +			    GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TH_L_REG); +			if (output_val > priv->thresh_val[thresh_val_id]) +				iio_push_event(indio_dev, +				       IIO_MOD_EVENT_CODE( +					    IIO_LIGHT, +					    GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, +					    IIO_MOD_LIGHT_CLEAR, +					    IIO_EV_TYPE_THRESH, +					    IIO_EV_DIR_RISING), +				       iio_get_time_ns()); +		} + +		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &priv->flags)) { +			thresh_val_id = +			    GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TL_L_REG); +			if (output_val < priv->thresh_val[thresh_val_id]) +				iio_push_event(indio_dev, +				       IIO_MOD_EVENT_CODE( +					    IIO_LIGHT, +					    GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, +					    IIO_MOD_LIGHT_CLEAR, +					    IIO_EV_TYPE_THRESH, +					    IIO_EV_DIR_FALLING), +				       iio_get_time_ns()); +		} +	} + +	if (priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR || +	    priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR || +	    priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) { +		set_bit(GP2AP020A00F_FLAG_DATA_READY, &priv->flags); +		wake_up(&priv->data_ready_queue); +		goto done; +	} + +	if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &priv->flags) || +	    test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &priv->flags) || +	    test_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &priv->flags)) +		/* This fires off the trigger. */ +		irq_work_queue(&priv->work); + +done: +	return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data) +{ +	struct iio_poll_func *pf = data; +	struct iio_dev *indio_dev = pf->indio_dev; +	struct gp2ap020a00f_data *priv = iio_priv(indio_dev); +	size_t d_size = 0; +	__le32 light_lux; +	int i, out_val, ret; + +	for_each_set_bit(i, indio_dev->active_scan_mask, +		indio_dev->masklength) { +		ret = regmap_bulk_read(priv->regmap, +				GP2AP020A00F_DATA_REG(i), +				&priv->buffer[d_size], 2); +		if (ret < 0) +			goto done; + +		if (i == GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR || +		    i == GP2AP020A00F_SCAN_MODE_LIGHT_IR) { +			out_val = le16_to_cpup((__le16 *)&priv->buffer[d_size]); +			gp2ap020a00f_output_to_lux(priv, &out_val); +			light_lux = cpu_to_le32(out_val); +			memcpy(&priv->buffer[d_size], (u8 *)&light_lux, 4); +			d_size += 4; +		} else { +			d_size += 2; +		} +	} + +	iio_push_to_buffers_with_timestamp(indio_dev, priv->buffer, +		pf->timestamp); +done: +	iio_trigger_notify_done(indio_dev->trig); + +	return IRQ_HANDLED; +} + +static u8 gp2ap020a00f_get_thresh_reg(const struct iio_chan_spec *chan, +					     enum iio_event_direction event_dir) +{ +	switch (chan->type) { +	case IIO_PROXIMITY: +		if (event_dir == IIO_EV_DIR_RISING) +			return GP2AP020A00F_PH_L_REG; +		else +			return GP2AP020A00F_PL_L_REG; +	case IIO_LIGHT: +		if (event_dir == IIO_EV_DIR_RISING) +			return GP2AP020A00F_TH_L_REG; +		else +			return GP2AP020A00F_TL_L_REG; +	default: +		break; +	} + +	return -EINVAL; +} + +static int gp2ap020a00f_write_event_val(struct iio_dev *indio_dev, +					const struct iio_chan_spec *chan, +					enum iio_event_type type, +					enum iio_event_direction dir, +					enum iio_event_info info, +					int val, int val2) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	bool event_en = false; +	u8 thresh_val_id; +	u8 thresh_reg_l; +	int err = 0; + +	mutex_lock(&data->lock); + +	thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); +	thresh_val_id = GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l); + +	if (thresh_val_id > GP2AP020A00F_THRESH_PH) { +		err = -EINVAL; +		goto error_unlock; +	} + +	switch (thresh_reg_l) { +	case GP2AP020A00F_TH_L_REG: +		event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, +							&data->flags); +		break; +	case GP2AP020A00F_TL_L_REG: +		event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, +							&data->flags); +		break; +	case GP2AP020A00F_PH_L_REG: +		if (val == 0) { +			err = -EINVAL; +			goto error_unlock; +		} +		event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, +							&data->flags); +		break; +	case GP2AP020A00F_PL_L_REG: +		if (val == 0) { +			err = -EINVAL; +			goto error_unlock; +		} +		event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, +							&data->flags); +		break; +	} + +	data->thresh_val[thresh_val_id] = val; +	err =  gp2ap020a00f_write_event_threshold(data, thresh_val_id, +							event_en); +error_unlock: +	mutex_unlock(&data->lock); + +	return err; +} + +static int gp2ap020a00f_read_event_val(struct iio_dev *indio_dev, +				       const struct iio_chan_spec *chan, +				       enum iio_event_type type, +				       enum iio_event_direction dir, +				       enum iio_event_info info, +				       int *val, int *val2) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	u8 thresh_reg_l; +	int err = IIO_VAL_INT; + +	mutex_lock(&data->lock); + +	thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + +	if (thresh_reg_l > GP2AP020A00F_PH_L_REG) { +		err = -EINVAL; +		goto error_unlock; +	} + +	*val = data->thresh_val[GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l)]; + +error_unlock: +	mutex_unlock(&data->lock); + +	return err; +} + +static int gp2ap020a00f_write_prox_event_config(struct iio_dev *indio_dev, +						int state) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	enum gp2ap020a00f_cmd cmd_high_ev, cmd_low_ev; +	int err; + +	cmd_high_ev = state ? GP2AP020A00F_CMD_PROX_HIGH_EV_EN : +			      GP2AP020A00F_CMD_PROX_HIGH_EV_DIS; +	cmd_low_ev = state ? GP2AP020A00F_CMD_PROX_LOW_EV_EN : +			     GP2AP020A00F_CMD_PROX_LOW_EV_DIS; + +	/* +	 * In order to enable proximity detection feature in the device +	 * both high and low threshold registers have to be written +	 * with different values, greater than zero. +	 */ +	if (state) { +		if (data->thresh_val[GP2AP020A00F_THRESH_PL] == 0) +			return -EINVAL; + +		if (data->thresh_val[GP2AP020A00F_THRESH_PH] == 0) +			return -EINVAL; +	} + +	err = gp2ap020a00f_exec_cmd(data, cmd_high_ev); +	if (err < 0) +		return err; + +	err = gp2ap020a00f_exec_cmd(data, cmd_low_ev); +	if (err < 0) +		return err; + +	free_irq(data->client->irq, indio_dev); + +	if (state) +		err = request_threaded_irq(data->client->irq, NULL, +					   &gp2ap020a00f_prox_sensing_handler, +					   IRQF_TRIGGER_RISING | +					   IRQF_TRIGGER_FALLING | +					   IRQF_ONESHOT, +					   "gp2ap020a00f_prox_sensing", +					   indio_dev); +	else { +		err = request_threaded_irq(data->client->irq, NULL, +					   &gp2ap020a00f_thresh_event_handler, +					   IRQF_TRIGGER_FALLING | +					   IRQF_ONESHOT, +					   "gp2ap020a00f_thresh_event", +					   indio_dev); +	} + +	return err; +} + +static int gp2ap020a00f_write_event_config(struct iio_dev *indio_dev, +					   const struct iio_chan_spec *chan, +					   enum iio_event_type type, +					   enum iio_event_direction dir, +					   int state) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	enum gp2ap020a00f_cmd cmd; +	int err; + +	mutex_lock(&data->lock); + +	switch (chan->type) { +	case IIO_PROXIMITY: +		err = gp2ap020a00f_write_prox_event_config(indio_dev, state); +		break; +	case IIO_LIGHT: +		if (dir == IIO_EV_DIR_RISING) { +			cmd = state ? GP2AP020A00F_CMD_ALS_HIGH_EV_EN : +				      GP2AP020A00F_CMD_ALS_HIGH_EV_DIS; +			err = gp2ap020a00f_exec_cmd(data, cmd); +		} else { +			cmd = state ? GP2AP020A00F_CMD_ALS_LOW_EV_EN : +				      GP2AP020A00F_CMD_ALS_LOW_EV_DIS; +			err = gp2ap020a00f_exec_cmd(data, cmd); +		} +		break; +	default: +		err = -EINVAL; +	} + +	mutex_unlock(&data->lock); + +	return err; +} + +static int gp2ap020a00f_read_event_config(struct iio_dev *indio_dev, +					   const struct iio_chan_spec *chan, +					   enum iio_event_type type, +					   enum iio_event_direction dir) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	int event_en = 0; + +	mutex_lock(&data->lock); + +	switch (chan->type) { +	case IIO_PROXIMITY: +		if (dir == IIO_EV_DIR_RISING) +			event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, +								&data->flags); +		else +			event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, +								&data->flags); +		break; +	case IIO_LIGHT: +		if (dir == IIO_EV_DIR_RISING) +			event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, +								&data->flags); +		else +			event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, +								&data->flags); +		break; +	default: +		event_en = -EINVAL; +		break; +	} + +	mutex_unlock(&data->lock); + +	return event_en; +} + +static int gp2ap020a00f_read_channel(struct gp2ap020a00f_data *data, +				struct iio_chan_spec const *chan, int *val) +{ +	enum gp2ap020a00f_cmd cmd; +	int err; + +	switch (chan->scan_index) { +	case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: +		cmd = GP2AP020A00F_CMD_READ_RAW_CLEAR; +		break; +	case GP2AP020A00F_SCAN_MODE_LIGHT_IR: +		cmd = GP2AP020A00F_CMD_READ_RAW_IR; +		break; +	case GP2AP020A00F_SCAN_MODE_PROXIMITY: +		cmd = GP2AP020A00F_CMD_READ_RAW_PROXIMITY; +		break; +	default: +		return -EINVAL; +	} + +	err = gp2ap020a00f_exec_cmd(data, cmd); +	if (err < 0) { +		dev_err(&data->client->dev, +			"gp2ap020a00f_exec_cmd failed\n"); +		goto error_ret; +	} + +	err = gp2ap020a00f_read_output(data, chan->address, val); +	if (err < 0) +		dev_err(&data->client->dev, +			"gp2ap020a00f_read_output failed\n"); + +	err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_SHUTDOWN); +	if (err < 0) +		dev_err(&data->client->dev, +			"Failed to shut down the device.\n"); + +	if (cmd == GP2AP020A00F_CMD_READ_RAW_CLEAR || +	    cmd == GP2AP020A00F_CMD_READ_RAW_IR) +		gp2ap020a00f_output_to_lux(data, val); + +error_ret: +	return err; +} + +static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev, +			   struct iio_chan_spec const *chan, +			   int *val, int *val2, +			   long mask) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	int err = -EINVAL; + +	mutex_lock(&data->lock); + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		if (iio_buffer_enabled(indio_dev)) { +			err = -EBUSY; +			goto error_unlock; +		} + +		err = gp2ap020a00f_read_channel(data, chan, val); +		break; +	} + +error_unlock: +	mutex_unlock(&data->lock); + +	return err < 0 ? err : IIO_VAL_INT; +} + +static const struct iio_event_spec gp2ap020a00f_event_spec_light[] = { +	{ +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_RISING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, { +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_FALLING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, +}; + +static const struct iio_event_spec gp2ap020a00f_event_spec_prox[] = { +	{ +		.type = IIO_EV_TYPE_ROC, +		.dir = IIO_EV_DIR_RISING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, { +		.type = IIO_EV_TYPE_ROC, +		.dir = IIO_EV_DIR_FALLING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +			BIT(IIO_EV_INFO_ENABLE), +	}, +}; + +static const struct iio_chan_spec gp2ap020a00f_channels[] = { +	{ +		.type = IIO_LIGHT, +		.channel2 = IIO_MOD_LIGHT_CLEAR, +		.modified = 1, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.scan_type = { +			.sign = 'u', +			.realbits = 24, +			.shift = 0, +			.storagebits = 32, +			.endianness = IIO_LE, +		}, +		.scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, +		.address = GP2AP020A00F_D0_L_REG, +		.event_spec = gp2ap020a00f_event_spec_light, +		.num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_light), +	}, +	{ +		.type = IIO_LIGHT, +		.channel2 = IIO_MOD_LIGHT_IR, +		.modified = 1, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.scan_type = { +			.sign = 'u', +			.realbits = 24, +			.shift = 0, +			.storagebits = 32, +			.endianness = IIO_LE, +		}, +		.scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_IR, +		.address = GP2AP020A00F_D1_L_REG, +	}, +	{ +		.type = IIO_PROXIMITY, +		.modified = 0, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.scan_type = { +			.sign = 'u', +			.realbits = 16, +			.shift = 0, +			.storagebits = 16, +			.endianness = IIO_LE, +		}, +		.scan_index = GP2AP020A00F_SCAN_MODE_PROXIMITY, +		.address = GP2AP020A00F_D2_L_REG, +		.event_spec = gp2ap020a00f_event_spec_prox, +		.num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_prox), +	}, +	IIO_CHAN_SOFT_TIMESTAMP(GP2AP020A00F_CHAN_TIMESTAMP), +}; + +static const struct iio_info gp2ap020a00f_info = { +	.read_raw = &gp2ap020a00f_read_raw, +	.read_event_value = &gp2ap020a00f_read_event_val, +	.read_event_config = &gp2ap020a00f_read_event_config, +	.write_event_value = &gp2ap020a00f_write_event_val, +	.write_event_config = &gp2ap020a00f_write_event_config, +	.driver_module = THIS_MODULE, +}; + +static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	int i, err = 0; + +	mutex_lock(&data->lock); + +	/* +	 * Enable triggers according to the scan_mask. Enabling either +	 * LIGHT_CLEAR or LIGHT_IR scan mode results in enabling ALS +	 * module in the device, which generates samples in both D0 (clear) +	 * and D1 (ir) registers. As the two registers are bound to the +	 * two separate IIO channels they are treated in the driver logic +	 * as if they were controlled independently. +	 */ +	for_each_set_bit(i, indio_dev->active_scan_mask, +		indio_dev->masklength) { +		switch (i) { +		case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_CLEAR_EN); +			break; +		case GP2AP020A00F_SCAN_MODE_LIGHT_IR: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_IR_EN); +			break; +		case GP2AP020A00F_SCAN_MODE_PROXIMITY: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_PROX_EN); +			break; +		} +	} + +	if (err < 0) +		goto error_unlock; + +	data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); +	if (!data->buffer) { +		err = -ENOMEM; +		goto error_unlock; +	} + +	err = iio_triggered_buffer_postenable(indio_dev); + +error_unlock: +	mutex_unlock(&data->lock); + +	return err; +} + +static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev) +{ +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	int i, err; + +	mutex_lock(&data->lock); + +	err = iio_triggered_buffer_predisable(indio_dev); +	if (err < 0) +		goto error_unlock; + +	for_each_set_bit(i, indio_dev->active_scan_mask, +		indio_dev->masklength) { +		switch (i) { +		case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS); +			break; +		case GP2AP020A00F_SCAN_MODE_LIGHT_IR: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_IR_DIS); +			break; +		case GP2AP020A00F_SCAN_MODE_PROXIMITY: +			err = gp2ap020a00f_exec_cmd(data, +					GP2AP020A00F_CMD_TRIGGER_PROX_DIS); +			break; +		} +	} + +	if (err == 0) +		kfree(data->buffer); + +error_unlock: +	mutex_unlock(&data->lock); + +	return err; +} + +static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = { +	.postenable = &gp2ap020a00f_buffer_postenable, +	.predisable = &gp2ap020a00f_buffer_predisable, +}; + +static const struct iio_trigger_ops gp2ap020a00f_trigger_ops = { +	.owner = THIS_MODULE, +}; + +static int gp2ap020a00f_probe(struct i2c_client *client, +				const struct i2c_device_id *id) +{ +	struct gp2ap020a00f_data *data; +	struct iio_dev *indio_dev; +	struct regmap *regmap; +	int err; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (!indio_dev) +		return -ENOMEM; + +	data = iio_priv(indio_dev); + +	data->vled_reg = devm_regulator_get(&client->dev, "vled"); +	if (IS_ERR(data->vled_reg)) +		return PTR_ERR(data->vled_reg); + +	err = regulator_enable(data->vled_reg); +	if (err) +		return err; + +	regmap = devm_regmap_init_i2c(client, &gp2ap020a00f_regmap_config); +	if (IS_ERR(regmap)) { +		dev_err(&client->dev, "Regmap initialization failed.\n"); +		err = PTR_ERR(regmap); +		goto error_regulator_disable; +	} + +	/* Initialize device registers */ +	err = regmap_bulk_write(regmap, GP2AP020A00F_OP_REG, +			gp2ap020a00f_reg_init_tab, +			ARRAY_SIZE(gp2ap020a00f_reg_init_tab)); + +	if (err < 0) { +		dev_err(&client->dev, "Device initialization failed.\n"); +		goto error_regulator_disable; +	} + +	i2c_set_clientdata(client, indio_dev); + +	data->client = client; +	data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN; +	data->regmap = regmap; +	init_waitqueue_head(&data->data_ready_queue); + +	mutex_init(&data->lock); +	indio_dev->dev.parent = &client->dev; +	indio_dev->channels = gp2ap020a00f_channels; +	indio_dev->num_channels = ARRAY_SIZE(gp2ap020a00f_channels); +	indio_dev->info = &gp2ap020a00f_info; +	indio_dev->name = id->name; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	/* Allocate buffer */ +	err = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, +		&gp2ap020a00f_trigger_handler, &gp2ap020a00f_buffer_setup_ops); +	if (err < 0) +		goto error_regulator_disable; + +	/* Allocate trigger */ +	data->trig = devm_iio_trigger_alloc(&client->dev, "%s-trigger", +							indio_dev->name); +	if (data->trig == NULL) { +		err = -ENOMEM; +		dev_err(&indio_dev->dev, "Failed to allocate iio trigger.\n"); +		goto error_uninit_buffer; +	} + +	/* This needs to be requested here for read_raw calls to work. */ +	err = request_threaded_irq(client->irq, NULL, +				   &gp2ap020a00f_thresh_event_handler, +				   IRQF_TRIGGER_FALLING | +				   IRQF_ONESHOT, +				   "gp2ap020a00f_als_event", +				   indio_dev); +	if (err < 0) { +		dev_err(&client->dev, "Irq request failed.\n"); +		goto error_uninit_buffer; +	} + +	data->trig->ops = &gp2ap020a00f_trigger_ops; +	data->trig->dev.parent = &data->client->dev; + +	init_irq_work(&data->work, gp2ap020a00f_iio_trigger_work); + +	err = iio_trigger_register(data->trig); +	if (err < 0) { +		dev_err(&client->dev, "Failed to register iio trigger.\n"); +		goto error_free_irq; +	} + +	err = iio_device_register(indio_dev); +	if (err < 0) +		goto error_trigger_unregister; + +	return 0; + +error_trigger_unregister: +	iio_trigger_unregister(data->trig); +error_free_irq: +	free_irq(client->irq, indio_dev); +error_uninit_buffer: +	iio_triggered_buffer_cleanup(indio_dev); +error_regulator_disable: +	regulator_disable(data->vled_reg); + +	return err; +} + +static int gp2ap020a00f_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); +	struct gp2ap020a00f_data *data = iio_priv(indio_dev); +	int err; + +	err = gp2ap020a00f_set_operation_mode(data, +					GP2AP020A00F_OPMODE_SHUTDOWN); +	if (err < 0) +		dev_err(&indio_dev->dev, "Failed to power off the device.\n"); + +	iio_device_unregister(indio_dev); +	iio_trigger_unregister(data->trig); +	free_irq(client->irq, indio_dev); +	iio_triggered_buffer_cleanup(indio_dev); +	regulator_disable(data->vled_reg); + +	return 0; +} + +static const struct i2c_device_id gp2ap020a00f_id[] = { +	{ GP2A_I2C_NAME, 0 }, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, gp2ap020a00f_id); + +#ifdef CONFIG_OF +static const struct of_device_id gp2ap020a00f_of_match[] = { +	{ .compatible = "sharp,gp2ap020a00f" }, +	{ } +}; +#endif + +static struct i2c_driver gp2ap020a00f_driver = { +	.driver = { +		.name	= GP2A_I2C_NAME, +		.of_match_table = of_match_ptr(gp2ap020a00f_of_match), +		.owner	= THIS_MODULE, +	}, +	.probe		= gp2ap020a00f_probe, +	.remove		= gp2ap020a00f_remove, +	.id_table	= gp2ap020a00f_id, +}; + +module_i2c_driver(gp2ap020a00f_driver); + +MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); +MODULE_DESCRIPTION("Sharp GP2AP020A00F Proximity/ALS sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index e59d00c3139..96e71e103ea 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -22,6 +22,7 @@  #include <linux/interrupt.h>  #include <linux/irq.h>  #include <linux/slab.h> +#include <linux/delay.h>  #include <linux/hid-sensor-hub.h>  #include <linux/iio/iio.h>  #include <linux/iio/sysfs.h> @@ -37,6 +38,10 @@ struct als_state {  	struct hid_sensor_common common_attributes;  	struct hid_sensor_hub_attribute_info als_illum;  	u32 illum; +	int scale_pre_decml; +	int scale_post_decml; +	int scale_precision; +	int value_offset;  };  /* Channel definitions */ @@ -45,6 +50,7 @@ static const struct iio_chan_spec als_channels[] = {  		.type = IIO_INTENSITY,  		.modified = 1,  		.channel2 = IIO_MOD_LIGHT_BOTH, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),  		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |  		BIT(IIO_CHAN_INFO_SCALE) |  		BIT(IIO_CHAN_INFO_SAMP_FREQ) | @@ -73,8 +79,8 @@ static int als_read_raw(struct iio_dev *indio_dev,  	struct als_state *als_state = iio_priv(indio_dev);  	int report_id = -1;  	u32 address; -	int ret;  	int ret_type; +	s32 poll_value;  	*val = 0;  	*val2 = 0; @@ -90,35 +96,44 @@ static int als_read_raw(struct iio_dev *indio_dev,  			report_id = -1;  			break;  		} -		if (report_id >= 0) +		if (report_id >= 0) { +			poll_value = hid_sensor_read_poll_value( +						&als_state->common_attributes); +			if (poll_value < 0) +				return -EINVAL; + +			hid_sensor_power_state(&als_state->common_attributes, +						true); +			msleep_interruptible(poll_value * 2); +  			*val = sensor_hub_input_attr_get_raw_value( -				als_state->common_attributes.hsdev, -				HID_USAGE_SENSOR_ALS, address, -				report_id); -		else { +					als_state->common_attributes.hsdev, +					HID_USAGE_SENSOR_ALS, address, +					report_id); +			hid_sensor_power_state(&als_state->common_attributes, +						false); +		} else {  			*val = 0;  			return -EINVAL;  		}  		ret_type = IIO_VAL_INT;  		break;  	case IIO_CHAN_INFO_SCALE: -		*val = als_state->als_illum.units; -		ret_type = IIO_VAL_INT; +		*val = als_state->scale_pre_decml; +		*val2 = als_state->scale_post_decml; +		ret_type = als_state->scale_precision;  		break;  	case IIO_CHAN_INFO_OFFSET: -		*val = hid_sensor_convert_exponent( -				als_state->als_illum.unit_expo); +		*val = als_state->value_offset;  		ret_type = IIO_VAL_INT;  		break;  	case IIO_CHAN_INFO_SAMP_FREQ: -		ret = hid_sensor_read_samp_freq_value( +		ret_type = hid_sensor_read_samp_freq_value(  				&als_state->common_attributes, val, val2); -		ret_type = IIO_VAL_INT_PLUS_MICRO;  		break;  	case IIO_CHAN_INFO_HYSTERESIS: -		ret = hid_sensor_read_raw_hyst_value( +		ret_type = hid_sensor_read_raw_hyst_value(  				&als_state->common_attributes, val, val2); -		ret_type = IIO_VAL_INT_PLUS_MICRO;  		break;  	default:  		ret_type = -EINVAL; @@ -161,10 +176,11 @@ static const struct iio_info als_info = {  };  /* Function to push data to buffer */ -static void hid_sensor_push_data(struct iio_dev *indio_dev, u8 *data, int len) +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, +					int len)  {  	dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); -	iio_push_to_buffers(indio_dev, (u8 *)data); +	iio_push_to_buffers(indio_dev, data);  }  /* Callback handler to send event after all samples are received and captured */ @@ -175,11 +191,10 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev,  	struct iio_dev *indio_dev = platform_get_drvdata(priv);  	struct als_state *als_state = iio_priv(indio_dev); -	dev_dbg(&indio_dev->dev, "als_proc_event [%d]\n", -				als_state->common_attributes.data_ready); -	if (als_state->common_attributes.data_ready) +	dev_dbg(&indio_dev->dev, "als_proc_event\n"); +	if (atomic_read(&als_state->common_attributes.data_ready))  		hid_sensor_push_data(indio_dev, -				(u8 *)&als_state->illum, +				&als_state->illum,  				sizeof(als_state->illum));  	return 0; @@ -228,6 +243,22 @@ static int als_parse_report(struct platform_device *pdev,  	dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index,  			st->als_illum.report_id); +	st->scale_precision = hid_sensor_format_scale( +				HID_USAGE_SENSOR_ALS, +				&st->als_illum, +				&st->scale_pre_decml, &st->scale_post_decml); + +	/* Set Sensitivity field ids, when there is no individual modifier */ +	if (st->common_attributes.sensitivity.index < 0) { +		sensor_hub_input_get_attribute_info(hsdev, +			HID_FEATURE_REPORT, usage_id, +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | +			HID_USAGE_SENSOR_DATA_LIGHT, +			&st->common_attributes.sensitivity); +		dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", +			st->common_attributes.sensitivity.index, +			st->common_attributes.sensitivity.report_id); +	}  	return ret;  } @@ -284,7 +315,7 @@ static int hid_als_probe(struct platform_device *pdev)  		dev_err(&pdev->dev, "failed to initialize trigger buffer\n");  		goto error_free_dev_mem;  	} -	als_state->common_attributes.data_ready = false; +	atomic_set(&als_state->common_attributes.data_ready, 0);  	ret = hid_sensor_setup_trigger(indio_dev, name,  				&als_state->common_attributes);  	if (ret < 0) { @@ -313,7 +344,7 @@ static int hid_als_probe(struct platform_device *pdev)  error_iio_unreg:  	iio_device_unregister(indio_dev);  error_remove_trigger: -	hid_sensor_remove_trigger(indio_dev); +	hid_sensor_remove_trigger(&als_state->common_attributes);  error_unreg_buffer_funcs:  	iio_triggered_buffer_cleanup(indio_dev);  error_free_dev_mem: @@ -326,10 +357,11 @@ static int hid_als_remove(struct platform_device *pdev)  {  	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;  	struct iio_dev *indio_dev = platform_get_drvdata(pdev); +	struct als_state *als_state = iio_priv(indio_dev);  	sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);  	iio_device_unregister(indio_dev); -	hid_sensor_remove_trigger(indio_dev); +	hid_sensor_remove_trigger(&als_state->common_attributes);  	iio_triggered_buffer_cleanup(indio_dev);  	kfree(indio_dev->channels); diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c new file mode 100644 index 00000000000..412bae86d6a --- /dev/null +++ b/drivers/iio/light/hid-sensor-prox.c @@ -0,0 +1,385 @@ +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +#define CHANNEL_SCAN_INDEX_PRESENCE 0 + +struct prox_state { +	struct hid_sensor_hub_callbacks callbacks; +	struct hid_sensor_common common_attributes; +	struct hid_sensor_hub_attribute_info prox_attr; +	u32 human_presence; +}; + +/* Channel definitions */ +static const struct iio_chan_spec prox_channels[] = { +	{ +		.type = IIO_PROXIMITY, +		.modified = 1, +		.channel2 = IIO_NO_MOD, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | +		BIT(IIO_CHAN_INFO_SCALE) | +		BIT(IIO_CHAN_INFO_SAMP_FREQ) | +		BIT(IIO_CHAN_INFO_HYSTERESIS), +		.scan_index = CHANNEL_SCAN_INDEX_PRESENCE, +	} +}; + +/* Adjust channel real bits based on report descriptor */ +static void prox_adjust_channel_bit_mask(struct iio_chan_spec *channels, +					int channel, int size) +{ +	channels[channel].scan_type.sign = 's'; +	/* Real storage bits will change based on the report desc. */ +	channels[channel].scan_type.realbits = size * 8; +	/* Maximum size of a sample to capture is u32 */ +	channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int prox_read_raw(struct iio_dev *indio_dev, +			      struct iio_chan_spec const *chan, +			      int *val, int *val2, +			      long mask) +{ +	struct prox_state *prox_state = iio_priv(indio_dev); +	int report_id = -1; +	u32 address; +	int ret_type; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		switch (chan->scan_index) { +		case  CHANNEL_SCAN_INDEX_PRESENCE: +			report_id = prox_state->prox_attr.report_id; +			address = +			HID_USAGE_SENSOR_HUMAN_PRESENCE; +			break; +		default: +			report_id = -1; +			break; +		} +		if (report_id >= 0) { +			poll_value = hid_sensor_read_poll_value( +					&prox_state->common_attributes); +			if (poll_value < 0) +				return -EINVAL; + +			hid_sensor_power_state(&prox_state->common_attributes, +						true); + +			msleep_interruptible(poll_value * 2); + +			*val = sensor_hub_input_attr_get_raw_value( +				prox_state->common_attributes.hsdev, +				HID_USAGE_SENSOR_PROX, address, +				report_id); +			hid_sensor_power_state(&prox_state->common_attributes, +						false); +		} else { +			*val = 0; +			return -EINVAL; +		} +		ret_type = IIO_VAL_INT; +		break; +	case IIO_CHAN_INFO_SCALE: +		*val = prox_state->prox_attr.units; +		ret_type = IIO_VAL_INT; +		break; +	case IIO_CHAN_INFO_OFFSET: +		*val = hid_sensor_convert_exponent( +				prox_state->prox_attr.unit_expo); +		ret_type = IIO_VAL_INT; +		break; +	case IIO_CHAN_INFO_SAMP_FREQ: +		ret_type = hid_sensor_read_samp_freq_value( +				&prox_state->common_attributes, val, val2); +		break; +	case IIO_CHAN_INFO_HYSTERESIS: +		ret_type = hid_sensor_read_raw_hyst_value( +				&prox_state->common_attributes, val, val2); +		break; +	default: +		ret_type = -EINVAL; +		break; +	} + +	return ret_type; +} + +/* Channel write_raw handler */ +static int prox_write_raw(struct iio_dev *indio_dev, +			       struct iio_chan_spec const *chan, +			       int val, +			       int val2, +			       long mask) +{ +	struct prox_state *prox_state = iio_priv(indio_dev); +	int ret = 0; + +	switch (mask) { +	case IIO_CHAN_INFO_SAMP_FREQ: +		ret = hid_sensor_write_samp_freq_value( +				&prox_state->common_attributes, val, val2); +		break; +	case IIO_CHAN_INFO_HYSTERESIS: +		ret = hid_sensor_write_raw_hyst_value( +				&prox_state->common_attributes, val, val2); +		break; +	default: +		ret = -EINVAL; +	} + +	return ret; +} + +static const struct iio_info prox_info = { +	.driver_module = THIS_MODULE, +	.read_raw = &prox_read_raw, +	.write_raw = &prox_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, +					int len) +{ +	dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); +	iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int prox_proc_event(struct hid_sensor_hub_device *hsdev, +				unsigned usage_id, +				void *priv) +{ +	struct iio_dev *indio_dev = platform_get_drvdata(priv); +	struct prox_state *prox_state = iio_priv(indio_dev); + +	dev_dbg(&indio_dev->dev, "prox_proc_event\n"); +	if (atomic_read(&prox_state->common_attributes.data_ready)) +		hid_sensor_push_data(indio_dev, +				&prox_state->human_presence, +				sizeof(prox_state->human_presence)); + +	return 0; +} + +/* Capture samples in local storage */ +static int prox_capture_sample(struct hid_sensor_hub_device *hsdev, +				unsigned usage_id, +				size_t raw_len, char *raw_data, +				void *priv) +{ +	struct iio_dev *indio_dev = platform_get_drvdata(priv); +	struct prox_state *prox_state = iio_priv(indio_dev); +	int ret = -EINVAL; + +	switch (usage_id) { +	case HID_USAGE_SENSOR_HUMAN_PRESENCE: +		prox_state->human_presence = *(u32 *)raw_data; +		ret = 0; +		break; +	default: +		break; +	} + +	return ret; +} + +/* Parse report which is specific to an usage id*/ +static int prox_parse_report(struct platform_device *pdev, +				struct hid_sensor_hub_device *hsdev, +				struct iio_chan_spec *channels, +				unsigned usage_id, +				struct prox_state *st) +{ +	int ret; + +	ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, +			usage_id, +			HID_USAGE_SENSOR_HUMAN_PRESENCE, +			&st->prox_attr); +	if (ret < 0) +		return ret; +	prox_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESENCE, +					st->prox_attr.size); + +	dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr.index, +			st->prox_attr.report_id); + +	/* Set Sensitivity field ids, when there is no individual modifier */ +	if (st->common_attributes.sensitivity.index < 0) { +		sensor_hub_input_get_attribute_info(hsdev, +			HID_FEATURE_REPORT, usage_id, +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | +			HID_USAGE_SENSOR_DATA_PRESENCE, +			&st->common_attributes.sensitivity); +		dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", +			st->common_attributes.sensitivity.index, +			st->common_attributes.sensitivity.report_id); +	} +	return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_prox_probe(struct platform_device *pdev) +{ +	int ret = 0; +	static const char *name = "prox"; +	struct iio_dev *indio_dev; +	struct prox_state *prox_state; +	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; +	struct iio_chan_spec *channels; + +	indio_dev = devm_iio_device_alloc(&pdev->dev, +				sizeof(struct prox_state)); +	if (!indio_dev) +		return -ENOMEM; +	platform_set_drvdata(pdev, indio_dev); + +	prox_state = iio_priv(indio_dev); +	prox_state->common_attributes.hsdev = hsdev; +	prox_state->common_attributes.pdev = pdev; + +	ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX, +					&prox_state->common_attributes); +	if (ret) { +		dev_err(&pdev->dev, "failed to setup common attributes\n"); +		return ret; +	} + +	channels = kmemdup(prox_channels, sizeof(prox_channels), GFP_KERNEL); +	if (!channels) { +		dev_err(&pdev->dev, "failed to duplicate channels\n"); +		return -ENOMEM; +	} + +	ret = prox_parse_report(pdev, hsdev, channels, +				HID_USAGE_SENSOR_PROX, prox_state); +	if (ret) { +		dev_err(&pdev->dev, "failed to setup attributes\n"); +		goto error_free_dev_mem; +	} + +	indio_dev->channels = channels; +	indio_dev->num_channels = +				ARRAY_SIZE(prox_channels); +	indio_dev->dev.parent = &pdev->dev; +	indio_dev->info = &prox_info; +	indio_dev->name = name; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, +		NULL, NULL); +	if (ret) { +		dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); +		goto error_free_dev_mem; +	} +	atomic_set(&prox_state->common_attributes.data_ready, 0); +	ret = hid_sensor_setup_trigger(indio_dev, name, +				&prox_state->common_attributes); +	if (ret) { +		dev_err(&pdev->dev, "trigger setup failed\n"); +		goto error_unreg_buffer_funcs; +	} + +	ret = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&pdev->dev, "device register failed\n"); +		goto error_remove_trigger; +	} + +	prox_state->callbacks.send_event = prox_proc_event; +	prox_state->callbacks.capture_sample = prox_capture_sample; +	prox_state->callbacks.pdev = pdev; +	ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PROX, +					&prox_state->callbacks); +	if (ret < 0) { +		dev_err(&pdev->dev, "callback reg failed\n"); +		goto error_iio_unreg; +	} + +	return ret; + +error_iio_unreg: +	iio_device_unregister(indio_dev); +error_remove_trigger: +	hid_sensor_remove_trigger(&prox_state->common_attributes); +error_unreg_buffer_funcs: +	iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: +	kfree(indio_dev->channels); +	return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_prox_remove(struct platform_device *pdev) +{ +	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; +	struct iio_dev *indio_dev = platform_get_drvdata(pdev); +	struct prox_state *prox_state = iio_priv(indio_dev); + +	sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX); +	iio_device_unregister(indio_dev); +	hid_sensor_remove_trigger(&prox_state->common_attributes); +	iio_triggered_buffer_cleanup(indio_dev); +	kfree(indio_dev->channels); + +	return 0; +} + +static struct platform_device_id hid_prox_ids[] = { +	{ +		/* Format: HID-SENSOR-usage_id_in_hex_lowercase */ +		.name = "HID-SENSOR-200011", +	}, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_prox_ids); + +static struct platform_driver hid_prox_platform_driver = { +	.id_table = hid_prox_ids, +	.driver = { +		.name	= KBUILD_MODNAME, +		.owner	= THIS_MODULE, +	}, +	.probe		= hid_prox_probe, +	.remove		= hid_prox_remove, +}; +module_platform_driver(hid_prox_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Proximity"); +MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c new file mode 100644 index 00000000000..62b7072af4d --- /dev/null +++ b/drivers/iio/light/ltr501.c @@ -0,0 +1,445 @@ +/* + * ltr501.c - Support for Lite-On LTR501 ambient light and proximity sensor + * + * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + * + * 7-bit I2C slave address 0x23 + * + * TODO: interrupt, threshold, measurement rate, IR LED characteristics + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define LTR501_DRV_NAME "ltr501" + +#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */ +#define LTR501_PS_CONTR 0x81 /* PS operation mode */ +#define LTR501_PART_ID 0x86 +#define LTR501_MANUFAC_ID 0x87 +#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */ +#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ +#define LTR501_ALS_PS_STATUS 0x8c +#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ + +#define LTR501_ALS_CONTR_SW_RESET BIT(2) +#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2)) +#define LTR501_CONTR_PS_GAIN_SHIFT 2 +#define LTR501_CONTR_ALS_GAIN_MASK BIT(3) +#define LTR501_CONTR_ACTIVE BIT(1) + +#define LTR501_STATUS_ALS_RDY BIT(2) +#define LTR501_STATUS_PS_RDY BIT(0) + +#define LTR501_PS_DATA_MASK 0x7ff + +struct ltr501_data { +	struct i2c_client *client; +	struct mutex lock_als, lock_ps; +	u8 als_contr, ps_contr; +}; + +static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask) +{ +	int tries = 100; +	int ret; + +	while (tries--) { +		ret = i2c_smbus_read_byte_data(data->client, +			LTR501_ALS_PS_STATUS); +		if (ret < 0) +			return ret; +		if ((ret & drdy_mask) == drdy_mask) +			return 0; +		msleep(25); +	} + +	dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n"); +	return -EIO; +} + +static int ltr501_read_als(struct ltr501_data *data, __le16 buf[2]) +{ +	int ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); +	if (ret < 0) +		return ret; +	/* always read both ALS channels in given order */ +	return i2c_smbus_read_i2c_block_data(data->client, +		LTR501_ALS_DATA1, 2 * sizeof(__le16), (u8 *) buf); +} + +static int ltr501_read_ps(struct ltr501_data *data) +{ +	int ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); +	if (ret < 0) +		return ret; +	return i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); +} + +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \ +	.type = IIO_INTENSITY, \ +	.modified = 1, \ +	.address = (_addr), \ +	.channel2 = (_mod), \ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +	.info_mask_shared_by_type = (_shared), \ +	.scan_index = (_idx), \ +	.scan_type = { \ +		.sign = 'u', \ +		.realbits = 16, \ +		.storagebits = 16, \ +		.endianness = IIO_CPU, \ +	} \ +} + +static const struct iio_chan_spec ltr501_channels[] = { +	LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0), +	LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, +		BIT(IIO_CHAN_INFO_SCALE)), +	{ +		.type = IIO_PROXIMITY, +		.address = LTR501_PS_DATA, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | +			BIT(IIO_CHAN_INFO_SCALE), +		.scan_index = 2, +		.scan_type = { +			.sign = 'u', +			.realbits = 11, +			.storagebits = 16, +			.endianness = IIO_CPU, +		}, +	}, +	IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const int ltr501_ps_gain[4][2] = { +	{1, 0}, {0, 250000}, {0, 125000}, {0, 62500} +}; + +static int ltr501_read_raw(struct iio_dev *indio_dev, +				struct iio_chan_spec const *chan, +				int *val, int *val2, long mask) +{ +	struct ltr501_data *data = iio_priv(indio_dev); +	__le16 buf[2]; +	int ret, i; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		if (iio_buffer_enabled(indio_dev)) +			return -EBUSY; + +		switch (chan->type) { +		case IIO_INTENSITY: +			mutex_lock(&data->lock_als); +			ret = ltr501_read_als(data, buf); +			mutex_unlock(&data->lock_als); +			if (ret < 0) +				return ret; +			*val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ? +				buf[0] : buf[1]); +			return IIO_VAL_INT; +		case IIO_PROXIMITY: +			mutex_lock(&data->lock_ps); +			ret = ltr501_read_ps(data); +			mutex_unlock(&data->lock_ps); +			if (ret < 0) +				return ret; +			*val = ret & LTR501_PS_DATA_MASK; +			return IIO_VAL_INT; +		default: +			return -EINVAL; +		} +	case IIO_CHAN_INFO_SCALE: +		switch (chan->type) { +		case IIO_INTENSITY: +			if (data->als_contr & LTR501_CONTR_ALS_GAIN_MASK) { +				*val = 0; +				*val2 = 5000; +				return IIO_VAL_INT_PLUS_MICRO; +			} else { +				*val = 1; +				*val2 = 0; +				return IIO_VAL_INT; +			} +		case IIO_PROXIMITY: +			i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >> +				LTR501_CONTR_PS_GAIN_SHIFT; +			*val = ltr501_ps_gain[i][0]; +			*val2 = ltr501_ps_gain[i][1]; +			return IIO_VAL_INT_PLUS_MICRO; +		default: +			return -EINVAL; +		} +	} +	return -EINVAL; +} + +static int ltr501_get_ps_gain_index(int val, int val2) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(ltr501_ps_gain); i++) +		if (val == ltr501_ps_gain[i][0] && val2 == ltr501_ps_gain[i][1]) +			return i; + +	return -1; +} + +static int ltr501_write_raw(struct iio_dev *indio_dev, +			       struct iio_chan_spec const *chan, +			       int val, int val2, long mask) +{ +	struct ltr501_data *data = iio_priv(indio_dev); +	int i; + +	if (iio_buffer_enabled(indio_dev)) +		return -EBUSY; + +	switch (mask) { +	case IIO_CHAN_INFO_SCALE: +		switch (chan->type) { +		case IIO_INTENSITY: +			if (val == 0 && val2 == 5000) +				data->als_contr |= LTR501_CONTR_ALS_GAIN_MASK; +			else if (val == 1 && val2 == 0) +				data->als_contr &= ~LTR501_CONTR_ALS_GAIN_MASK; +			else +				return -EINVAL; +			return i2c_smbus_write_byte_data(data->client, +				LTR501_ALS_CONTR, data->als_contr); +		case IIO_PROXIMITY: +			i = ltr501_get_ps_gain_index(val, val2); +			if (i < 0) +				return -EINVAL; +			data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK; +			data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT; +			return i2c_smbus_write_byte_data(data->client, +				LTR501_PS_CONTR, data->ps_contr); +		default: +			return -EINVAL; +		} +	} +	return -EINVAL; +} + +static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625"); +static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005"); + +static struct attribute *ltr501_attributes[] = { +	&iio_const_attr_in_proximity_scale_available.dev_attr.attr, +	&iio_const_attr_in_intensity_scale_available.dev_attr.attr, +	NULL +}; + +static const struct attribute_group ltr501_attribute_group = { +	.attrs = ltr501_attributes, +}; + +static const struct iio_info ltr501_info = { +	.read_raw = ltr501_read_raw, +	.write_raw = ltr501_write_raw, +	.attrs = <r501_attribute_group, +	.driver_module = THIS_MODULE, +}; + +static int ltr501_write_contr(struct i2c_client *client, u8 als_val, u8 ps_val) +{ +	int ret = i2c_smbus_write_byte_data(client, LTR501_ALS_CONTR, als_val); +	if (ret < 0) +		return ret; + +	return i2c_smbus_write_byte_data(client, LTR501_PS_CONTR, ps_val); +} + +static irqreturn_t ltr501_trigger_handler(int irq, void *p) +{ +	struct iio_poll_func *pf = p; +	struct iio_dev *indio_dev = pf->indio_dev; +	struct ltr501_data *data = iio_priv(indio_dev); +	u16 buf[8]; +	__le16 als_buf[2]; +	u8 mask = 0; +	int j = 0; +	int ret; + +	memset(buf, 0, sizeof(buf)); + +	/* figure out which data needs to be ready */ +	if (test_bit(0, indio_dev->active_scan_mask) || +		test_bit(1, indio_dev->active_scan_mask)) +		mask |= LTR501_STATUS_ALS_RDY; +	if (test_bit(2, indio_dev->active_scan_mask)) +		mask |= LTR501_STATUS_PS_RDY; + +	ret = ltr501_drdy(data, mask); +	if (ret < 0) +		goto done; + +	if (mask & LTR501_STATUS_ALS_RDY) { +		ret = i2c_smbus_read_i2c_block_data(data->client, +			LTR501_ALS_DATA1, sizeof(als_buf), (u8 *) als_buf); +		if (ret < 0) +			return ret; +		if (test_bit(0, indio_dev->active_scan_mask)) +			buf[j++] = le16_to_cpu(als_buf[1]); +		if (test_bit(1, indio_dev->active_scan_mask)) +			buf[j++] = le16_to_cpu(als_buf[0]); +	} + +	if (mask & LTR501_STATUS_PS_RDY) { +		ret = i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); +		if (ret < 0) +			goto done; +		buf[j++] = ret & LTR501_PS_DATA_MASK; +	} + +	iio_push_to_buffers_with_timestamp(indio_dev, buf, +		iio_get_time_ns()); + +done: +	iio_trigger_notify_done(indio_dev->trig); + +	return IRQ_HANDLED; +} + +static int ltr501_init(struct ltr501_data *data) +{ +	int ret; + +	ret = i2c_smbus_read_byte_data(data->client, LTR501_ALS_CONTR); +	if (ret < 0) +		return ret; +	data->als_contr = ret | LTR501_CONTR_ACTIVE; + +	ret = i2c_smbus_read_byte_data(data->client, LTR501_PS_CONTR); +	if (ret < 0) +		return ret; +	data->ps_contr = ret | LTR501_CONTR_ACTIVE; + +	return ltr501_write_contr(data->client, data->als_contr, +		data->ps_contr); +} + +static int ltr501_probe(struct i2c_client *client, +			  const struct i2c_device_id *id) +{ +	struct ltr501_data *data; +	struct iio_dev *indio_dev; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (!indio_dev) +		return -ENOMEM; + +	data = iio_priv(indio_dev); +	i2c_set_clientdata(client, indio_dev); +	data->client = client; +	mutex_init(&data->lock_als); +	mutex_init(&data->lock_ps); + +	ret = i2c_smbus_read_byte_data(data->client, LTR501_PART_ID); +	if (ret < 0) +		return ret; +	if ((ret >> 4) != 0x8) +		return -ENODEV; + +	indio_dev->dev.parent = &client->dev; +	indio_dev->info = <r501_info; +	indio_dev->channels = ltr501_channels; +	indio_dev->num_channels = ARRAY_SIZE(ltr501_channels); +	indio_dev->name = LTR501_DRV_NAME; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = ltr501_init(data); +	if (ret < 0) +		return ret; + +	ret = iio_triggered_buffer_setup(indio_dev, NULL, +		ltr501_trigger_handler, NULL); +	if (ret) +		return ret; + +	ret = iio_device_register(indio_dev); +	if (ret) +		goto error_unreg_buffer; + +	return 0; + +error_unreg_buffer: +	iio_triggered_buffer_cleanup(indio_dev); +	return ret; +} + +static int ltr501_powerdown(struct ltr501_data *data) +{ +	return ltr501_write_contr(data->client, +		data->als_contr & ~LTR501_CONTR_ACTIVE, +		data->ps_contr & ~LTR501_CONTR_ACTIVE); +} + +static int ltr501_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); + +	iio_device_unregister(indio_dev); +	iio_triggered_buffer_cleanup(indio_dev); +	ltr501_powerdown(iio_priv(indio_dev)); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ltr501_suspend(struct device *dev) +{ +	struct ltr501_data *data = iio_priv(i2c_get_clientdata( +		to_i2c_client(dev))); +	return ltr501_powerdown(data); +} + +static int ltr501_resume(struct device *dev) +{ +	struct ltr501_data *data = iio_priv(i2c_get_clientdata( +		to_i2c_client(dev))); + +	return ltr501_write_contr(data->client, data->als_contr, +		data->ps_contr); +} +#endif + +static SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume); + +static const struct i2c_device_id ltr501_id[] = { +	{ "ltr501", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, ltr501_id); + +static struct i2c_driver ltr501_driver = { +	.driver = { +		.name   = LTR501_DRV_NAME, +		.pm	= <r501_pm_ops, +		.owner  = THIS_MODULE, +	}, +	.probe  = ltr501_probe, +	.remove	= ltr501_remove, +	.id_table = ltr501_id, +}; + +module_i2c_driver(ltr501_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c new file mode 100644 index 00000000000..752569985d1 --- /dev/null +++ b/drivers/iio/light/tcs3472.c @@ -0,0 +1,379 @@ +/* + * tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + * + * Color light sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725, + * TCS34727) + * + * TODO: interrupt support, thresholds, wait time + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define TCS3472_DRV_NAME "tcs3472" + +#define TCS3472_COMMAND BIT(7) +#define TCS3472_AUTO_INCR BIT(5) + +#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00) +#define TCS3472_ATIME (TCS3472_COMMAND | 0x01) +#define TCS3472_WTIME (TCS3472_COMMAND | 0x03) +#define TCS3472_AILT (TCS3472_COMMAND | 0x04) +#define TCS3472_AIHT (TCS3472_COMMAND | 0x06) +#define TCS3472_PERS (TCS3472_COMMAND | 0x0c) +#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d) +#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f) +#define TCS3472_ID (TCS3472_COMMAND | 0x12) +#define TCS3472_STATUS (TCS3472_COMMAND | 0x13) +#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14) +#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16) +#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18) +#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a) + +#define TCS3472_STATUS_AVALID BIT(0) +#define TCS3472_ENABLE_AEN BIT(1) +#define TCS3472_ENABLE_PON BIT(0) +#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) + +struct tcs3472_data { +	struct i2c_client *client; +	struct mutex lock; +	u8 enable; +	u8 control; +	u8 atime; +	u16 buffer[8]; /* 4 16-bit channels + 64-bit timestamp */ +}; + +#define TCS3472_CHANNEL(_color, _si, _addr) { \ +	.type = IIO_INTENSITY, \ +	.modified = 1, \ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ +		BIT(IIO_CHAN_INFO_INT_TIME), \ +	.channel2 = IIO_MOD_LIGHT_##_color, \ +	.address = _addr, \ +	.scan_index = _si, \ +	.scan_type = { \ +		.sign = 'u', \ +		.realbits = 16, \ +		.storagebits = 16, \ +		.endianness = IIO_CPU, \ +	}, \ +} + +static const int tcs3472_agains[] = { 1, 4, 16, 60 }; + +static const struct iio_chan_spec tcs3472_channels[] = { +	TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), +	TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), +	TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA), +	TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA), +	IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3472_req_data(struct tcs3472_data *data) +{ +	int tries = 50; +	int ret; + +	while (tries--) { +		ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); +		if (ret < 0) +			return ret; +		if (ret & TCS3472_STATUS_AVALID) +			break; +		msleep(20); +	} + +	if (tries < 0) { +		dev_err(&data->client->dev, "data not ready\n"); +		return -EIO; +	} + +	return 0; +} + +static int tcs3472_read_raw(struct iio_dev *indio_dev, +			   struct iio_chan_spec const *chan, +			   int *val, int *val2, long mask) +{ +	struct tcs3472_data *data = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		if (iio_buffer_enabled(indio_dev)) +			return -EBUSY; + +		mutex_lock(&data->lock); +		ret = tcs3472_req_data(data); +		if (ret < 0) { +			mutex_unlock(&data->lock); +			return ret; +		} +		ret = i2c_smbus_read_word_data(data->client, chan->address); +		mutex_unlock(&data->lock); +		if (ret < 0) +			return ret; +		*val = ret; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_CALIBSCALE: +		*val = tcs3472_agains[data->control & +			TCS3472_CONTROL_AGAIN_MASK]; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_INT_TIME: +		*val = 0; +		*val2 = (256 - data->atime) * 2400; +		return IIO_VAL_INT_PLUS_MICRO; +	} +	return -EINVAL; +} + +static int tcs3472_write_raw(struct iio_dev *indio_dev, +			       struct iio_chan_spec const *chan, +			       int val, int val2, long mask) +{ +	struct tcs3472_data *data = iio_priv(indio_dev); +	int i; + +	switch (mask) { +	case IIO_CHAN_INFO_CALIBSCALE: +		if (val2 != 0) +			return -EINVAL; +		for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) { +			if (val == tcs3472_agains[i]) { +				data->control &= ~TCS3472_CONTROL_AGAIN_MASK; +				data->control |= i; +				return i2c_smbus_write_byte_data( +					data->client, TCS3472_CONTROL, +					data->control); +			} +		} +		return -EINVAL; +	case IIO_CHAN_INFO_INT_TIME: +		if (val != 0) +			return -EINVAL; +		for (i = 0; i < 256; i++) { +			if (val2 == (256 - i) * 2400) { +				data->atime = i; +				return i2c_smbus_write_word_data( +					data->client, TCS3472_ATIME, +					data->atime); +			} + +		} +		return -EINVAL; +	} +	return -EINVAL; +} + +static irqreturn_t tcs3472_trigger_handler(int irq, void *p) +{ +	struct iio_poll_func *pf = p; +	struct iio_dev *indio_dev = pf->indio_dev; +	struct tcs3472_data *data = iio_priv(indio_dev); +	int i, j = 0; + +	int ret = tcs3472_req_data(data); +	if (ret < 0) +		goto done; + +	for_each_set_bit(i, indio_dev->active_scan_mask, +		indio_dev->masklength) { +		ret = i2c_smbus_read_word_data(data->client, +			TCS3472_CDATA + 2*i); +		if (ret < 0) +			goto done; + +		data->buffer[j++] = ret; +	} + +	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, +		iio_get_time_ns()); + +done: +	iio_trigger_notify_done(indio_dev->trig); + +	return IRQ_HANDLED; +} + +static ssize_t tcs3472_show_int_time_available(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	size_t len = 0; +	int i; + +	for (i = 1; i <= 256; i++) +		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", +			2400 * i); + +	/* replace trailing space by newline */ +	buf[len - 1] = '\n'; + +	return len; +} + +static IIO_CONST_ATTR(calibscale_available, "1 4 16 60"); +static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available); + +static struct attribute *tcs3472_attributes[] = { +	&iio_const_attr_calibscale_available.dev_attr.attr, +	&iio_dev_attr_integration_time_available.dev_attr.attr, +	NULL +}; + +static const struct attribute_group tcs3472_attribute_group = { +	.attrs = tcs3472_attributes, +}; + +static const struct iio_info tcs3472_info = { +	.read_raw = tcs3472_read_raw, +	.write_raw = tcs3472_write_raw, +	.attrs = &tcs3472_attribute_group, +	.driver_module = THIS_MODULE, +}; + +static int tcs3472_probe(struct i2c_client *client, +			   const struct i2c_device_id *id) +{ +	struct tcs3472_data *data; +	struct iio_dev *indio_dev; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (indio_dev == NULL) +		return -ENOMEM; + +	data = iio_priv(indio_dev); +	i2c_set_clientdata(client, indio_dev); +	data->client = client; +	mutex_init(&data->lock); + +	indio_dev->dev.parent = &client->dev; +	indio_dev->info = &tcs3472_info; +	indio_dev->name = TCS3472_DRV_NAME; +	indio_dev->channels = tcs3472_channels; +	indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels); +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID); +	if (ret < 0) +		return ret; + +	if (ret == 0x44) +		dev_info(&client->dev, "TCS34721/34725 found\n"); +	else if (ret == 0x4d) +		dev_info(&client->dev, "TCS34723/34727 found\n"); +	else +		return -ENODEV; + +	ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL); +	if (ret < 0) +		return ret; +	data->control = ret; + +	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME); +	if (ret < 0) +		return ret; +	data->atime = ret; + +	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE); +	if (ret < 0) +		return ret; + +	/* enable device */ +	data->enable = ret | TCS3472_ENABLE_PON | TCS3472_ENABLE_AEN; +	ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, +		data->enable); +	if (ret < 0) +		return ret; + +	ret = iio_triggered_buffer_setup(indio_dev, NULL, +		tcs3472_trigger_handler, NULL); +	if (ret < 0) +		return ret; + +	ret = iio_device_register(indio_dev); +	if (ret < 0) +		goto buffer_cleanup; + +	return 0; + +buffer_cleanup: +	iio_triggered_buffer_cleanup(indio_dev); +	return ret; +} + +static int tcs3472_powerdown(struct tcs3472_data *data) +{ +	return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, +		data->enable & ~(TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON)); +} + +static int tcs3472_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); + +	iio_device_unregister(indio_dev); +	iio_triggered_buffer_cleanup(indio_dev); +	tcs3472_powerdown(iio_priv(indio_dev)); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tcs3472_suspend(struct device *dev) +{ +	struct tcs3472_data *data = iio_priv(i2c_get_clientdata( +		to_i2c_client(dev))); +	return tcs3472_powerdown(data); +} + +static int tcs3472_resume(struct device *dev) +{ +	struct tcs3472_data *data = iio_priv(i2c_get_clientdata( +		to_i2c_client(dev))); +	return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, +		data->enable | (TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON)); +} +#endif + +static SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, tcs3472_resume); + +static const struct i2c_device_id tcs3472_id[] = { +	{ "tcs3472", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tcs3472_id); + +static struct i2c_driver tcs3472_driver = { +	.driver = { +		.name	= TCS3472_DRV_NAME, +		.pm	= &tcs3472_pm_ops, +		.owner	= THIS_MODULE, +	}, +	.probe		= tcs3472_probe, +	.remove		= tcs3472_remove, +	.id_table	= tcs3472_id, +}; +module_i2c_driver(tcs3472_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TCS3472 color light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c index ebb962c5c32..94daa9fc124 100644 --- a/drivers/iio/light/tsl2563.c +++ b/drivers/iio/light/tsl2563.c @@ -460,10 +460,14 @@ static int tsl2563_write_raw(struct iio_dev *indio_dev,  {  	struct tsl2563_chip *chip = iio_priv(indio_dev); -	if (chan->channel == IIO_MOD_LIGHT_BOTH) +	if (mask != IIO_CHAN_INFO_CALIBSCALE) +		return -EINVAL; +	if (chan->channel2 == IIO_MOD_LIGHT_BOTH)  		chip->calib0 = calib_from_sysfs(val); -	else +	else if (chan->channel2 == IIO_MOD_LIGHT_IR)  		chip->calib1 = calib_from_sysfs(val); +	else +		return -EINVAL;  	return 0;  } @@ -472,14 +476,14 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev,  			    struct iio_chan_spec const *chan,  			    int *val,  			    int *val2, -			    long m) +			    long mask)  {  	int ret = -EINVAL;  	u32 calib0, calib1;  	struct tsl2563_chip *chip = iio_priv(indio_dev);  	mutex_lock(&chip->lock); -	switch (m) { +	switch (mask) {  	case IIO_CHAN_INFO_RAW:  	case IIO_CHAN_INFO_PROCESSED:  		switch (chan->type) { @@ -498,7 +502,7 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev,  			ret = tsl2563_get_adc(chip);  			if (ret)  				goto error_ret; -			if (chan->channel == 0) +			if (chan->channel2 == IIO_MOD_LIGHT_BOTH)  				*val = chip->data0;  			else  				*val = chip->data1; @@ -510,7 +514,7 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev,  		break;  	case IIO_CHAN_INFO_CALIBSCALE: -		if (chan->channel == 0) +		if (chan->channel2 == IIO_MOD_LIGHT_BOTH)  			*val = calib_to_sysfs(chip->calib0);  		else  			*val = calib_to_sysfs(chip->calib1); @@ -526,6 +530,20 @@ error_ret:  	return ret;  } +static const struct iio_event_spec tsl2563_events[] = { +	{ +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_RISING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +				BIT(IIO_EV_INFO_ENABLE), +	}, { +		.type = IIO_EV_TYPE_THRESH, +		.dir = IIO_EV_DIR_FALLING, +		.mask_separate = BIT(IIO_EV_INFO_VALUE) | +				BIT(IIO_EV_INFO_ENABLE), +	}, +}; +  static const struct iio_chan_spec tsl2563_channels[] = {  	{  		.type = IIO_LIGHT, @@ -538,10 +556,8 @@ static const struct iio_chan_spec tsl2563_channels[] = {  		.channel2 = IIO_MOD_LIGHT_BOTH,  		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |  		BIT(IIO_CHAN_INFO_CALIBSCALE), -		.event_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH, -					  IIO_EV_DIR_RISING) | -			       IIO_EV_BIT(IIO_EV_TYPE_THRESH, -					  IIO_EV_DIR_FALLING)), +		.event_spec = tsl2563_events, +		.num_event_specs = ARRAY_SIZE(tsl2563_events),  	}, {  		.type = IIO_INTENSITY,  		.modified = 1, @@ -552,12 +568,13 @@ static const struct iio_chan_spec tsl2563_channels[] = {  };  static int tsl2563_read_thresh(struct iio_dev *indio_dev, -			       u64 event_code, -			       int *val) +	const struct iio_chan_spec *chan, enum iio_event_type type, +	enum iio_event_direction dir, enum iio_event_info info, int *val, +	int *val2)  {  	struct tsl2563_chip *chip = iio_priv(indio_dev); -	switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { +	switch (dir) {  	case IIO_EV_DIR_RISING:  		*val = chip->high_thres;  		break; @@ -568,18 +585,19 @@ static int tsl2563_read_thresh(struct iio_dev *indio_dev,  		return -EINVAL;  	} -	return 0; +	return IIO_VAL_INT;  }  static int tsl2563_write_thresh(struct iio_dev *indio_dev, -				  u64 event_code, -				  int val) +	const struct iio_chan_spec *chan, enum iio_event_type type, +	enum iio_event_direction dir, enum iio_event_info info, int val, +	int val2)  {  	struct tsl2563_chip *chip = iio_priv(indio_dev);  	int ret;  	u8 address; -	if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) +	if (dir == IIO_EV_DIR_RISING)  		address = TSL2563_REG_HIGHLOW;  	else  		address = TSL2563_REG_LOWLOW; @@ -591,7 +609,7 @@ static int tsl2563_write_thresh(struct iio_dev *indio_dev,  	ret = i2c_smbus_write_byte_data(chip->client,  					TSL2563_CMD | (address + 1),  					(val >> 8) & 0xFF); -	if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) +	if (dir == IIO_EV_DIR_RISING)  		chip->high_thres = val;  	else  		chip->low_thres = val; @@ -620,8 +638,8 @@ static irqreturn_t tsl2563_event_handler(int irq, void *private)  }  static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev, -					  u64 event_code, -					  int state) +	const struct iio_chan_spec *chan, enum iio_event_type type, +	enum iio_event_direction dir, int state)  {  	struct tsl2563_chip *chip = iio_priv(indio_dev);  	int ret = 0; @@ -662,7 +680,8 @@ out:  }  static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, -					 u64 event_code) +	const struct iio_chan_spec *chan, enum iio_event_type type, +	enum iio_event_direction dir)  {  	struct tsl2563_chip *chip = iio_priv(indio_dev);  	int ret; @@ -699,6 +718,7 @@ static int tsl2563_probe(struct i2c_client *client,  	struct iio_dev *indio_dev;  	struct tsl2563_chip *chip;  	struct tsl2563_platform_data *pdata = client->dev.platform_data; +	struct device_node *np = client->dev.of_node;  	int err = 0;  	u8 id = 0; @@ -735,6 +755,9 @@ static int tsl2563_probe(struct i2c_client *client,  	if (pdata)  		chip->cover_comp_gain = pdata->cover_comp_gain; +	else if (np) +		of_property_read_u32(np, "amstaos,cover-comp-gain", +				     &chip->cover_comp_gain);  	else  		chip->cover_comp_gain = 1; diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c new file mode 100644 index 00000000000..a15006efa13 --- /dev/null +++ b/drivers/iio/light/tsl4531.c @@ -0,0 +1,258 @@ +/* + * tsl4531.c - Support for TAOS TSL4531 ambient light sensor + * + * Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for the TSL4531x family + *   TSL45311/TSL45313: 7-bit I2C slave address 0x39 + *   TSL45315/TSL45317: 7-bit I2C slave address 0x29 + * + * TODO: single cycle measurement + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define TSL4531_DRV_NAME "tsl4531" + +#define TCS3472_COMMAND BIT(7) + +#define TSL4531_CONTROL (TCS3472_COMMAND | 0x00) +#define TSL4531_CONFIG (TCS3472_COMMAND | 0x01) +#define TSL4531_DATA (TCS3472_COMMAND | 0x04) +#define TSL4531_ID (TCS3472_COMMAND | 0x0a) + +/* operating modes in control register */ +#define TSL4531_MODE_POWERDOWN 0x00 +#define TSL4531_MODE_SINGLE_ADC 0x02 +#define TSL4531_MODE_NORMAL 0x03 + +/* integration time control in config register */ +#define TSL4531_TCNTRL_400MS 0x00 +#define TSL4531_TCNTRL_200MS 0x01 +#define TSL4531_TCNTRL_100MS 0x02 + +/* part number in id register */ +#define TSL45311_ID 0x8 +#define TSL45313_ID 0x9 +#define TSL45315_ID 0xa +#define TSL45317_ID 0xb +#define TSL4531_ID_SHIFT 4 + +struct tsl4531_data { +	struct i2c_client *client; +	struct mutex lock; +	int int_time; +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4"); + +static struct attribute *tsl4531_attributes[] = { +	&iio_const_attr_integration_time_available.dev_attr.attr, +	NULL +}; + +static const struct attribute_group tsl4531_attribute_group = { +	.attrs = tsl4531_attributes, +}; + +static const struct iio_chan_spec tsl4531_channels[] = { +	{ +		.type = IIO_LIGHT, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | +			BIT(IIO_CHAN_INFO_SCALE) | +			BIT(IIO_CHAN_INFO_INT_TIME) +	} +}; + +static int tsl4531_read_raw(struct iio_dev *indio_dev, +				struct iio_chan_spec const *chan, +				int *val, int *val2, long mask) +{ +	struct tsl4531_data *data = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		ret = i2c_smbus_read_word_data(data->client, +			TSL4531_DATA); +		if (ret < 0) +			return ret; +		*val = ret; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_SCALE: +		/* 0.. 1x, 1 .. 2x, 2 .. 4x */ +		*val = 1 << data->int_time; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_INT_TIME: +		if (data->int_time == 0) +			*val2 = 400000; +		else if (data->int_time == 1) +			*val2 = 200000; +		else if (data->int_time == 2) +			*val2 = 100000; +		else +			return -EINVAL; +		*val = 0; +		return IIO_VAL_INT_PLUS_MICRO; +	default: +		return -EINVAL; +	} +} + +static int tsl4531_write_raw(struct iio_dev *indio_dev, +			     struct iio_chan_spec const *chan, +			     int val, int val2, long mask) +{ +	struct tsl4531_data *data = iio_priv(indio_dev); +	int int_time, ret; + +	switch (mask) { +	case IIO_CHAN_INFO_INT_TIME: +		if (val != 0) +			return -EINVAL; +		if (val2 == 400000) +			int_time = 0; +		else if (val2 == 200000) +			int_time = 1; +		else if (val2 == 100000) +			int_time = 2; +		else +			return -EINVAL; +		mutex_lock(&data->lock); +		ret = i2c_smbus_write_byte_data(data->client, +			TSL4531_CONFIG, int_time); +		if (ret >= 0) +			data->int_time = int_time; +		mutex_unlock(&data->lock); +		return ret; +	default: +		return -EINVAL; +	} +} + +static const struct iio_info tsl4531_info = { +	.read_raw = tsl4531_read_raw, +	.write_raw = tsl4531_write_raw, +	.attrs = &tsl4531_attribute_group, +	.driver_module = THIS_MODULE, +}; + +static int tsl4531_check_id(struct i2c_client *client) +{ +	int ret = i2c_smbus_read_byte_data(client, TSL4531_ID); +	if (ret < 0) +		return ret; + +	switch (ret >> TSL4531_ID_SHIFT) { +	case TSL45311_ID: +	case TSL45313_ID: +	case TSL45315_ID: +	case TSL45317_ID: +		return 1; +	default: +		return 0; +	} +} + +static int tsl4531_probe(struct i2c_client *client, +			  const struct i2c_device_id *id) +{ +	struct tsl4531_data *data; +	struct iio_dev *indio_dev; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (!indio_dev) +		return -ENOMEM; + +	data = iio_priv(indio_dev); +	i2c_set_clientdata(client, indio_dev); +	data->client = client; +	mutex_init(&data->lock); + +	if (!tsl4531_check_id(client)) { +		dev_err(&client->dev, "no TSL4531 sensor\n"); +		return -ENODEV; +	} + +	ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL, +		TSL4531_MODE_NORMAL); +	if (ret < 0) +		return ret; + +	ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG, +		TSL4531_TCNTRL_400MS); +	if (ret < 0) +		return ret; + +	indio_dev->dev.parent = &client->dev; +	indio_dev->info = &tsl4531_info; +	indio_dev->channels = tsl4531_channels; +	indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels); +	indio_dev->name = TSL4531_DRV_NAME; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	return iio_device_register(indio_dev); +} + +static int tsl4531_powerdown(struct i2c_client *client) +{ +	return i2c_smbus_write_byte_data(client, TSL4531_CONTROL, +		TSL4531_MODE_POWERDOWN); +} + +static int tsl4531_remove(struct i2c_client *client) +{ +	iio_device_unregister(i2c_get_clientdata(client)); +	tsl4531_powerdown(client); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tsl4531_suspend(struct device *dev) +{ +	return tsl4531_powerdown(to_i2c_client(dev)); +} + +static int tsl4531_resume(struct device *dev) +{ +	return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL, +		TSL4531_MODE_NORMAL); +} +#endif + +static SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, tsl4531_resume); + +static const struct i2c_device_id tsl4531_id[] = { +	{ "tsl4531", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tsl4531_id); + +static struct i2c_driver tsl4531_driver = { +	.driver = { +		.name   = TSL4531_DRV_NAME, +		.pm	= &tsl4531_pm_ops, +		.owner  = THIS_MODULE, +	}, +	.probe  = tsl4531_probe, +	.remove = tsl4531_remove, +	.id_table = tsl4531_id, +}; + +module_i2c_driver(tsl4531_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 2bb304215b1..d948c4778ba 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -56,7 +56,7 @@ static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,  				u8 rdy_mask, u8 data_reg, int *val)  {  	int tries = 20; -	u16 buf; +	__be16 buf;  	int ret;  	ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, @@ -179,17 +179,7 @@ static int vcnl4000_probe(struct i2c_client *client,  	indio_dev->name = VCNL4000_DRV_NAME;  	indio_dev->modes = INDIO_DIRECT_MODE; -	ret = iio_device_register(indio_dev); -	if (ret < 0) -		return ret; - -	return 0; -} - -static int vcnl4000_remove(struct i2c_client *client) -{ -	iio_device_unregister(i2c_get_clientdata(client)); -	return 0; +	return devm_iio_device_register(&client->dev, indio_dev);  }  static struct i2c_driver vcnl4000_driver = { @@ -198,7 +188,6 @@ static struct i2c_driver vcnl4000_driver = {  		.owner  = THIS_MODULE,  	},  	.probe  = vcnl4000_probe, -	.remove = vcnl4000_remove,  	.id_table = vcnl4000_id,  };  | 
