[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
light_ir and proximity. It also supports triggered buffer,
high and low ambient light threshold event and proximity
detection event.

Signed-off-by: Jacek Anaszewski <j.anaszewski@xxxxxxxxxxx>
Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
---
 .../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)
+
+#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]);
+		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);
+	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");
-- 
1.7.5.4

--
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