Re: [PATCH/RFC v2] iio: gp2ap020a00f: Add a driver for the device

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> Add a new driver for the ambient light/proximity sensor
> device. The driver exposes three channels: light_clear

the driver is described as 'proximity/opto' and also 'proximity/ambient 
light' -- most drivers say ALS, not opto

more small comments inline

regards, p.

> ---
>  .../devicetree/bindings/iio/light/gp2ap020a00f.txt |   20 +
>  drivers/iio/light/Kconfig                          |   12 +
>  drivers/iio/light/Makefile                         |    1 +
>  drivers/iio/light/gp2ap020a00f.c                   | 1193 ++++++++++++++++++++
>  4 files changed, 1226 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/iio/light/gp2ap020a00f.txt
>  create mode 100644 drivers/iio/light/gp2ap020a00f.c
> 
> diff --git a/Documentation/devicetree/bindings/iio/light/gp2ap020a00f.txt b/Documentation/devicetree/bindings/iio/light/gp2ap020a00f.txt
> new file mode 100644
> index 0000000..bef1d37
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/light/gp2ap020a00f.txt
> @@ -0,0 +1,20 @@
> +* Sharp GP2AP020A00F I2C Proximity/Opto sensor
> +
> +Required properties:
> +
> +  - compatible : should be "sharp,gp2ap020a00f"
> +  - reg : the I2C address of the light sensor
> +  - interrupt-parent : phandle to the parent interrupt controller
> +  - interrupts : should be INT interrupt pin
> +  - vled-supply : VLED power supply, as covered
> +		  in Documentation/devicetree/bindings/regulator/regulator.txt
> +
> +Example:
> +
> +gp2ap020a00f@39 {
> +	compatible = "sharp,gp2ap020a00f";
> +	reg = <0x39>;
> +	interrupt-parent = <&gpx0>;
> +	interrupts = <2 0>;
> +	vled-supply = <...>;
> +};
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 5ef1a39..c3d5152 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -52,6 +52,18 @@ config VCNL4000
>  	 To compile this driver as a module, choose M here: the
>  	 module will be called vcnl4000.
>  
> +config GP2AP020A00F
> +	tristate "Sharp GP2AP020A00F I2C Proximity/Opto sensor driver"
> +	depends on I2C
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
> +	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
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index 040d9c7..b20c667 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -7,3 +7,4 @@ obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
>  obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
>  obj-$(CONFIG_VCNL4000)		+= vcnl4000.o
>  obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
> +obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
> diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c
> new file mode 100644
> index 0000000..abb246c
> --- /dev/null
> +++ b/drivers/iio/light/gp2ap020a00f.c
> @@ -0,0 +1,1193 @@
> +/*
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + * Author: Jacek Anaszewski <j.anaszewski@xxxxxxxxxxx>
> + *
> + * 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/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.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/iio/trigger.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +
> +#define GP2A_I2C_NAME "gp2ap020a00f"
> +
> +/* Registers */
> +#define OP_REG			0x00 /* Basic operations */
> +#define ALS_REG			0x01 /* ALS related settings */
> +#define PS_REG			0x02 /* PS related settings */
> +#define LED_REG			0x03 /* LED reg */
> +#define TL_L_REG		0x04 /* ALS: Threshold low LSB */
> +#define TL_H_REG		0x05 /* ALS: Threshold low MSB */
> +#define TH_L_REG		0x06 /* ALS: Threshold high LSB */
> +#define TH_H_REG		0x07 /* ALS: Threshold high MSB */
> +#define PL_L_REG		0x08 /* PS: Threshold low LSB */
> +#define PL_H_REG		0x09 /* PS: Threshold low MSB */
> +#define PH_L_REG		0x0a /* PS: Threshold high LSB */
> +#define PH_H_REG		0x0b /* PS: Threshold high MSB */
> +#define D0_L_REG		0x0c /* ALS result: Clear/Illuminance LSB */
> +#define D0_H_REG		0x0d /* ALS result: Clear/Illuminance MSB */
> +#define D1_L_REG		0x0e /* ALS result: IR LSB */
> +#define D1_H_REG		0x0f /* ALS result: IR LSB */
> +#define D2_L_REG		0x10 /* PS result LSB */
> +#define D2_H_REG		0x11 /* PS result MSB */
> +#define REGS_NUM		0x12 /* Number of registers */
> +
> +/* OP_REG bits */
> +#define OP3_MASK		0x80 /* Software shutdown */
> +#define OP3_SHUTDOWN		0x00
> +#define OP3_OPERATION		0x80
> +#define OP2_MASK		0x40 /* Auto shutdown/Continuous operation  */
> +#define OP2_AUTO_SHUTDOWN	0x00
> +#define OP2_CONT_OPERATION	0x40
> +#define OP_MASK			0x30 /* Operating mode selection  */
> +#define OP_ALS_AND_PS		0x00
> +#define OP_ALS			0x10
> +#define OP_PS			0x20
> +#define OP_DEBUG		0x30
> +#define PROX_MASK		0x08 /* PS: detection/non-detection  */
> +#define PROX_NON_DETECT		0x00
> +#define PROX_DETECT		0x08
> +#define FLAG_P			0x04 /* PS: interrupt result  */
> +#define FLAG_A			0x02 /* ALS: interrupt result  */
> +#define TYPE_MASK		0x01 /* Output data type selection */
> +#define TYPE_MANUAL_CALC	0x00
> +#define TYPE_AUTO_CALC		0x01
> +
> +/* ALS_REG bits */
> +#define PRST_MASK		0xc0 /* Number of measurement cycles */
> +#define PRST_ONCE		0x00
> +#define PRST_4_CYCLES		0x40
> +#define PRST_8_CYCLES		0x80
> +#define PRST_16_CYCLES		0xc0
> +#define RES_A_MASK		0x38 /* ALS: Resolution (0.39ms - 800ms) */
> +#define RES_A_800ms		0x00
> +#define RES_A_400ms		0x08
> +#define RES_A_200ms		0x10
> +#define RES_A_100ms		0x18
> +#define RES_A_25ms		0x20
> +#define RES_A_6_25ms		0x28
> +#define RES_A_1_56ms		0x30
> +#define RES_A_0_39ms		0x38
> +#define RANGE_A_MASK		0x07 /* ALS: Max measurable range (x1 - x128) */
> +#define RANGE_A_x1		0x00
> +#define RANGE_A_x2		0x01
> +#define RANGE_A_x4		0x02
> +#define RANGE_A_x8		0x03
> +#define RANGE_A_x16		0x04
> +#define RANGE_A_x32		0x05
> +#define RANGE_A_x64		0x06
> +#define RANGE_A_x128		0x07
> +
> +/* PS_REG bits */
> +#define ALC_MASK		0x80 /* Auto light cancel */
> +#define ALC_ON			0x80
> +#define ALC_OFF			0x00
> +#define INTTYPE_MASK		0x40 /* Interrupt type setting */
> +#define INTTYPE_LEVEL		0x00
> +#define INTTYPE_PULSE		0x40
> +#define RES_P_MASK		0x38 /* PS: Resolution (0.39ms - 800ms)  */
> +#define RES_P_800ms_x2		0x00
> +#define RES_P_400ms_x2		0x08
> +#define RES_P_200ms_x2		0x10
> +#define RES_P_100ms_x2		0x18
> +#define RES_P_25ms_x2		0x20
> +#define RES_P_6_25ms_x2		0x28
> +#define RES_P_1_56ms_x2		0x30
> +#define RES_P_0_39ms_x2		0x38
> +#define RANGE_P_MASK		0x07 /* PS: Max measurable range (x1 - x128) */
> +#define RANGE_P_x1		0x00
> +#define RANGE_P_x2		0x01
> +#define RANGE_P_x4		0x02
> +#define RANGE_P_x8		0x03
> +#define RANGE_P_x16		0x04
> +#define RANGE_P_x32		0x05
> +#define RANGE_P_x64		0x06
> +#define RANGE_P_x128		0x07
> +
> +/* LED reg bits */
> +#define INTVAL_MASK		0xc0 /* Intermittent operating */
> +#define INTVAL_0		0x00
> +#define INTVAL_4		0x40
> +#define INTVAL_8		0x80
> +#define INTVAL_16		0xc0
> +#define IS_MASK			0x30 /* ILED drive peak current setting  */
> +#define IS_13_8mA		0x00
> +#define IS_27_5mA		0x10
> +#define IS_55mA			0x20
> +#define IS_110mA		0x30
> +#define PIN_MASK		0x0c /* INT terminal setting */
> +#define PIN_ALS_OR_PS		0x00
> +#define PIN_ALS			0x04
> +#define PIN_PS			0x08
> +#define PIN_PS_DETECT		0x0c
> +#define FREQ_MASK		0x02 /* LED modulation frequency */
> +#define FREQ_327_5kHz		0x00
> +#define FREQ_81_8kHz		0x02
> +#define 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 ((1000*HZ)/1000)

shouldn't this use msecs_to_jiffies() instead of HZ?

> +
> +#define GP2AP020A00F_DATA_REG(chan) (D0_L_REG + (chan) * 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,
> +};
> +
> +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_EV_EN,
> +	GP2AP020A00F_CMD_PROX_EV_DIS,
> +};
> +
> +enum gp2ap020a00f_flags {
> +	GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER,
> +	GP2AP020A00F_FLAG_ALS_IR_TRIGGER,
> +	GP2AP020A00F_FLAG_PS_TRIGGER,
> +	GP2AP020A00F_FLAG_PS_RISING_EVENT,
> +	GP2AP020A00F_FLAG_ALS_RISING_EVENT,
> +	GP2AP020A00F_FLAG_ALS_FALLING_EVENT,
> +	GP2AP020A00F_FLAG_DATA_READY,
> +};
> +
> +struct gp2ap020a00f_data {
> +	const struct gp2ap020a00f_platform_data *pdata;
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	char *buffer;
> +	u8 reg_cache[REGS_NUM];
> +	struct regulator *vled_reg;
> +	unsigned long flags;
> +	enum gp2ap020a00f_opmode cur_opmode;
> +	wait_queue_head_t data_ready_queue;
> +	struct iio_trigger *trig;
> +};
> +
> +static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data,
> +					enum gp2ap020a00f_opmode op)
> +{
> +	u8 prev_ctrl_regs[4];
> +	int err;
> +
> +	prev_ctrl_regs[OP_REG] = data->reg_cache[OP_REG];
> +	prev_ctrl_regs[ALS_REG] = data->reg_cache[ALS_REG];
> +	prev_ctrl_regs[PS_REG] = data->reg_cache[PS_REG];
> +	prev_ctrl_regs[LED_REG] = data->reg_cache[LED_REG];
> +
> +	data->reg_cache[OP_REG] &= ~(OP_MASK | OP2_MASK | OP3_MASK
> +				     | TYPE_MASK);
> +	data->reg_cache[ALS_REG] &= ~PRST_MASK;
> +	data->reg_cache[LED_REG] &= ~PIN_MASK;
> +
> +	switch (op) {
> +	case GP2AP020A00F_OPMODE_READ_RAW_CLEAR:
> +	case GP2AP020A00F_OPMODE_READ_RAW_IR:
> +		data->reg_cache[OP_REG] |= (OP_ALS | OP2_AUTO_SHUTDOWN
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_ONCE;
> +		data->reg_cache[LED_REG] |= PIN_ALS;
> +		break;
> +	case GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY:
> +		data->reg_cache[OP_REG] |= (OP_PS | OP2_AUTO_SHUTDOWN
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_ONCE;
> +		data->reg_cache[LED_REG] |= PIN_PS;
> +		break;
> +	case GP2AP020A00F_OPMODE_PROX_DETECT:
> +		data->reg_cache[OP_REG] |= (OP_PS | OP2_CONT_OPERATION
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_4_CYCLES;
> +		data->reg_cache[LED_REG] |= PIN_PS_DETECT;
> +		break;
> +	case GP2AP020A00F_OPMODE_ALS:
> +		data->reg_cache[OP_REG] |= (OP_ALS | OP2_CONT_OPERATION
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_ONCE;
> +		data->reg_cache[LED_REG] |= PIN_ALS;
> +		break;
> +	case GP2AP020A00F_OPMODE_PS:
> +		data->reg_cache[OP_REG] |= (OP_PS | OP2_CONT_OPERATION
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_4_CYCLES;
> +		data->reg_cache[LED_REG] |= PIN_PS;
> +		break;
> +	case GP2AP020A00F_OPMODE_ALS_AND_PS:
> +		data->reg_cache[OP_REG] |= (OP_ALS_AND_PS | OP2_CONT_OPERATION
> +					   | OP3_OPERATION | TYPE_MANUAL_CALC);
> +		data->reg_cache[ALS_REG] |= PRST_4_CYCLES;
> +		data->reg_cache[LED_REG] |= PIN_ALS_OR_PS;
> +		break;
> +	case GP2AP020A00F_OPMODE_SHUTDOWN:
> +		/*
> +		 * Bring back last OP state to avoid sending shutdown
> +		 * command twice due to spurious op mode transition.
> +		 */
> +		data->reg_cache[OP_REG] = prev_ctrl_regs[OP_REG]
> +					& OP_MASK;
> +		break;
> +	}
> +
> +	/*
> +	 * Shutdown the device if the operation being executed entails
> +	 * mode transition.
> +	 */
> +	if ((data->reg_cache[OP_REG] & OP_MASK) !=
> +	    (prev_ctrl_regs[OP_REG] & OP_MASK)) {
> +		/* set shutdown mode */
> +		err = i2c_smbus_write_byte_data(data->client, OP_REG, 0);
> +		if (err < 0)
> +			goto error_ret;
> +	}
> +
> +	err = i2c_smbus_write_i2c_block_data(data->client, ALS_REG, 3,
> +						&data->reg_cache[ALS_REG]);
> +	if (err < 0)
> +		goto error_ret;
> +
> +	/* Set OP_REG and apply operation mode (power on / off) */
> +	err = i2c_smbus_write_byte_data(data->client, OP_REG,
> +						data->reg_cache[OP_REG]);
> +	if (err < 0)
> +		goto error_ret;
> +
> +	data->cur_opmode = op;
> +
> +	return 0;
> +
> +error_ret:
> +	data->reg_cache[OP_REG] = prev_ctrl_regs[OP_REG];
> +	data->reg_cache[ALS_REG] = prev_ctrl_regs[ALS_REG];
> +	data->reg_cache[PS_REG] = prev_ctrl_regs[PS_REG];
> +	data->reg_cache[LED_REG] = prev_ctrl_regs[LED_REG];
> +
> +	return err;
> +}
> +
> +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_EVENT,
> +							&data->flags) ||
> +	       test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EVENT,
> +							&data->flags);
> +}
> +
> +static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data,
> +			enum gp2ap020a00f_opmode diff_mode, int add_sub)
> +{
> +	enum gp2ap020a00f_opmode new_mode;
> +	int err;
> +
> +	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;
> +	}
> +
> +	err = gp2ap020a00f_set_operation_mode(data, new_mode);
> +
> +	return err;
> +}
> +
> +static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data,
> +					enum gp2ap020a00f_cmd cmd)
> +{
> +	const u8 thresh_off_buf[2] = {0x00, 0x00};
> +	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:
> +		set_bit(GP2AP020A00F_FLAG_PS_TRIGGER, &data->flags);
> +		/*
> +		 * Don't change opmode if in PROX_DETECT, as
> +		 * it is compatible with PS mode.
> +		 */
> +		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
> +			break;
> +		err = gp2ap020a00f_alter_opmode(data,
> +						GP2AP020A00F_OPMODE_PS,
> +						GP2AP020A00F_ADD_MODE);
> +		break;
> +	case GP2AP020A00F_CMD_TRIGGER_PROX_DIS:
> +		clear_bit(GP2AP020A00F_FLAG_PS_TRIGGER, &data->flags);
> +		if (test_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT, &data->flags))
> +			break;
> +		err = gp2ap020a00f_alter_opmode(data,
> +						GP2AP020A00F_OPMODE_PS,
> +						GP2AP020A00F_SUBTRACT_MODE);
> +		break;
> +	case GP2AP020A00F_CMD_ALS_HIGH_EV_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);
> +			if (err < 0)
> +				return err;
> +		}
> +		set_bit(GP2AP020A00F_FLAG_ALS_RISING_EVENT, &data->flags);
> +		err = i2c_smbus_write_i2c_block_data(data->client, TH_L_REG, 2,
> +						&data->reg_cache[TH_L_REG]);

could use write_word_data() instead of i2c_smbus_write_i2c_block_data()

maybe check with i2c_check_functionality() in probe() if block transfer is 
supported

> +		break;
> +	case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS:
> +		clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EVENT, &data->flags);
> +		if (!gp2ap020a00f_als_enabled(data)) {
> +			err = gp2ap020a00f_alter_opmode(data,
> +						GP2AP020A00F_OPMODE_ALS,
> +						GP2AP020A00F_SUBTRACT_MODE);
> +			if (err < 0)
> +				return err;
> +		}
> +		err = i2c_smbus_write_i2c_block_data(data->client, TH_L_REG, 2,
> +						thresh_off_buf);
> +		break;
> +	case GP2AP020A00F_CMD_ALS_LOW_EV_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);
> +			if (err < 0)
> +				return err;
> +		}
> +		set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EVENT, &data->flags);
> +		err = i2c_smbus_write_i2c_block_data(data->client, TL_L_REG, 2,
> +						&data->reg_cache[TL_L_REG]);
> +		break;
> +	case GP2AP020A00F_CMD_ALS_LOW_EV_DIS:
> +		clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EVENT, &data->flags);
> +		if (!gp2ap020a00f_als_enabled(data)) {
> +			err = gp2ap020a00f_alter_opmode(data,
> +						GP2AP020A00F_OPMODE_ALS,
> +						GP2AP020A00F_SUBTRACT_MODE);
> +			if (err < 0)
> +				return err;
> +		}
> +		err = i2c_smbus_write_i2c_block_data(data->client, TL_L_REG, 2,
> +						thresh_off_buf);
> +		break;
> +	case GP2AP020A00F_CMD_PROX_EV_EN:
> +		if (gp2ap020a00f_als_enabled(data))
> +			return -EBUSY;
> +		set_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT, &data->flags);
> +		err = gp2ap020a00f_set_operation_mode(data,
> +					GP2AP020A00F_OPMODE_PROX_DETECT);
> +		if (err < 0)
> +			return err;
> +		err = i2c_smbus_write_i2c_block_data(data->client, PH_L_REG, 2,
> +						&data->reg_cache[PH_L_REG]);
> +		break;
> +	case GP2AP020A00F_CMD_PROX_EV_DIS:
> +		clear_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT, &data->flags);
> +		if (test_bit(GP2AP020A00F_FLAG_PS_TRIGGER, &data->flags))
> +			err = gp2ap020a00f_set_operation_mode(data,
> +					GP2AP020A00F_OPMODE_PS);
> +		else
> +			err = gp2ap020a00f_set_operation_mode(data,
> +					GP2AP020A00F_OPMODE_SHUTDOWN);
> +		if (err < 0)
> +			return err;
> +		err = i2c_smbus_write_i2c_block_data(data->client, PH_L_REG, 2,
> +						thresh_off_buf);
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int gp2ap020a00f_get_reg_cache_word(struct gp2ap020a00f_data *data,
> +					u8 reg_addr)
> +{
> +	return (data->reg_cache[reg_addr + 1] << 8) |
> +		data->reg_cache[reg_addr];
> +}
> +
> +/* Returns 0 if the end of conversion interrupt occured or -ETIME otherwise */
> +static int wait_conversion_complete_interrupt(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,
> +					u8 output_reg, int *val)
> +{
> +	int err = -EINVAL;
> +
> +	err = wait_conversion_complete_interrupt(data);
> +	if (err < 0)
> +		dev_dbg(&data->client->dev, "data ready timeout\n");
> +
> +	err = i2c_smbus_read_i2c_block_data(data->client, output_reg, 2,
> +						&data->reg_cache[output_reg]);
> +	if (err < 0)
> +		return err;
> +
> +	*val = gp2ap020a00f_get_reg_cache_word(data, output_reg);
> +
> +	return err;
> +}
> +
> +static irqreturn_t gp2ap020a00f_event_handler(int irq, void *data)
> +{
> +	struct iio_dev *indio_dev = data;
> +	struct gp2ap020a00f_data *lgt = iio_priv(indio_dev);
> +	u8 op_reg_val, op_reg_flags;
> +	int output_val, ret;
> +
> +	/* Read interrupt flags */
> +	ret = i2c_smbus_read_i2c_block_data(lgt->client, OP_REG, 1,
> +						&op_reg_val);

i2c_smbus_read_byte_data()?

> +	if (ret < 0)
> +		goto done;
> +
> +	op_reg_flags = op_reg_val & (FLAG_A | FLAG_P | PROX_DETECT);
> +
> +	/* Clear interrupt flags */
> +	op_reg_val &= (~FLAG_A & ~FLAG_P & ~PROX_DETECT);
> +	ret = i2c_smbus_write_i2c_block_data(lgt->client, OP_REG, 1,
> +						&op_reg_val);
> +	if (ret < 0)
> +		goto done;
> +
> +	if (lgt->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR ||
> +	    lgt->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR ||
> +	    lgt->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) {
> +		set_bit(GP2AP020A00F_FLAG_DATA_READY, &lgt->flags);
> +		wake_up(&lgt->data_ready_queue);
> +		goto done;
> +	}
> +
> +	/*
> +	 * We need to read output value to distinguish
> +	 * between high and low ambient light threshold event.
> +	 */
> +	if (op_reg_flags & FLAG_A) {
> +		ret = i2c_smbus_read_i2c_block_data(lgt->client, D0_L_REG, 2,
> +					&lgt->reg_cache[D0_L_REG]);
> +		if (ret < 0)
> +			goto done;
> +
> +		output_val = gp2ap020a00f_get_reg_cache_word(lgt, D0_L_REG);
> +
> +		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EVENT, &lgt->flags))
> +			if (output_val > gp2ap020a00f_get_reg_cache_word(lgt,
> +								 TH_L_REG))
> +				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_EVENT, &lgt->flags))
> +			if (output_val < gp2ap020a00f_get_reg_cache_word(lgt,
> +								    TL_L_REG))
> +				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 (test_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT, &lgt->flags) &&
> +	    (op_reg_flags & FLAG_P)) {
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(
> +				    IIO_PROXIMITY,
> +				    GP2AP020A00F_SCAN_MODE_PROXIMITY,
> +				    IIO_EV_TYPE_THRESH,
> +				    IIO_EV_DIR_RISING),
> +			       iio_get_time_ns());
> +	}
> +
> +	/* This fires off trigger handler */
> +	iio_trigger_poll(lgt->trig, 0);
> +
> +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 *lgt = iio_priv(indio_dev);
> +	size_t d_size = 0;
> +	int i, ret;
> +
> +	for_each_set_bit(i, indio_dev->active_scan_mask,
> +		indio_dev->masklength) {
> +		ret = i2c_smbus_read_i2c_block_data(lgt->client,
> +				GP2AP020A00F_DATA_REG(i), 2,
> +				&lgt->buffer[d_size]);
> +		if (ret < 0)
> +			goto done;
> +		d_size += 2;
> +	}
> +
> +	if (indio_dev->scan_timestamp) {
> +		s64 *timestamp = (s64 *)((u8 *)lgt->buffer +
> +						ALIGN(d_size, sizeof(s64)));
> +		*timestamp = pf->timestamp;
> +	}
> +
> +	iio_push_to_buffers(indio_dev, lgt->buffer);
> +
> +done:
> +	iio_trigger_notify_done(indio_dev->trig);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int gp2ap020a00f_setup(struct gp2ap020a00f_data *data)
> +{
> +	data->reg_cache[OP_REG] = OP3_SHUTDOWN;
> +	data->reg_cache[LED_REG] = INTVAL_0 | IS_110mA | FREQ_327_5kHz;
> +	data->reg_cache[ALS_REG] = RES_A_25ms | RANGE_A_x8;
> +	data->reg_cache[PS_REG] = ALC_ON | INTTYPE_LEVEL | RES_P_1_56ms_x2
> +				  | RANGE_P_x4;
> +
> +	return i2c_smbus_write_i2c_block_data(data->client, OP_REG, REGS_NUM,
> +						&data->reg_cache[OP_REG]);
> +}
> +
> +static u8 gp2ap020a00f_get_reg_by_event_code(u64 event_code)
> +{
> +	int chan_type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code);
> +
> +	if (chan_type == IIO_PROXIMITY) {
> +		/*
> +		 * Only IIO_EV_DIR_RISING direction for the IIO_PROXIMITY
> +		 * event is supported by the driver.
> +		 */
> +		return PH_L_REG;
> +	} else if (chan_type == IIO_LIGHT) {
> +		if (IIO_EVENT_CODE_EXTRACT_DIR(event_code)
> +					== IIO_EV_DIR_RISING)
> +			return TH_L_REG;
> +		else
> +			return TL_L_REG;
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int gp2ap020a00f_write_event_val(struct iio_dev *indio_dev,
> +					u64 event_code, int val)
> +{
> +	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
> +	bool event_en = false;
> +	int thresh_reg_l, err = 0;
> +
> +	mutex_lock(&data->lock);
> +
> +	thresh_reg_l = gp2ap020a00f_get_reg_by_event_code(event_code);
> +
> +	if (thresh_reg_l > PH_L_REG) {
> +		err = -EINVAL;
> +		goto error_ret;
> +	}
> +
> +	data->reg_cache[thresh_reg_l] = val & 0xff;
> +	data->reg_cache[thresh_reg_l + 1] = val >> 8;
> +
> +	switch (thresh_reg_l) {
> +	case TH_L_REG:
> +		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EVENT,
> +							&data->flags))
> +			event_en = true;
> +		break;
> +	case TL_L_REG:
> +		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EVENT,
> +							&data->flags))
> +			event_en = true;
> +		break;
> +	case PH_L_REG:
> +		if (test_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT,
> +							&data->flags))
> +			event_en = true;
> +		break;
> +	}
> +
> +	if (event_en)
> +		err = i2c_smbus_write_i2c_block_data(data->client,
> +					thresh_reg_l, 2,
> +					&data->reg_cache[thresh_reg_l]);
> +error_ret:
> +	mutex_unlock(&data->lock);
> +
> +	return err;
> +}
> +
> +static int gp2ap020a00f_read_event_val(struct iio_dev *indio_dev,
> +					u64 event_code, int *val)
> +{
> +	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
> +	int thresh_reg_l;
> +
> +	mutex_lock(&data->lock);
> +
> +	thresh_reg_l = gp2ap020a00f_get_reg_by_event_code(event_code);
> +
> +	*val = gp2ap020a00f_get_reg_cache_word(data, thresh_reg_l);
> +
> +	mutex_unlock(&data->lock);
> +
> +	return 0;
> +}
> +
> +static int gp2ap020a00f_write_event_config(struct iio_dev *indio_dev,
> +					u64 event_code, int state)
> +{
> +	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
> +	enum gp2ap020a00f_cmd cmd;
> +	int chan_type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code);
> +	int err;
> +
> +	mutex_lock(&data->lock);
> +
> +	if (chan_type == IIO_PROXIMITY) {
> +		cmd = state ? GP2AP020A00F_CMD_PROX_EV_EN :
> +			      GP2AP020A00F_CMD_PROX_EV_DIS;
> +		err = gp2ap020a00f_exec_cmd(data, cmd);
> +	} else if (chan_type == IIO_LIGHT) {
> +		if (IIO_EVENT_CODE_EXTRACT_DIR(event_code)
> +					== 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);
> +		}
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	mutex_unlock(&data->lock);
> +
> +	return err;
> +}
> +
> +static int gp2ap020a00f_read_event_config(struct iio_dev *indio_dev,
> +					u64 event_code)
> +{
> +	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
> +	int chan_type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code);
> +	int event_en;
> +
> +	mutex_lock(&data->lock);
> +
> +	if (chan_type == IIO_PROXIMITY) {
> +		event_en = test_bit(GP2AP020A00F_FLAG_PS_RISING_EVENT,
> +								&data->flags);
> +	} else if (chan_type == IIO_LIGHT) {
> +		if (IIO_EVENT_CODE_EXTRACT_DIR(event_code)
> +					== IIO_EV_DIR_RISING)
> +			event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EVENT,
> +								&data->flags);
> +		else
> +			event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EVENT,
> +								&data->flags);
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	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;
> +	}
> +
> +	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");
> +
> +	data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN;
> +
> +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_ret;
> +		}
> +		err = gp2ap020a00f_read_channel(data, chan, val);
> +		break;
> +	}
> +
> +error_ret:
> +	mutex_unlock(&data->lock);
> +
> +	return err < 0 ? err : IIO_VAL_INT;
> +}
> +
> +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 = 16,
> +			.shift = 0,
> +			.storagebits = 16,
> +			.endianness = IIO_LE,
> +		},
> +		.scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR,
> +		.address = D0_L_REG,
> +		.event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH,
> +					 IIO_EV_DIR_RISING) |
> +			      IIO_EV_BIT(IIO_EV_TYPE_THRESH,
> +					 IIO_EV_DIR_FALLING),
> +	},
> +	{
> +		.type = IIO_LIGHT,
> +		.channel2 = IIO_MOD_LIGHT_IR,
> +		.modified = 1,
> +		.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_LIGHT_IR,
> +		.address = 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 = D2_L_REG,
> +		.event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH,
> +					 IIO_EV_DIR_RISING),
> +	},
> +	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_preenable(struct iio_dev *indio_dev)
> +{
> +	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
> +	size_t d_size = 0;
> +	int i, err = 0;
> +
> +	mutex_lock(&data->lock);
> +
> +	/* Enable triggers according to the scan_mask */
> +	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);
> +			if (err < 0)
> +				goto error_ret;
> +			break;
> +		case GP2AP020A00F_SCAN_MODE_LIGHT_IR:
> +			err = gp2ap020a00f_exec_cmd(data,
> +					GP2AP020A00F_CMD_TRIGGER_IR_EN);
> +			if (err < 0)
> +				goto error_ret;
> +			break;
> +		case GP2AP020A00F_SCAN_MODE_PROXIMITY:
> +			err = gp2ap020a00f_exec_cmd(data,
> +					GP2AP020A00F_CMD_TRIGGER_PROX_EN);
> +			if (err < 0)
> +				goto error_ret;
> +			break;
> +		}
> +		d_size += 2;
> +	}
> +
> +	if (indio_dev->scan_timestamp)
> +		d_size += sizeof(s64);
> +
> +	data->buffer = kmalloc(d_size, GFP_KERNEL);
> +	if (data->buffer == NULL) {
> +		err = -ENOMEM;
> +		goto error_ret;
> +	}
> +
> +	err = iio_sw_buffer_preenable(indio_dev);
> +
> +error_ret:
> +	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 err;
> +
> +	mutex_lock(&data->lock);
> +
> +	if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags)) {
> +		err = gp2ap020a00f_exec_cmd(data,
> +					GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS);
> +		if (err < 0)
> +			goto error_ret;
> +	}
> +
> +	if (test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags)) {
> +		err = gp2ap020a00f_exec_cmd(data,
> +					GP2AP020A00F_CMD_TRIGGER_IR_DIS);
> +		if (err < 0)
> +			goto error_ret;
> +	}
> +
> +	if (test_bit(GP2AP020A00F_FLAG_PS_TRIGGER, &data->flags)) {
> +		err = gp2ap020a00f_exec_cmd(data,
> +					GP2AP020A00F_CMD_TRIGGER_PROX_DIS);
> +		if (err < 0)
> +			goto error_ret;
> +	}
> +
> +	err = iio_triggered_buffer_predisable(indio_dev);
> +	if (err < 0)
> +		goto error_ret;
> +
> +	kfree(data->buffer);
> +
> +error_ret:
> +	mutex_unlock(&data->lock);
> +
> +	return err;
> +}
> +
> +static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = {
> +	.preenable = &gp2ap020a00f_buffer_preenable,
> +	.postenable = &iio_triggered_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;
> +	int err;
> +
> +	indio_dev = iio_device_alloc(sizeof(*data));
> +	if (indio_dev == NULL) {
> +		err = -ENOMEM;
> +		goto error_alloc;
> +	}
> +
> +	data = iio_priv(indio_dev);
> +
> +	data->vled_reg = devm_regulator_get(&client->dev, "vled");
> +	if (IS_ERR(data->vled_reg)) {
> +		err = PTR_ERR(data->vled_reg);
> +		goto error_regulator_get;
> +	}
> +
> +	err = regulator_enable(data->vled_reg);
> +	if (err)
> +		goto error_free_data;
> +
> +	i2c_set_clientdata(client, indio_dev);
> +
> +	data->client = client;
> +	data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN;
> +
> +	init_waitqueue_head(&data->data_ready_queue);
> +
> +	err = gp2ap020a00f_setup(data);
> +	if (err < 0)
> +		goto error_free_data;
> +
> +	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_free_data;
> +
> +	/* Allocate trigger */
> +	data->trig = iio_trigger_alloc("%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;
> +	}
> +
> +	err = request_threaded_irq(client->irq,
> +				   NULL,
> +				   &gp2ap020a00f_event_handler,
> +				   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> +				   "gp2ap020a00f_event",
> +				   indio_dev);
> +	if (err < 0) {
> +		dev_err(&indio_dev->dev, "Irq request failed.\n");
> +		goto error_uninit_buffer;
> +	}
> +
> +	data->trig->private_data = indio_dev;
> +	data->trig->ops = &gp2ap020a00f_trigger_ops;
> +	data->trig->dev.parent = &data->client->dev;
> +
> +	err = iio_trigger_register(data->trig);
> +	if (err < 0) {
> +		dev_err(&indio_dev->dev, "Failed to register iio trigger.\n");
> +		goto error_free_irq;
> +	}
> +
> +	indio_dev->trig = data->trig;
> +
> +	err = iio_device_register(indio_dev);
> +	if (err < 0)
> +		goto error_free_irq;
> +
> +	return 0;
> +
> +error_free_irq:
> +	free_irq(client->irq, indio_dev);
> +error_uninit_buffer:
> +	iio_triggered_buffer_cleanup(indio_dev);
> +error_free_data:
> +	regulator_disable(data->vled_reg);
> +error_regulator_get:
> +	iio_device_free(indio_dev);
> +error_alloc:
> +	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);
> +
> +	iio_device_unregister(indio_dev);
> +	iio_triggered_buffer_cleanup(indio_dev);
> +	regulator_disable(data->vled_reg);
> +	free_irq(client->irq, indio_dev);
> +	iio_device_free(indio_dev);
> +
> +	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@xxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Sharp GP2AP020A00F I2C Proximity/Opto sensor driver");
> +MODULE_LICENSE("GPL v2");
> 

-- 

Peter Meerwald
+43-664-2444418 (mobile)
--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux