[PATCH v3 1/1] iio:light: Avago APDS9930 ALS and proximity sensor

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

 



Added IIO driver for Avago's APDS9930 ALS and proximity sensor on top of
APDS9300, which offers ALS suuport.

It offers ACPI/DT support, irq and event handling, raw readings for both
ALS and proximity.

Datasheet for this device can be found at:
	http://www.avagotech.com/docs/AV02-3190EN

Signed-off-by: Cristina Ciocan <cristina.ciocan@xxxxxxxxx>
---
 drivers/iio/light/apds9300.c | 1361 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 1248 insertions(+), 113 deletions(-)

diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c
index 9ddde0c..68f8c78 100644
--- a/drivers/iio/light/apds9300.c
+++ b/drivers/iio/light/apds9300.c
@@ -1,7 +1,9 @@
 /*
- * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor
+ * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor and APDS9930
+ * ambient light and proximity sensor
  *
  * Copyright 2013 Oleksandr Kravchenko <o.v.kravchenko@xxxxxxxxxxxxxxx>
+ * Copyright 2015 Intel
  *
  * 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
@@ -15,23 +17,45 @@
 #include <linux/err.h>
 #include <linux/mutex.h>
 #include <linux/interrupt.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 #include <linux/iio/events.h>

-#define APDS9300_DRV_NAME "apds9300"
-#define APDS9300_IRQ_NAME "apds9300_event"
+#include <linux/gpio/consumer.h>
+
+enum {
+	APDS9300_ID,
+	APDS9930_ID,
+};
+
+#define APDS9300_DRV_NAME	"apds9300"
+#define APDS9300_IRQ_NAME	"apds9300_event"
+#define APDS9300_GPIO_NAME	"apds9300_gpio"
+
+#define APDS9930_INIT_SLEEP	5 /* sleep for 5 ms before issuing commands */

 /* Command register bits */
 #define APDS9300_CMD	BIT(7) /* Select command register. Must write as 1 */
 #define APDS9300_WORD	BIT(5) /* I2C write/read: if 1 word, if 0 byte */
 #define APDS9300_CLEAR	BIT(6) /* Interrupt clear. Clears pending interrupt */

-/* Register set */
+/*
+ * Generic naming for the register that has the power on/off bits. Both APDS9300
+ * and APDS9930 are powered on/off by setting bits at address 0x0, but the
+ * registers are in naming and other bits meaning, thus set a generic naming for
+ * power purposes.
+ */
+#define APDS9300_POWER_ADDR	0x00
+
+/* Register set for APDS9300 */
 #define APDS9300_CONTROL	0x00 /* Control of basic functions */
 #define APDS9300_THRESHLOWLOW	0x02 /* Low byte of low interrupt threshold */
 #define APDS9300_THRESHHIGHLOW	0x04 /* Low byte of high interrupt threshold */
 #define APDS9300_INTERRUPT	0x06 /* Interrupt control */
+#define APDS9300_ID_REG		0x0a /* Part number */
 #define APDS9300_DATA0LOW	0x0c /* Low byte of ADC channel 0 */
 #define APDS9300_DATA1LOW	0x0e /* Low byte of ADC channel 1 */

@@ -39,20 +63,260 @@
 #define APDS9300_POWER_ON	0x03
 #define APDS9300_POWER_OFF	0x00

-/* Interrupts */
+/* Interrupt settings for APDS9300 */
 #define APDS9300_INTR_ENABLE	0x10
-/* Interrupt Persist Function: Any value outside of threshold range */
+/*
+ * Interrupt Persist Function for APDS9300 : Any value outside of threshold
+ * range
+ */
 #define APDS9300_THRESH_INTR	0x01

 #define APDS9300_THRESH_MAX	0xffff /* Max threshold value */

+/* Register set for APDS9930 (rw = read/write, r = read, w = write) */
+#define APDS9930_ENABLE_REG	0x00	/* rw-Enable of states and interrupts */
+#define APDS9930_ATIME_REG	0x01	/* rw-ALS ADC time*/
+#define APDS9930_PTIME_REG	0x02	/* rw-Proximity ADC time */
+#define APDS9930_WTIME_REG	0x03	/* rw-Wait time */
+#define APDS9930_AILTL_REG	0x04	/*
+					 * rw-ALS interrupt low threshold low
+					 * byte
+					 */
+#define APDS9930_AIHTL_REG	0x06	/*
+					 * rw-ALS interrupt high threshold low
+					 * byte
+					 */
+#define APDS9930_PILTL_REG	0x08	/*
+					 * rw-Proximity interrupt low threshold
+					 * low byte
+					 */
+#define APDS9930_PIHTL_REG	0x0A	/*
+					 * rw-Proximity interrupt high threshold
+					 * low byte
+					 */
+#define APDS9930_PERS_REG	0x0C	/* rw-Interrupt persistence filters */
+#define APDS9930_CONFIG_REG	0x0D	/* rw-Configuration */
+#define APDS9930_PPULSE_REG	0x0E	/* rw-Proximity pulse control */
+#define APDS9930_CONTROL_REG	0x0F	/* rw-Gain control register */
+#define APDS9930_ID_REG		0x12	/* r-Device ID */
+#define APDS9930_STATUS_REG	0x13	/* r-Device status */
+#define APDS9930_DATA0LOW	0x14	/* r-Ch0 ADC low data register */
+#define APDS9930_CDATAH_REG	0x15	/* r-Ch0 ADC high data register */
+#define APDS9930_DATA1LOW	0x16	/* r-Ch1 ADC low data register */
+#define APDS9930_IRDATAH_REG	0x17	/* r-Ch1 ADC high data register */
+#define APDS9930_PDATAL_REG	0x18	/* r-Proximity ADC low data register */
+#define APDS9930_POFFSET_REG	0x1E	/* rw-Proximity offset register */
+
+/* Useful bits per register for APDS9930 */
+
+/* Command register */
+#define APDS9930_CMD_TYPE_SPECIAL_FUNC	GENMASK(6, 5)	/* Special function */
+#define APDS9930_CMD_TYPE_PS		0x5	/* Proximity interrupt clear */
+#define APDS9930_CMD_TYPE_ALS		0x6	/* ALS interrupt clear */
+#define APDS9930_CMD_TYPE_BOTH		0x7	/* Both interrupt clear */
+
+/* Enable register for APDS9930 */
+#define APDS9930_SAI	BIT(6)	/* Sleep after interrupt */
+#define APDS9930_PIEN	BIT(5)	/* Proximity interrupt enable */
+#define APDS9930_AIEN	BIT(4)	/* ALS interrupt enable */
+#define APDS9930_WEN	BIT(3)	/* Wait enable */
+#define APDS9930_PEN	BIT(2)	/* Proximity enable */
+#define APDS9930_AEN	BIT(1)	/* ALS enable */
+#define APDS9930_PON	BIT(0)	/* Power ON */
+
+/* Persistence register for APDS9930 */
+#define APDS9930_PPERS_SHIFT	4	/* Proximity interrupt persistence */
+#define APDS9930_APERS_SHIFT	0	/* Interrupt persistence */
+
+/* Configuration register for APDS9930 */
+#define APDS9930_AGL_SHIFT	2	/* ALS gain level */
+#define APDS9930_WLONG_SHIFT	1	/* Wait long */
+#define APDS9930_PDL_SHIFT	0	/* Proximity drive level */
+
+/* Control register for APDS9930 */
+#define APDS9930_PDRIVE_SHIFT	6	/* LED drive strength */
+#define APDS9930_PDIODE_SHIFT	4	/* Proximity diode select */
+#define APDS9930_PGAIN_SHIFT	2	/* Proximity gain control */
+#define APDS9930_AGAIN_SHIFT	0	/* ALS gain control */
+
+/* Device ID - possible values for APDS9930 */
+#define APDS9930_DEVICE_ID	0x39
+
+/* Status register for APDS9930 */
+#define APDS9930_PSAT	BIT(6)	/* Proximity saturation */
+#define APDS9930_PINT	BIT(5)	/* Proximity interrupt */
+#define APDS9930_AINT	BIT(4)	/* ALS interrupt */
+#define APDS9930_PVALID	BIT(1)	/* PS valid */
+#define APDS9930_AVALID	BIT(0)	/* ALS valid */
+
+/* APDS9930 enabling and disabling register values */
+#define APDS9930_DISABLE_ALL	0	/* Disable and powerdown */
+#define APDS9930_ENABLE_ALL	0x37	/* Set all ALS & PS bits and power on */
+
+/* Default values for registers content for APDS9930 */
+#define APDS9930_DEF_ATIME	0xdb	/*
+					 * 50 ms - ALSIT value in order to
+					 * reject 50/60 Hz ripple; if higher
+					 * resolution is needed, increase ALSIT
+					 * with mutiples of 50
+					 */
+#define APDS9930_DEF_PTIME	0xff	/* 2.7 ms - min prox integration time */
+#define APDS9930_DEF_WTIME	0xff	/* 2.7 ms - min wait time */
+#define APDS9930_DEF_PPULSE	8	/* Min prox pulse count */
+#define APDS9930_DEF_PDRIVE	0	/* 100 mA of LED power */
+#define APDS9930_DEF_PDIODE	2	/* Ch1 diode (shifted value) */
+#define APDS9930_DEF_PGAIN	2	/* 4 x proximity gain */
+#define APDS9930_DEF_AGAIN	2	/* 16 x ALS gain */
+#define APDS9930_DEF_WEN	8	/* Enable wait */
+#define APDS9930_DEF_PEN	4	/* Enable proximity */
+#define APDS9930_DEF_AEN	2	/* Enable ALS */
+#define APDS9930_DEF_PON	1	/* Power ON */
+#define APDS9930_DEF_APERS	3	/*
+					 * Consecutive exceeding threshold
+					 * cycles to trigger ALS interrupt
+					 */
+#define APDS9930_DEF_PPERS	3	/*
+					 * Consecutive PS execeeding threshold
+					 * cycles to trigger PS interrupt
+					 */
+
+/*
+ * Interrupt threshold defaults - setting low to maximum will trigger a first
+ * interrupt to allow us to read the data registers status and properly set a
+ * threshold value to match the current environment.
+ *
+ * The default high threshold is set only for consistency, since the registers
+ * are checked against in order: first if the value of CDATA/PDATA is above LOW;
+ * if so, trigger interrupt and do not check the HIGH threshold.
+ */
+#define APDS9930_DEF_ALS_THRESH_LOW	0xffff
+#define APDS9930_DEF_ALS_THRESH_HIGH	0x0
+#define APDS9930_DEF_PS_THRESH_LOW	1023
+#define APDS9930_DEF_PS_THRESH_HIGH	0
+
+#define APDS9930_MIN_PS_THRESH		0	/* No object near the sensor */
+#define APDS9930_MAX_PS_THRESH		1023	/* Object in close proximity */
+#define APDS9930_DETECTION_PS_THRESH	600	/* Object detected near-by */
+
+/* Default, open air, coefficients (for Lux computation) */
+#define APDS9930_DEF_DF			52	/* Device factor */
+/*
+ * Material-depending coefficients (set to open air values). They are scaled for
+ * computation purposes by 100 (as stored in APDS9930_COEF_SCALE).
+ */
+#define APDS9930_DEF_LUX_DIVISION_SCALE	10000
+#define APDS9930_DEF_GA			49
+#define APDS9930_DEF_B			186
+#define APDS9930_DEF_C			74
+#define APDS9930_DEF_D			129
+#define APDS9930_COEF_SCALE		100
+
+/* Possible ALS gain values (with AGL cleared) */
+static const int apds9930_again_values[] = {1, 8, 16, 120};
+#define APDS9930_MAX_AGAIN_INDEX	3
+#define APDS9930_AGL_DIVISION_SCALE	6
+
+/*
+ * Percentages from the maximum CH0 value that indicate the recorded CH0 data is
+ * too low or too high - for AGAIN adapting purposes.
+ */
+#define APDS9930_CH0_HIGH_PERCENTAGE	90
+#define APDS9930_CH0_LOW_PERCENTAGE	10
+
+/*
+ * With how much (in percentages) must the CH0 value differ from one step to
+ * another in order to consider a significant change in light.
+ */
+#define APDS9930_ALS_HYSTERESIS		20
+
+/* DT or ACPI device properties names */
+#define APDS9930_GA_PROP	"avago,ga"
+#define APDS9930_COEF_B_PROP	"avago,coeff-B"
+#define APDS9930_COEF_C_PROP	"avago,coeff-C"
+#define APDS9930_COEF_D_PROP	"avago,coeff-D"
+#define APDS9930_DF_PROP	"avago,df"
+#define APDS9930_AGAIN_PROP	"avago,als-gain"
+#define APDS9930_ATIME_PROP	"avago,atime"
+#define APDS9930_PDRIVE_PROP	"avago,pdrive"
+#define APDS9930_PPULSE_PROP	"avago,ppcount"
+
+struct apds9930_coefficients {
+	/*
+	 * GA, B, C and D coefficients are scaled by 100 for computational
+	 * purposes
+	 */
+	int ga;
+	/*
+	 * This is 1, but needs to be set to a scaled value, thus if
+	 * we decide to change the scale, this coefficient must also
+	 * be changed.
+	 */
+	int coef_a;
+	int coef_b;
+	int coef_c;
+	int coef_d;
+	int df;
+};
+
+struct apds9930_platform_data {
+	/* Glass-influenced factors */
+	struct apds9930_coefficients coefs;
+
+	/* ALS platform data */
+	u8 again;	/* AGAIN value (index in apds9930_again_values[]) */
+	u8 atime;	/* ALS integration time */
+
+	/* Proximity platform data */
+	u8 pdrive;
+	u8 ppulse;
+};
+
+struct apds9300_threshold {
+	u16 low;
+	u16 high;
+};
+
+struct apds9930_data {
+	u8	alsit;
+	u16	ch0_max;
+	bool	agl_enabled;
+	bool	ps_enabled;
+	bool	ps_intr_enabled;
+	struct apds9300_threshold ps_thresh;
+};
+
+enum apds9300_intr {
+	ALS_INTR,
+	PS_INTR,
+};
+
 struct apds9300_data {
 	struct i2c_client *client;
+
+	/* Protect access to device data */
 	struct mutex mutex;
-	int power_state;
-	int thresh_low;
-	int thresh_hi;
-	int intr_en;
+
+	bool	power_state;
+	bool	als_enabled;
+	bool	als_intr_enabled;
+	struct apds9300_threshold als_thresh;
+
+	/* Register set */
+	u8	ch0_low_reg;
+	u8	ch1_low_reg;
+	u8	power_on_value;
+
+	/* Function pointers set */
+	unsigned long	(*calculate_lux)(struct apds9300_data *data,
+					 u16 ch0, u16 ch1);
+	int		(*set_intr_state)(struct apds9300_data *data,
+					  enum apds9300_intr intr,
+					  int state);
+	irq_handler_t	irq_handler;
+
+	/* APDS9930 platform and chip specific specific data */
+	struct apds9930_platform_data	*platform_data;
+	struct apds9930_data		*aux_data;
 };

 /* Lux calculation */
@@ -65,7 +329,9 @@ static const u16 apds9300_lux_ratio[] = {
 	347, 358, 368, 379, 390, 400,
 };

-static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1)
+static unsigned long apds9300_calculate_lux(
+			__attribute__((unused)) struct apds9300_data *data,
+			u16 ch0, u16 ch1)
 {
 	unsigned long lux, tmp;

@@ -90,6 +356,86 @@ static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1)
 	return lux / 100000;
 }

+/* Computes the ALS gain value for the next step and updates the current one */
+static void apds9930_update_als_gain(struct apds9300_data *data, u16 ch0)
+{
+	int current_index, next_index, err;
+	struct apds9930_data *apds9930 = data->aux_data;
+
+	/* Compute the value for the next measurement */
+	current_index	= data->platform_data->again;
+	next_index	= data->platform_data->again;
+
+	/* CH0 data too high, try to lower the ALS gain if possible */
+	if (ch0 >= (apds9930->ch0_max * APDS9930_CH0_HIGH_PERCENTAGE) / 100) {
+		if (next_index == 0 && !(apds9930->agl_enabled)) {
+			err = i2c_smbus_write_byte_data(
+					data->client,
+					APDS9300_CMD | APDS9930_CONFIG_REG,
+					1 << APDS9930_AGL_SHIFT);
+			if (!err)
+				apds9930->agl_enabled = true;
+		} else if (next_index > 0) {
+			next_index--;
+		}
+	}
+
+	/* CH0 data too low, try to increase the ALS gain if possible */
+	else if (ch0 <= (apds9930->ch0_max * APDS9930_CH0_LOW_PERCENTAGE)
+		 / 100) {
+		if (apds9930->agl_enabled) {
+			err = i2c_smbus_write_byte_data(
+					data->client,
+					APDS9300_CMD | APDS9930_CONFIG_REG,
+					0);
+			if (!err)
+				apds9930->agl_enabled = false;
+		} else if (next_index < APDS9930_MAX_AGAIN_INDEX) {
+			next_index++;
+		}
+	}
+
+	if (next_index != current_index) {
+		/* Update data's index value */
+		data->platform_data->again = next_index;
+
+		/* Update AGAIN for the next reading */
+		i2c_smbus_write_byte_data(
+			data->client,
+			APDS9300_CMD | APDS9930_CONTROL_REG,
+			data->platform_data->again << APDS9930_AGAIN_SHIFT |
+			APDS9930_DEF_PGAIN << APDS9930_PGAIN_SHIFT |
+			APDS9930_DEF_PDIODE << APDS9930_PDIODE_SHIFT |
+			data->platform_data->pdrive << APDS9930_PDRIVE_SHIFT);
+	}
+}
+
+static unsigned long apds9930_calculate_lux(struct apds9300_data *data,
+					    u16 ch0, u16 ch1)
+{
+	long int iac1, iac2, alsit_val, again_val, tmp_iac;
+	unsigned long int iac, lux;
+	struct apds9930_coefficients cf;
+
+	/* Lux equation from datasheet */
+	cf		= data->platform_data->coefs;
+	iac1		= cf.coef_a * ch0 - cf.coef_b * ch1;
+	iac2		= cf.coef_c * ch0 - cf.coef_d * ch1;
+	tmp_iac         = max(iac1, iac2);
+	iac             = (tmp_iac < 0) ? 0 : (unsigned long)tmp_iac;
+	alsit_val	= (int)(data->aux_data->alsit);
+	again_val	= apds9930_again_values[data->platform_data->again];
+	lux		= (iac * cf.ga * cf.df) /
+				(alsit_val * again_val *
+				 APDS9930_DEF_LUX_DIVISION_SCALE);
+
+	/* Update ALS gain */
+	apds9930_update_als_gain(data, ch0);
+
+	return data->aux_data->agl_enabled ?
+		(APDS9930_AGL_DIVISION_SCALE * lux) : lux;
+}
+
 static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number)
 {
 	int ret;
@@ -98,8 +444,8 @@ static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number)
 	if (!data->power_state)
 		return -EBUSY;

-	/* Select ADC0 or ADC1 data register */
-	flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW;
+	/* Select ADC0 or ADC1 ALS data register */
+	flags |= adc_number ? data->ch1_low_reg : data->ch0_low_reg;

 	ret = i2c_smbus_read_word_data(data->client, flags);
 	if (ret < 0)
@@ -125,7 +471,7 @@ static int apds9300_set_thresh_low(struct apds9300_data *data, int value)
 		dev_err(&data->client->dev, "failed to set thresh_low\n");
 		return ret;
 	}
-	data->thresh_low = value;
+	data->als_thresh.low = value;

 	return 0;
 }
@@ -146,12 +492,15 @@ static int apds9300_set_thresh_hi(struct apds9300_data *data, int value)
 		dev_err(&data->client->dev, "failed to set thresh_hi\n");
 		return ret;
 	}
-	data->thresh_hi = value;
+	data->als_thresh.high = value;

 	return 0;
 }

-static int apds9300_set_intr_state(struct apds9300_data *data, int state)
+static int apds9300_set_intr_state(
+			struct apds9300_data *data,
+			__attribute__((unused)) enum apds9300_intr intr,
+			int state)
 {
 	int ret;
 	u8 cmd;
@@ -167,27 +516,107 @@ static int apds9300_set_intr_state(struct apds9300_data *data, int state)
 			"failed to set interrupt state %d\n", state);
 		return ret;
 	}
-	data->intr_en = state;
+	data->als_intr_enabled = state;

 	return 0;
 }

+static int apds9930_set_intr_state(struct apds9300_data *data,
+				   enum apds9300_intr intr,
+				   int state)
+{
+	int ret, enable_reg;
+	struct i2c_client *client = data->client;
+
+	ret = 0;
+
+	mutex_lock(&data->mutex);
+	state = !!state;
+	switch (intr) {
+	case ALS_INTR:
+		/* Check if the interrupt isn't already in the desired state */
+		if (state == data->als_intr_enabled)
+			break;
+
+		enable_reg = i2c_smbus_read_byte_data(
+					client,
+					APDS9300_CMD | APDS9930_ENABLE_REG);
+		if (enable_reg < 0) {
+			ret = enable_reg;
+			break;
+		}
+		if (state)
+			enable_reg |= APDS9930_AIEN;	/* enable */
+		else
+			enable_reg &= ~APDS9930_AIEN;	/* disable */
+
+		ret = i2c_smbus_write_byte_data(
+					data->client,
+					APDS9300_CMD | APDS9930_ENABLE_REG,
+					enable_reg);
+		if (!ret)
+			data->als_intr_enabled = state;
+		break;
+	case PS_INTR:
+		if (!data->aux_data) {
+			ret = -EINVAL;
+			break;
+		}
+
+		/* Check if the interrupt isn't already in the desired state */
+		if (state == data->aux_data->ps_intr_enabled)
+			break;
+
+		enable_reg = i2c_smbus_read_byte_data(
+					client,
+					APDS9300_CMD | APDS9930_ENABLE_REG);
+		if (enable_reg < 0) {
+			ret = enable_reg;
+			break;
+		}
+		if (state)
+			enable_reg |= APDS9930_PIEN;	/* enable */
+		else
+			enable_reg &= ~APDS9930_PIEN;	/* disable */
+
+		ret = i2c_smbus_write_byte_data(
+					data->client,
+					APDS9300_CMD | APDS9930_ENABLE_REG,
+					enable_reg);
+		if (!ret)
+			data->aux_data->ps_intr_enabled = state;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
 static int apds9300_set_power_state(struct apds9300_data *data, int state)
 {
 	int ret;
 	u8 cmd;

-	cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF;
+	mutex_lock(&data->mutex);
+	cmd = state ? data->power_on_value : APDS9300_POWER_OFF;
 	ret = i2c_smbus_write_byte_data(data->client,
-			APDS9300_CONTROL | APDS9300_CMD, cmd);
+					APDS9300_POWER_ADDR | APDS9300_CMD,
+					cmd);
 	if (ret) {
 		dev_err(&data->client->dev,
 			"failed to set power state %d\n", state);
-		return ret;
+		goto out;
 	}
+	/* For APDS9300, power on also means ALS enabled, so mark them both */
+	if (!data->aux_data)
+		data->als_enabled = state;
 	data->power_state = state;
-
-	return 0;
+out:
+	mutex_unlock(&data->mutex);
+	return ret;
 }

 static void apds9300_clear_intr(struct apds9300_data *data)
@@ -199,32 +628,474 @@ static void apds9300_clear_intr(struct apds9300_data *data)
 		dev_err(&data->client->dev, "failed to clear interrupt\n");
 }

-static int apds9300_chip_init(struct apds9300_data *data)
+static irqreturn_t apds9300_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *dev_info = private;
+	struct apds9300_data *data = iio_priv(dev_info);
+
+	iio_push_event(dev_info,
+		       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+
+	apds9300_clear_intr(data);
+
+	return IRQ_HANDLED;
+}
+
+/* Determine what has triggered the interrupt and clear it accordingly */
+static int apds9930_interrupt_clear(struct apds9300_data *data, u8 intr_status)
+{
+	u8 reg = APDS9300_CMD | APDS9930_CMD_TYPE_SPECIAL_FUNC;
+
+	switch (intr_status & (APDS9930_AINT | APDS9930_PINT)) {
+	case APDS9930_AINT:
+		reg |= APDS9930_CMD_TYPE_ALS;
+		break;
+	case APDS9930_PINT:
+		reg |= APDS9930_CMD_TYPE_PS;
+		break;
+	case (APDS9930_AINT & APDS9930_PINT):
+		reg |= APDS9930_CMD_TYPE_BOTH;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return i2c_smbus_read_byte_data(data->client, reg);
+}
+
+/*
+ * Update thresholds so as to generate interrupts when the CH0 data changes
+ * significantly since the last update; significantly is quantified by the
+ * hysteresis factor: if the ALS state changes with more than hysteresis %,
+ * update the thresholds.
+ */
+static void apds9930_update_als_thresholds(struct apds9300_data *data, u16 ch0)
+{
+	data->als_thresh.low	= (ch0 * (100 - APDS9930_ALS_HYSTERESIS)) / 100;
+	data->als_thresh.high	=
+		min((u16)((ch0 * (100 + APDS9930_ALS_HYSTERESIS)) / 100),
+		    data->aux_data->ch0_max);
+
+	i2c_smbus_write_word_data(
+			data->client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_AILTL_REG,
+			data->als_thresh.low);
+	i2c_smbus_write_word_data(
+			data->client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_AIHTL_REG,
+			data->als_thresh.high);
+}
+
+static void apds9930_update_ps_thresholds(struct apds9300_data *data, u16 ps)
+{
+	struct apds9930_data *apds9930 = data->aux_data;
+
+	if (ps <= apds9930->ps_thresh.low) {
+		/* Near to far => set limits to detect when it comes close */
+		apds9930->ps_thresh.low		= APDS9930_MIN_PS_THRESH;
+		apds9930->ps_thresh.high	= APDS9930_DETECTION_PS_THRESH;
+	} else if (ps >= apds9930->ps_thresh.high) {
+		/* Far to near => set limits to detect when it goes further */
+		apds9930->ps_thresh.low		= APDS9930_DETECTION_PS_THRESH;
+		apds9930->ps_thresh.high	= APDS9930_MAX_PS_THRESH;
+	}
+
+	/* Update thresholds */
+	i2c_smbus_write_word_data(
+			data->client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_PILTL_REG,
+			apds9930->ps_thresh.low);
+	i2c_smbus_write_word_data(
+			data->client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_PIHTL_REG,
+			apds9930->ps_thresh.high);
+}
+
+static irqreturn_t apds9930_interrupt_handler(int irq, void *private_data)
+{
+	struct iio_dev *indio_dev	= private_data;
+	struct apds9300_data *data	= iio_priv(indio_dev);
+	u8 status, enable_reg;
+	u16 ch0, ps_data;
+	int ret;
+
+	/* Disable ADCs converters while processing data */
+	enable_reg = i2c_smbus_read_byte_data(
+					data->client,
+					APDS9300_CMD | APDS9930_ENABLE_REG);
+	if (enable_reg < 0)
+		return IRQ_HANDLED;
+	ret = i2c_smbus_write_byte_data(data->client,
+					APDS9300_CMD | APDS9930_ENABLE_REG, 1);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	/* Read status register to see what caused the interrupt */
+	status = i2c_smbus_read_byte_data(data->client,
+					  APDS9300_CMD | APDS9930_STATUS_REG);
+	if (status < 0)
+		goto err;
+
+	/* Push event to userspace */
+	if (status & APDS9930_AINT) {
+		/* Clear interrupt */
+		apds9930_interrupt_clear(data, status);
+
+		iio_push_event(indio_dev,
+			       IIO_MOD_EVENT_CODE(IIO_INTENSITY,
+						  0,
+						  IIO_MOD_LIGHT_BOTH,
+						  IIO_EV_TYPE_THRESH,
+						  IIO_EV_DIR_EITHER),
+			       iio_get_time_ns());
+
+		ch0 = i2c_smbus_read_word_data(data->client,
+					       APDS9300_CMD | APDS9300_WORD |
+					       APDS9930_DATA0LOW);
+		if (ch0 < 0)
+			goto err;
+
+		apds9930_update_als_gain(data, ch0);
+
+		/* Update ALS thresholds to environment */
+		apds9930_update_als_thresholds(data, ch0);
+	}
+
+	if (status & APDS9930_PINT) {
+		/* Clear interrupt */
+		apds9930_interrupt_clear(data, status);
+
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY,
+						    0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns());
+
+		ps_data = i2c_smbus_read_word_data(data->client,
+						   APDS9300_CMD |
+						   APDS9300_WORD |
+						   APDS9930_PDATAL_REG);
+		if (ps_data < 0)
+			goto err;
+
+		apds9930_update_ps_thresholds(data, ps_data);
+	}
+
+err:
+	/* Re-enable converters */
+	i2c_smbus_write_byte_data(data->client,
+				  APDS9300_CMD | APDS9930_ENABLE_REG,
+				  enable_reg);
+
+	return IRQ_HANDLED;
+}
+
+static void apds9300_set_registers(struct apds9300_data *data,
+				   u8 ch0_low_reg,
+				   u8 ch1_low_reg,
+				   u8 power_on_value)
+{
+	data->ch0_low_reg	= ch0_low_reg;
+	data->ch1_low_reg	= ch1_low_reg;
+	data->power_on_value	= power_on_value;
+}
+
+static void apds9300_set_functions(
+		struct apds9300_data *data,
+		unsigned long (*calculate_lux)(struct apds9300_data *data,
+					       u16 ch0, u16 ch1),
+		int (*set_intr_state)(struct apds9300_data *data,
+				      enum apds9300_intr intr,
+				      int state),
+		irq_handler_t irq_handler)
+{
+	data->calculate_lux	= calculate_lux;
+	data->set_intr_state	= set_intr_state;
+	data->irq_handler	= irq_handler;
+}
+
+static void apds9300_init(struct apds9300_data *data)
+{
+	apds9300_set_functions(data,
+			       apds9300_calculate_lux,
+			       apds9300_set_intr_state,
+			       apds9300_interrupt_handler);
+}
+
+/*
+ * Set the coefficients for APDS9930 to those specified in DT/ACPI if they
+ * exist, otherwise to default values.
+ */
+static void apds9930_set_platform_data(struct apds9300_data *data)
+{
+	struct apds9930_coefficients defaults = {
+		.ga	= APDS9930_DEF_GA,
+		.coef_a	= APDS9930_COEF_SCALE,
+		.coef_b	= APDS9930_DEF_B,
+		.coef_c	= APDS9930_DEF_C,
+		.coef_d	= APDS9930_DEF_D,
+		.df	= APDS9930_DEF_DF,
+	};
+	struct device *dev = &data->client->dev;
+
+	/*
+	 * Look for device properties and set them to proper value or to
+	 * default.
+	 */
+	if (device_property_read_u32(dev, APDS9930_GA_PROP,
+				     &data->platform_data->coefs.ga))
+		data->platform_data->coefs.ga = defaults.ga;
+
+	if (device_property_read_u32(dev, APDS9930_DF_PROP,
+				     &data->platform_data->coefs.df))
+		data->platform_data->coefs.df = defaults.df;
+
+	if (device_property_read_u32(dev, APDS9930_COEF_B_PROP,
+				     &data->platform_data->coefs.coef_b) ||
+	    device_property_read_u32(dev, APDS9930_COEF_C_PROP,
+				     &data->platform_data->coefs.coef_c) ||
+	    device_property_read_u32(dev, APDS9930_COEF_D_PROP,
+				     &data->platform_data->coefs.coef_d)) {
+		data->platform_data->coefs.coef_b	= defaults.coef_b;
+		data->platform_data->coefs.coef_c	= defaults.coef_c;
+		data->platform_data->coefs.coef_d	= defaults.coef_d;
+	}
+	data->platform_data->coefs.coef_a = defaults.coef_a;
+
+	if (device_property_read_u8(dev, APDS9930_ATIME_PROP,
+				    &data->platform_data->atime))
+		data->platform_data->atime = APDS9930_DEF_ATIME;
+
+	if (device_property_read_u8(dev, APDS9930_AGAIN_PROP,
+				    &data->platform_data->again))
+		data->platform_data->again = APDS9930_DEF_AGAIN;
+
+	/*
+	 * We expect for the AGAIN value to be the one in the register (0, 1, 2
+	 * or 3). If we do find device property AGAIN, but is not valid, fall
+	 * back to the default one.
+	 */
+	if (data->platform_data->again > APDS9930_MAX_AGAIN_INDEX)
+		data->platform_data->again = APDS9930_DEF_AGAIN;
+
+	if (device_property_read_u8(dev, APDS9930_PDRIVE_PROP,
+				    &data->platform_data->pdrive))
+		data->platform_data->pdrive = APDS9930_DEF_PDRIVE;
+
+	if (device_property_read_u8(dev, APDS9930_PPULSE_PROP,
+				    &data->platform_data->ppulse))
+		data->platform_data->ppulse = APDS9930_DEF_PPULSE;
+}
+
+/* Basic chip initialization, as described in the datasheet */
+static int apds9930_chip_registers_init(struct apds9300_data *data)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	/* Set timing registers default values (minimum) */
+	ret = i2c_smbus_write_byte_data(client,
+					APDS9300_CMD | APDS9930_ATIME_REG,
+					data->platform_data->atime);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(client,
+					APDS9300_CMD | APDS9930_PTIME_REG,
+					APDS9930_DEF_PTIME);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_byte_data(client,
+					APDS9300_CMD | APDS9930_WTIME_REG,
+					APDS9930_DEF_WTIME);
+	if (ret < 0)
+		return ret;
+
+	/* Interrupt threshold default settings */
+	ret = i2c_smbus_write_word_data(
+			client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_AILTL_REG,
+			APDS9930_DEF_ALS_THRESH_LOW);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_word_data(
+			client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_AIHTL_REG,
+			APDS9930_DEF_ALS_THRESH_HIGH);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_word_data(
+			client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_PILTL_REG,
+			APDS9930_DEF_PS_THRESH_LOW);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_word_data(
+			client,
+			APDS9300_CMD | APDS9300_WORD | APDS9930_PIHTL_REG,
+			APDS9930_DEF_PS_THRESH_HIGH);
+	if (ret < 0)
+		return ret;
+
+	/* Set persistence filters to default values */
+	ret = i2c_smbus_write_byte_data(
+				client,
+				APDS9300_CMD | APDS9930_PERS_REG,
+				APDS9930_DEF_APERS << APDS9930_APERS_SHIFT |
+				APDS9930_DEF_PPERS << APDS9930_PPERS_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	/* Reset the configuration register (do not wait long) */
+	ret = i2c_smbus_write_byte_data(client,
+					APDS9300_CMD | APDS9930_CONFIG_REG, 0);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Set proximity pulse count register (number of pulses to be generated
+	 * during the proximity accum state)
+	 */
+	ret = i2c_smbus_write_byte_data(client,
+					APDS9300_CMD | APDS9930_PPULSE_REG,
+					data->platform_data->ppulse);
+	if (ret < 0)
+		return ret;
+
+	/* Gain selection setting */
+	ret = i2c_smbus_write_byte_data(
+			client,
+			APDS9300_CMD | APDS9930_CONTROL_REG,
+			data->platform_data->again << APDS9930_AGAIN_SHIFT |
+			APDS9930_DEF_PGAIN << APDS9930_PGAIN_SHIFT |
+			APDS9930_DEF_PDIODE << APDS9930_PDIODE_SHIFT |
+			data->platform_data->pdrive << APDS9930_PDRIVE_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	return apds9300_set_power_state(data, 1);
+}
+
+/* ALSIT = 2.73ms * (256 - ATIME), 2.73 = 5591/(2^11) */
+static inline u8 apds9930_atime_to_alsit(u8 atime_val)
+{
+	return (u8)(((256 - (u32)atime_val) * 5591) >> 11);
+}
+
+/* Computes maximum Ch0 data when atime is the given one. */
+static inline u16 apds9930_compute_max_ch0(u8 atime_val)
+{
+	return 1024 * (256 - (u32)atime_val) - 1;
+}
+
+static int apds9930_init(struct apds9300_data *data)
+{
+	data->platform_data = kmalloc(sizeof(*data->platform_data), GFP_KERNEL);
+	if (!data->platform_data)
+		return -ENOMEM;
+	data->aux_data = kmalloc(sizeof(*data->aux_data), GFP_KERNEL);
+	if (!data->aux_data)
+		return -ENOMEM;
+
+	apds9930_set_platform_data(data);
+
+	apds9300_set_functions(data,
+			       apds9930_calculate_lux,
+			       apds9930_set_intr_state,
+			       apds9930_interrupt_handler);
+
+	/* Init APDS9930 data to default values */
+	data->aux_data->alsit		= apds9930_atime_to_alsit(
+						data->platform_data->atime);
+	data->aux_data->ch0_max		= apds9930_compute_max_ch0(
+						APDS9930_DEF_ATIME);
+	data->aux_data->agl_enabled	= false;
+	data->als_enabled		= false;
+	data->aux_data->ps_enabled	= false;
+	data->als_intr_enabled		= false;
+	data->aux_data->ps_intr_enabled	= false;
+	data->als_thresh.low		= APDS9930_DEF_ALS_THRESH_LOW;
+	data->als_thresh.high		= APDS9930_DEF_ALS_THRESH_HIGH;
+	data->aux_data->ps_thresh.low	= APDS9930_DEF_PS_THRESH_LOW;
+	data->aux_data->ps_thresh.high	= APDS9930_DEF_PS_THRESH_HIGH;
+
+	return apds9930_chip_registers_init(data);
+}
+
+static int apds9300_chip_init(struct apds9300_data *data, int *device_index)
 {
 	int ret;

-	/* Need to set power off to ensure that the chip is off */
+	/*
+	 * Try to first read the APDS9930's ID reg since this reg does not exist
+	 * in APDS9300, so if it fails we are sure we did not read another
+	 * register from the other sensor.
+	 */
+	ret = i2c_smbus_read_byte_data(data->client,
+				       APDS9300_CMD | APDS9930_ID_REG);
+	if (ret == APDS9930_DEVICE_ID) {
+		dev_info(&data->client->dev,
+			 "identified APDS9930, chip id %i\n", ret);
+		*device_index = APDS9930_ID;
+		apds9300_set_registers(data,
+				       APDS9930_DATA0LOW,
+				       APDS9930_DATA1LOW,
+				       APDS9930_ENABLE_ALL);
+
+		return apds9930_init(data);
+	}
+	if (ret > 0) {
+		dev_err(&data->client->dev,
+			"wrong chip id %i for APDS9930\n", ret);
+		return -EINVAL;
+	}
+
+	/*
+	 * If APDS9930 identification failed, try to identify APDS9300. Need to
+	 * set power off to ensure that the chip is off.
+	 */
+	apds9300_set_registers(data,
+			       APDS9300_DATA0LOW,
+			       APDS9300_DATA1LOW,
+			       APDS9300_POWER_ON);
 	ret = apds9300_set_power_state(data, 0);
 	if (ret < 0)
 		goto err;
 	/*
 	 * Probe the chip. To do so we try to power up the device and then to
-	 * read back the 0x03 code
+	 * read back the 0x03 code for the APDS9300 sensor
 	 */
 	ret = apds9300_set_power_state(data, 1);
 	if (ret < 0)
 		goto err;
 	ret = i2c_smbus_read_byte_data(data->client,
-			APDS9300_CONTROL | APDS9300_CMD);
+				       APDS9300_CMD | APDS9300_CONTROL);
 	if (ret != APDS9300_POWER_ON) {
 		ret = -ENODEV;
 		goto err;
 	}
+
+	ret = i2c_smbus_read_byte_data(data->client,
+				       APDS9300_CMD | APDS9300_ID_REG);
+	if (ret < 0)
+		dev_err(&data->client->dev,
+			"error reading both APDS9300 and APDS9930 chip ids\n");
+	else {
+		dev_info(&data->client->dev,
+			 "identified APDS9300, chip id %i", ret);
+		*device_index = APDS9300_ID;
+		apds9300_init(data);
+	}
+
 	/*
-	 * Disable interrupt to ensure thai it is doesn't enable
+	 * Disable interrupt to ensure that it is doesn't enable
 	 * i.e. after device soft reset
 	 */
-	ret = apds9300_set_intr_state(data, 0);
+	ret = data->set_intr_state(data, ALS_INTR, 0);
 	if (ret < 0)
 		goto err;

@@ -235,37 +1106,94 @@ err:
 	return ret;
 }

+/* APDS9300 IIO functions */
 static int apds9300_read_raw(struct iio_dev *indio_dev,
 		struct iio_chan_spec const *chan, int *val, int *val2,
 		long mask)
 {
 	int ch0, ch1, ret = -EINVAL;
+	u16 ch;
 	struct apds9300_data *data = iio_priv(indio_dev);

 	mutex_lock(&data->mutex);
 	switch (chan->type) {
 	case IIO_LIGHT:
-		ch0 = apds9300_get_adc_val(data, 0);
-		if (ch0 < 0) {
-			ret = ch0;
+		switch (mask) {
+		case IIO_CHAN_INFO_PROCESSED:
+			if (!data->als_enabled) {
+				ret = -ENODATA;
+				break;
+			}
+
+			ch0 = apds9300_get_adc_val(data, 0);
+			if (ch0 < 0) {
+				ret = ch0;
+				break;
+			}
+			ch1 = apds9300_get_adc_val(data, 1);
+			if (ch1 < 0) {
+				ret = ch1;
+				break;
+			}
+			*val	= data->calculate_lux(data, ch0, ch1);
+			ret	= IIO_VAL_INT;
 			break;
-		}
-		ch1 = apds9300_get_adc_val(data, 1);
-		if (ch1 < 0) {
-			ret = ch1;
+		case IIO_CHAN_INFO_ENABLE:
+			*val	= data->als_enabled;
+			ret	= IIO_VAL_INT;
+			break;
+		default:
+			ret = -EINVAL;
 			break;
 		}
-		*val = apds9300_calculate_lux(ch0, ch1);
-		ret = IIO_VAL_INT;
 		break;
 	case IIO_INTENSITY:
+		if (!data->als_enabled) {
+			ret = -ENODATA;
+			break;
+		}
+
 		ret = apds9300_get_adc_val(data, chan->channel);
 		if (ret < 0)
 			break;
 		*val = ret;
 		ret = IIO_VAL_INT;
 		break;
+	case IIO_PROXIMITY:
+		if (!data->aux_data)
+			ret = -EINVAL;
+		else
+			switch (mask) {
+			case IIO_CHAN_INFO_RAW:
+				if (!data->aux_data->ps_enabled) {
+					ret = -ENODATA;
+					break;
+				}
+
+				/* Get proximity raw value */
+				ch = i2c_smbus_read_word_data(
+						data->client,
+						APDS9300_CMD | APDS9300_WORD |
+						APDS9930_PDATAL_REG);
+				if (ch < 0) {
+					ret = ch;
+					break;
+				}
+
+				*val	= APDS9930_MAX_PS_THRESH - (int)ch;
+				ret	= IIO_VAL_INT;
+				break;
+			case IIO_CHAN_INFO_ENABLE:
+				*val	= data->aux_data->ps_enabled;
+				ret	= IIO_VAL_INT;
+				break;
+			default:
+				ret = -EINVAL;
+				break;
+			}
+		break;
 	default:
+		ret = -EINVAL;
 		break;
 	}
 	mutex_unlock(&data->mutex);
@@ -273,19 +1201,103 @@ static int apds9300_read_raw(struct iio_dev *indio_dev,
 	return ret;
 }

+/*
+ * For APDS9300, disabling the ALS means disabling the entire sensor, so power
+ * off.
+ */
+static int apds9300_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan, int val,
+			      int val2, long mask)
+{
+	struct apds9300_data *data = iio_priv(indio_dev);
+
+	if (mask != IIO_CHAN_INFO_ENABLE || chan->type != IIO_LIGHT)
+		return -EINVAL;
+
+	return apds9300_set_power_state(data, val);
+}
+
+static int apds9930_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan, int val,
+			      int val2, long mask)
+{
+	struct apds9300_data *data	= iio_priv(indio_dev);
+	struct i2c_client *client	= data->client;
+	int ret				= 0;
+	u8 enable_reg, function_reg;
+	bool *feature_enabled;
+
+	if (mask != IIO_CHAN_INFO_ENABLE)
+		return -EINVAL;
+
+	mutex_lock(&data->mutex);
+
+	if (chan->type == IIO_LIGHT) {
+		feature_enabled = &data->als_enabled;
+	} else if (chan->type == IIO_PROXIMITY && data->aux_data) {
+		feature_enabled = &data->aux_data->ps_enabled;
+	} else {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	val = !!val;
+	if (val == *feature_enabled)
+		goto out;
+
+	enable_reg = i2c_smbus_read_byte_data(
+					client,
+					APDS9300_CMD | APDS9930_ENABLE_REG);
+	if (enable_reg < 0) {
+		ret = enable_reg;
+		goto out;
+	}
+
+	function_reg = chan->type == IIO_LIGHT ? APDS9930_AEN : APDS9930_PEN;
+	if (val)
+		enable_reg |= function_reg;
+	else
+		enable_reg &= ~function_reg;
+	ret = i2c_smbus_write_byte_data(
+					client,
+					APDS9300_CMD | APDS9930_ENABLE_REG,
+					enable_reg);
+	if (ret < 0)
+		goto out;
+
+	*feature_enabled = val;
+out:
+	mutex_unlock(&data->mutex);
+	return ret;
+}
+
 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);
+	struct apds9300_threshold thresh;
+
+	switch (chan->type) {
+	case IIO_INTENSITY:
+		thresh = data->als_thresh;
+		break;
+	case IIO_PROXIMITY:
+		if (data->aux_data) {
+			thresh = data->aux_data->ps_thresh;
+			break;
+		}
+	default:
+		return -EINVAL;
+	}

 	switch (dir) {
 	case IIO_EV_DIR_RISING:
-		*val = data->thresh_hi;
+		*val = thresh.high;
 		break;
 	case IIO_EV_DIR_FALLING:
-		*val = data->thresh_low;
+		*val = thresh.low;
 		break;
 	default:
 		return -EINVAL;
@@ -319,7 +1331,15 @@ static int apds9300_read_interrupt_config(struct iio_dev *indio_dev,
 {
 	struct apds9300_data *data = iio_priv(indio_dev);

-	return data->intr_en;
+	switch (chan->type) {
+	case IIO_INTENSITY:
+		return data->als_intr_enabled;
+	case IIO_PROXIMITY:
+		if (data->aux_data)
+			return data->aux_data->ps_intr_enabled;
+	default:
+		return -EINVAL;
+	}
 }

 static int apds9300_write_interrupt_config(struct iio_dev *indio_dev,
@@ -330,26 +1350,22 @@ static int apds9300_write_interrupt_config(struct iio_dev *indio_dev,
 	int ret;

 	mutex_lock(&data->mutex);
-	ret = apds9300_set_intr_state(data, state);
+	switch (chan->type) {
+	case IIO_INTENSITY:
+		ret = data->set_intr_state(data, ALS_INTR, state);
+		break;
+	case IIO_PROXIMITY:
+		ret = data->set_intr_state(data, PS_INTR, state);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
 	mutex_unlock(&data->mutex);

 	return ret;
 }

-static const struct iio_info apds9300_info_no_irq = {
-	.driver_module	= THIS_MODULE,
-	.read_raw	= apds9300_read_raw,
-};
-
-static const struct iio_info apds9300_info = {
-	.driver_module		= THIS_MODULE,
-	.read_raw		= apds9300_read_raw,
-	.read_event_value	= apds9300_read_thresh,
-	.write_event_value	= apds9300_write_thresh,
-	.read_event_config	= apds9300_read_interrupt_config,
-	.write_event_config	= apds9300_write_interrupt_config,
-};
-
 static const struct iio_event_spec apds9300_event_spec[] = {
 	{
 		.type = IIO_EV_TYPE_THRESH,
@@ -364,43 +1380,133 @@ static const struct iio_event_spec apds9300_event_spec[] = {
 	},
 };

+/* Lux (processed Ch0 + Ch1) */
+#define APDS9300_LIGHT_CHAN {						\
+	.type			= IIO_LIGHT,				\
+	.info_mask_separate	= BIT(IIO_CHAN_INFO_PROCESSED) |	\
+			BIT(IIO_CHAN_INFO_ENABLE),			\
+}
+
+/*
+ * Ch0 photodiode (visible light + infrared); threshold
+ * triggered event
+ */
+#define APDS9300_INTENSITY_BOTH_CHAN {					\
+	.type			= IIO_INTENSITY,			\
+	.channel		= 0,					\
+	.modified		= true,					\
+	.channel2		= IIO_MOD_LIGHT_BOTH,			\
+	.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW),		\
+	.event_spec		= apds9300_event_spec,			\
+	.num_event_specs	= ARRAY_SIZE(apds9300_event_spec),	\
+}
+
+/* Ch1 photodiode (infrared) */
+#define APDS9300_INTENSITY_IR_CHAN {				\
+	.type			= IIO_INTENSITY,		\
+	.channel		= 1,				\
+	.modified		= true,				\
+	.channel2		= IIO_MOD_LIGHT_IR,		\
+	.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW),	\
+}
+
 static const struct iio_chan_spec apds9300_channels[] = {
+	APDS9300_LIGHT_CHAN,
+	APDS9300_INTENSITY_BOTH_CHAN,
+	APDS9300_INTENSITY_IR_CHAN,
+};
+
+static const struct iio_chan_spec apds9930_channels[] = {
+	APDS9300_LIGHT_CHAN,
+	APDS9300_INTENSITY_BOTH_CHAN,
+	APDS9300_INTENSITY_IR_CHAN,
 	{
-		.type = IIO_LIGHT,
-		.channel = 0,
-		.indexed = true,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
-	}, {
-		.type = IIO_INTENSITY,
-		.channel = 0,
-		.channel2 = IIO_MOD_LIGHT_BOTH,
-		.indexed = true,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
-		.event_spec = apds9300_event_spec,
-		.num_event_specs = ARRAY_SIZE(apds9300_event_spec),
-	}, {
-		.type = IIO_INTENSITY,
-		.channel = 1,
-		.channel2 = IIO_MOD_LIGHT_IR,
-		.indexed = true,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		/* Proximity channel; threshold triggered event */
+		.type			= IIO_PROXIMITY,
+		.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_ENABLE),
+		.event_spec		= apds9300_event_spec,
+		.num_event_specs	= ARRAY_SIZE(apds9300_event_spec),
 	},
 };

-static irqreturn_t apds9300_interrupt_handler(int irq, void *private)
+static const struct iio_info apds9300_info_no_irq = {
+	.driver_module	= THIS_MODULE,
+	.read_raw	= apds9300_read_raw,
+	.write_raw	= apds9300_write_raw,
+};
+
+static const struct iio_info apds9300_info = {
+	.driver_module		= THIS_MODULE,
+	.read_raw		= apds9300_read_raw,
+	.write_raw		= apds9300_write_raw,
+	.read_event_value	= apds9300_read_thresh,
+	.write_event_value	= apds9300_write_thresh,
+	.read_event_config	= apds9300_read_interrupt_config,
+	.write_event_config	= apds9300_write_interrupt_config,
+};
+
+static const struct iio_info apds9930_info_no_irq = {
+	.driver_module	= THIS_MODULE,
+	.read_raw	= apds9300_read_raw,
+	.write_raw	= apds9930_write_raw,
+};
+
+static const struct iio_info apds9930_info = {
+	.driver_module		= THIS_MODULE,
+	.read_raw		= apds9300_read_raw,
+	.write_raw		= apds9930_write_raw,
+	.read_event_value	= apds9300_read_thresh,
+	.read_event_config	= apds9300_read_interrupt_config,
+	.write_event_config	= apds9300_write_interrupt_config,
+};
+
+/* iio_dev data for APDS9300 and APDS9930 */
+struct apds9300_iio_dev_info {
+	const struct iio_chan_spec	*channels;
+	const int			num_channels;
+	const struct iio_info		*irq_info;
+	const struct iio_info		*no_irq_info;
+};
+
+static const struct apds9300_iio_dev_info apds9300_info_tbl[] = {
+	[APDS9300_ID] = {
+		.channels	= apds9300_channels,
+		.num_channels	= ARRAY_SIZE(apds9300_channels),
+		.irq_info	= &apds9300_info,
+		.no_irq_info	= &apds9300_info_no_irq,
+	},
+	[APDS9930_ID] = {
+		.channels	= apds9930_channels,
+		.num_channels	= ARRAY_SIZE(apds9930_channels),
+		.irq_info	= &apds9930_info,
+		.no_irq_info	= &apds9930_info_no_irq,
+	},
+};
+
+static int apds9300_gpio_probe(struct i2c_client *client,
+			       struct apds9300_data *data)
 {
-	struct iio_dev *dev_info = private;
-	struct apds9300_data *data = iio_priv(dev_info);
+	struct device *dev;
+	struct gpio_desc *gpio;
+	int ret;

-	iio_push_event(dev_info,
-		       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
-					    IIO_EV_TYPE_THRESH,
-					    IIO_EV_DIR_EITHER),
-		       iio_get_time_ns());
+	if (!client)
+		return -EINVAL;

-	apds9300_clear_intr(data);
+	dev = &client->dev;

-	return IRQ_HANDLED;
+	gpio = devm_gpiod_get_index(dev, APDS9300_GPIO_NAME, 0);
+	if (IS_ERR(gpio)) {
+		dev_err(dev, "ACPI GPIO get index failed\n");
+		return PTR_ERR(gpio);
+	}
+
+	ret =  gpiod_direction_input(gpio);
+	if (ret)
+		return ret;
+
+	return gpiod_to_irq(gpio);
 }

 static int apds9300_probe(struct i2c_client *client,
@@ -408,7 +1514,7 @@ static int apds9300_probe(struct i2c_client *client,
 {
 	struct apds9300_data *data;
 	struct iio_dev *indio_dev;
-	int ret;
+	int ret, device_index;

 	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 	if (!indio_dev)
@@ -418,32 +1524,43 @@ static int apds9300_probe(struct i2c_client *client,
 	i2c_set_clientdata(client, indio_dev);
 	data->client = client;

-	ret = apds9300_chip_init(data);
-	if (ret < 0)
-		goto err;
-
 	mutex_init(&data->mutex);

-	indio_dev->dev.parent = &client->dev;
-	indio_dev->channels = apds9300_channels;
-	indio_dev->num_channels = ARRAY_SIZE(apds9300_channels);
-	indio_dev->name = APDS9300_DRV_NAME;
-	indio_dev->modes = INDIO_DIRECT_MODE;
+	/* Recommended wait time after power on. */
+	msleep(APDS9930_INIT_SLEEP);

-	if (client->irq)
-		indio_dev->info = &apds9300_info;
-	else
-		indio_dev->info = &apds9300_info_no_irq;
+	/* Check chip ID to differentiate chips (9300/9930) */
+	ret = apds9300_chip_init(data, &device_index);
+	if (ret < 0)
+		goto err;

-	if (client->irq) {
-		ret = devm_request_threaded_irq(&client->dev, client->irq,
-				NULL, apds9300_interrupt_handler,
-				IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-				APDS9300_IRQ_NAME, indio_dev);
+	indio_dev->dev.parent	= &client->dev;
+	indio_dev->channels	= apds9300_info_tbl[device_index].channels;
+	indio_dev->num_channels	= apds9300_info_tbl[device_index].num_channels;
+	indio_dev->name		= APDS9300_DRV_NAME;
+	indio_dev->modes	= INDIO_DIRECT_MODE;
+
+	if (client->irq <= 0)
+		client->irq = apds9300_gpio_probe(client, data);
+
+	if (client->irq > 0) {
+		indio_dev->info = apds9300_info_tbl[device_index].irq_info;
+
+		ret = devm_request_threaded_irq(&client->dev,
+						client->irq,
+						NULL,
+						data->irq_handler,
+						IRQF_TRIGGER_FALLING |
+						IRQF_ONESHOT,
+						APDS9300_IRQ_NAME,
+						indio_dev);
 		if (ret) {
 			dev_err(&client->dev, "irq request error %d\n", -ret);
 			goto err;
 		}
+	} else {
+		indio_dev->info = apds9300_info_tbl[device_index].no_irq_info;
+		dev_info(&client->dev, "no irq\n");
 	}

 	ret = iio_device_register(indio_dev);
@@ -453,20 +1570,29 @@ static int apds9300_probe(struct i2c_client *client,
 	return 0;

 err:
-	/* Ensure that power off in case of error */
+	/* Ensure that power is off in case of error */
 	apds9300_set_power_state(data, 0);
+
 	return ret;
 }

 static int apds9300_remove(struct i2c_client *client)
 {
-	struct iio_dev *indio_dev = i2c_get_clientdata(client);
-	struct apds9300_data *data = iio_priv(indio_dev);
+	struct iio_dev *indio_dev	= i2c_get_clientdata(client);
+	struct apds9300_data *data	= iio_priv(indio_dev);

 	iio_device_unregister(indio_dev);

 	/* Ensure that power off and interrupts are disabled */
-	apds9300_set_intr_state(data, 0);
+	data->set_intr_state(data, ALS_INTR, 0);
+
+	if (data->aux_data) {
+		data->set_intr_state(data, PS_INTR, 0);
+
+		kfree(data->platform_data);
+		kfree(data->aux_data);
+	}
+
 	apds9300_set_power_state(data, 0);

 	return 0;
@@ -505,18 +1631,26 @@ static SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, apds9300_resume);
 #define APDS9300_PM_OPS NULL
 #endif

+static const struct acpi_device_id apds9300_acpi_table[] = {
+	{ "APDS9300", APDS9300_ID },
+	{ "APDS9930", APDS9930_ID },
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, apds9300_acpi_table);
+
 static struct i2c_device_id apds9300_id[] = {
-	{ APDS9300_DRV_NAME, 0 },
+	{ "apds9300",  APDS9300_ID },
+	{ "apds9930",  APDS9930_ID },
 	{ }
 };
-
 MODULE_DEVICE_TABLE(i2c, apds9300_id);

 static struct i2c_driver apds9300_driver = {
 	.driver = {
-		.name	= APDS9300_DRV_NAME,
-		.owner	= THIS_MODULE,
-		.pm	= APDS9300_PM_OPS,
+		.name			= APDS9300_DRV_NAME,
+		.owner			= THIS_MODULE,
+		.pm			= APDS9300_PM_OPS,
+		.acpi_match_table	= ACPI_PTR(apds9300_acpi_table),
 	},
 	.probe		= apds9300_probe,
 	.remove		= apds9300_remove,
@@ -527,5 +1661,6 @@ module_i2c_driver(apds9300_driver);

 MODULE_AUTHOR("Kravchenko Oleksandr <o.v.kravchenko@xxxxxxxxxxxxxxx>");
 MODULE_AUTHOR("GlobalLogic inc.");
-MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver");
-MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cristina Ciocan <cristina.ciocan@xxxxxxxxx>");
+MODULE_DESCRIPTION("APDS9300 ALS and APDS9930 ALS & proximity sensors driver");
+MODULE_LICENSE("GPL v2");
--
1.8.1.2

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