linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC] iio: add driver for si114x ambient light / proximity sensors
@ 2016-05-19 17:53 Crestez Dan Leonard
  2016-05-19 21:59 ` [RFC] iio: Add driver for Silabs si1132, si1141/2/3 and si1145/6/7 ambient light, uv index and " Peter Meerwald-Stadler
  0 siblings, 1 reply; 3+ messages in thread
From: Crestez Dan Leonard @ 2016-05-19 17:53 UTC (permalink / raw)
  To: Jonathan Cameron, linux-iio, Peter Meerwald
  Cc: linux-kernel, Hartmut Knaack, Lars-Peter Clausen, Daniel Baluta,
	Crestez Dan Leonard

The si114x supports x=1,2,3 IR LEDs for proximity sensing together with
visible and IR ambient light sensing (ALS)

Arranging 3 IR LEDs in a triangular shape can be used for detection of swipe
gestures (the present driver only measures the intensities, it does not process
the data). There is an affordable reference design (via Digikey), see
http://www.silabs.com/products/sensors/Pages/HID-USB-to-IR-Reference-Design.aspx

Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net>
Signed-off-by: Crestez Dan Leonard <leonard.crestez@intel.com>
---

This is based on an old driver by Peter which was posted for review but not
actually submitted. I made the minimal changes required to compile and
succesfully probe on new kernels and next I intend to add support for the
1145/1146/1147 family.

Peter: If you have a newer version you should post it now.

There is another si114x driver in the chromiumos tree but I decided against
using it because:
 * Copyright is more complicated
 * It does probe because it doesn't set scan_index -1 on current channels. This
indicates a lack of testing.
 * It blindly replaces IIO_INTESITY with IIO_ILLUMINANCE. The device exposes
channels for measuring infrared and visible light and the datasheet recommends
doing calculations on the host to derive an actual lux measurement. I think
that simply exposing the raw values as 'lux' is wrong and unit-less
IIO_INTENSITY is more appropriate.

 drivers/iio/light/Kconfig  |   13 +
 drivers/iio/light/Makefile |    1 +
 drivers/iio/light/si114x.c | 1202 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1216 insertions(+)
 create mode 100644 drivers/iio/light/si114x.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 7c566f5..a2039d7 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -266,6 +266,19 @@ config PA12203001
          This driver can also be built as a module.  If so, the module
          will be called pa12203001.
 
+config SI114X
+	tristate "SI114x combined ALS and proximity sensor"
+	depends on I2C
+	default n
+	---help---
+	  Say Y here if you want to build a driver for the Silicon Labs SI114x
+	  combined ambient light and proximity sensor chips (SI1141, SI1142,
+	  SI1143). The driver supports forced (with and w/o IRQ) and autonomous
+	  measurements (with IRQ only).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called si114x.
+
 config STK3310
 	tristate "STK3310 ALS and proximity sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 6f2a3c6..cd7824f 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
 obj-$(CONFIG_RPR0521)		+= rpr0521.o
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
+obj-$(CONFIG_SI114X)		+= si114x.o
 obj-$(CONFIG_STK3310)          += stk3310.o
 obj-$(CONFIG_TCS3414)		+= tcs3414.o
 obj-$(CONFIG_TCS3472)		+= tcs3472.o
diff --git a/drivers/iio/light/si114x.c b/drivers/iio/light/si114x.c
new file mode 100644
index 0000000..5ac2ef0
--- /dev/null
+++ b/drivers/iio/light/si114x.c
@@ -0,0 +1,1202 @@
+/*
+ * si114x.c - Support for Silabs si114x combined ambient light and
+ * proximity sensor
+ *
+ * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License.  See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * IIO driver for si114x (7-bit I2C slave address 0x5a) with sequencer
+ * version >= A03
+ *
+ * driver supports IRQ and non-IRQ mode; an IRQ is required for
+ * autonomous measurement mode
+ * TODO:
+ *   thresholds
+ *   power management (measurement rate zero)
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/buffer.h>
+
+#define SI114X_REG_PART_ID		0x00
+#define SI114X_REG_REV_ID		0x01
+#define SI114X_REG_SEQ_ID		0x02
+#define SI114X_REG_INT_CFG		0x03
+#define SI114X_REG_IRQ_ENABLE		0x04
+#define SI114X_REG_IRQ_MODE1		0x05
+#define SI114X_REG_IRQ_MODE2		0x06
+#define SI114X_REG_HW_KEY		0x07
+/* RATE stores a 16 bit value compressed to 8 bit */
+#define SI114X_REG_MEAS_RATE		0x08
+#define SI114X_REG_ALS_RATE		0x09
+#define SI114X_REG_PS_RATE		0x0a
+#define SI114X_REG_ALS_LOW_TH0		0x0b
+#define SI114X_REG_ALS_LOW_TH1		0x0c
+#define SI114X_REG_ALS_HI_TH0		0x0d
+#define SI114X_REG_ALS_HI_TH1		0x0e
+#define SI114X_REG_PS_LED21		0x0f
+#define SI114X_REG_PS_LED3		0x10
+/*
+ * for rev A10 and below TH0 stores a 16 bit value compressed to 8 bit and
+ * TH1 is not used; newer revision have the LSB in TH0 and the MSB in TH1
+ */
+#define SI114X_REG_PS1_TH0		0x11
+#define SI114X_REG_PS1_TH1		0x12
+#define SI114X_REG_PS2_TH0		0x13
+#define SI114X_REG_PS2_TH1		0x11
+#define SI114X_REG_PS3_TH0		0x15
+#define SI114X_REG_PS3_TH1		0x16
+#define SI114X_REG_PARAM_WR		0x17
+#define SI114X_REG_COMMAND		0x18
+#define SI114X_REG_RESPONSE		0x20
+#define SI114X_REG_IRQ_STATUS		0x21
+#define SI114X_REG_ALSVIS_DATA0		0x22
+#define SI114X_REG_ALSVIS_DATA1		0x23
+#define SI114X_REG_ALSIR_DATA0		0x24
+#define SI114X_REG_ALSIR_DATA1		0x25
+#define SI114X_REG_PS1_DATA0		0x26
+#define SI114X_REG_PS1_DATA1		0x27
+#define SI114X_REG_PS2_DATA0		0x28
+#define SI114X_REG_PS2_DATA1		0x29
+#define SI114X_REG_PS3_DATA0		0x2a
+#define SI114X_REG_PS3_DATA1		0x2b
+#define SI114X_REG_AUX_DATA0		0x2c
+#define SI114X_REG_AUX_DATA1		0x2d
+#define SI114X_REG_PARAM_RD		0x2e
+#define SI114X_REG_CHIP_STAT		0x30
+
+/* helper to figure out PS_LED register / shift per channel */
+#define SI114X_PS_LED_REG(ch) \
+	(((ch) == 2) ? SI114X_REG_PS_LED3 : SI114X_REG_PS_LED21)
+#define SI114X_PS_LED_SHIFT(ch) \
+	(((ch) == 1) ? 4 : 0)
+
+/* Parameter offsets */
+#define SI114X_PARAM_I2C_ADDR		0x00
+#define SI114X_PARAM_CHLIST		0x01
+#define SI114X_PARAM_PSLED12_SELECT	0x02
+#define SI114X_PARAM_PSLED3_SELECT	0x03
+#define SI114X_PARAM_FILTER_EN		0x04
+#define SI114X_PARAM_PS_ENCODING	0x05
+#define SI114X_PARAM_ALS_ENCODING	0x06
+#define SI114X_PARAM_PS1_ADC_MUX	0x07
+#define SI114X_PARAM_PS2_ADC_MUX	0x08
+#define SI114X_PARAM_PS3_ADC_MUX	0x09
+#define SI114X_PARAM_PS_ADC_COUNTER	0x0a
+#define SI114X_PARAM_PS_ADC_GAIN	0x0b
+#define SI114X_PARAM_PS_ADC_MISC	0x0c
+#define SI114X_PARAM_ALS_ADC_MUX	0x0d
+#define SI114X_PARAM_ALSIR_ADC_MUX	0x0e
+#define SI114X_PARAM_AUX_ADC_MUX	0x0f
+#define SI114X_PARAM_ALSVIS_ADC_COUNTER	0x10
+#define SI114X_PARAM_ALSVIS_ADC_GAIN	0x11
+#define SI114X_PARAM_ALSVIS_ADC_MISC	0x12
+#define SI114X_PARAM_ALS_HYST		0x16
+#define SI114X_PARAM_PS_HYST		0x17
+#define SI114X_PARAM_PS_HISTORY		0x18
+#define SI114X_PARAM_ALS_HISTORY	0x19
+#define SI114X_PARAM_ADC_OFFSET		0x1a
+#define SI114X_PARAM_SLEEP_CTRL		0x1b
+#define SI114X_PARAM_LED_RECOVERY	0x1c
+#define SI114X_PARAM_ALSIR_ADC_COUNTER	0x1d
+#define SI114X_PARAM_ALSIR_ADC_GAIN	0x1e
+#define SI114X_PARAM_ALSIR_ADC_MISC	0x1f
+
+/* Channel enable masks for CHLIST parameter */
+#define SI114X_CHLIST_EN_PS1		0x01
+#define SI114X_CHLIST_EN_PS2		0x02
+#define SI114X_CHLIST_EN_PS3		0x04
+#define SI114X_CHLIST_EN_ALSVIS		0x10
+#define SI114X_CHLIST_EN_ALSIR		0x20
+#define SI114X_CHLIST_EN_AUX		0x40
+
+/* Signal range mask for ADC_MISC parameter */
+#define SI114X_MISC_RANGE		0x20
+
+/* Commands for REG_COMMAND */
+#define SI114X_CMD_NOP			0x00
+#define SI114X_CMD_RESET		0x01
+#define SI114X_CMD_BUSADDR		0x02
+#define SI114X_CMD_PS_FORCE		0x05
+#define SI114X_CMD_ALS_FORCE		0x06
+#define SI114X_CMD_PSALS_FORCE		0x07
+#define SI114X_CMD_PS_PAUSE		0x09
+#define SI114X_CMD_ALS_PAUSE		0x0a
+#define SI114X_CMD_PSALS_PAUSE		0x0b
+#define SI114X_CMD_PS_AUTO		0x0d
+#define SI114X_CMD_ALS_AUTO		0x0e
+#define SI114X_CMD_PSALS_AUTO		0x0f
+#define SI114X_CMD_PARAM_QUERY		0x80
+#define SI114X_CMD_PARAM_SET		0xa0
+#define SI114X_CMD_PARAM_AND		0xc0
+#define SI114X_CMD_PARAM_OR		0xe0
+
+/* Interrupt configuration masks for INT_CFG register */
+#define SI114X_INT_CFG_OE		0x01 /* enable interrupt */
+#define SI114X_INT_CFG_MODE		0x02 /* auto reset interrupt pin */
+
+/* Interrupt enable masks for IRQ_ENABLE register */
+#define SI114X_CMD_IE			0x20
+#define SI114X_PS3_IE			0x10
+#define SI114X_PS2_IE			0x08
+#define SI114X_PS1_IE			0x04
+#define SI114X_ALS_INT1_IE		0x02
+#define SI114X_ALS_INT0_IE		0x01
+
+/* Interrupt mode masks for IRQ_MODE1 register */
+#define SI114X_PS2_IM_GREATER		0xc0
+#define SI114X_PS2_IM_CROSS		0x40
+#define SI114X_PS1_IM_GREATER		0x30
+#define SI114X_PS1_IM_CROSS		0x10
+
+/* Interrupt mode masks for IRQ_MODE2 register */
+#define SI114X_CMD_IM_ERROR		0x04
+#define SI114X_PS3_IM_GREATER		0x03
+#define SI114X_PS3_IM_CROSS		0x01
+
+/* Measurement rate settings */
+#define SI114X_MEAS_RATE_FORCED		0x00
+#define SI114X_MEAS_RATE_10MS		0x84
+#define SI114X_MEAS_RATE_20MS		0x94
+#define SI114X_MEAS_RATE_100MS		0xb9
+#define SI114X_MEAS_RATE_496MS		0xdf
+#define SI114X_MEAS_RATE_1984MS		0xff
+
+/* ALS rate settings relative to measurement rate */
+#define SI114X_ALS_RATE_OFF		0x00
+#define SI114X_ALS_RATE_1X		0x08
+#define SI114X_ALS_RATE_10X		0x32
+#define SI114X_ALS_RATE_100X		0x69
+
+/* PS rate settings relative to measurement rate */
+#define SI114X_PS_RATE_OFF		0x00
+#define SI114X_PS_RATE_1X		0x08
+#define SI114X_PS_RATE_10X		0x32
+#define SI114X_PS_RATE_100X		0x69
+
+/* Sequencer revision from SEQ_ID */
+#define SI114X_SEQ_REV_A01		0x01
+#define SI114X_SEQ_REV_A02		0x02
+#define SI114X_SEQ_REV_A03		0x03
+#define SI114X_SEQ_REV_A10		0x08
+#define SI114X_SEQ_REV_A11		0x09
+
+#define SI114X_DRV_NAME "si114x"
+
+/**
+ * struct si114x_data - si114x chip state data
+ * @client:	I2C client
+ * @mutex:	mutex to protect multi-step I2C accesses and state changes
+ * @part:	chip part number (0x41, 0x42, 0x43) for 1 to 3 LEDs version
+ * @seq:	sequencer firmware revision determines chip features
+ * @data_avail:	wait queue for single measurement IRQ completion
+ * @got_data:	wait condition variable
+ * @use_irq:	set when IRQ is available
+ * @autonomous:	set when chip performs autonomous measurements (only when
+ *		IRQ available)
+ * @trig:	IIO trigger (only when IRQ available)
+ *
+ * The driver supports polling and IRQ operation for single and buffered
+ * measurements. A device trigger enables autonomous measurements are when
+ * an IRQ is available.
+ **/
+struct si114x_data {
+	struct i2c_client *client;
+	struct mutex mutex;
+	u8 part;
+	u8 seq;
+	wait_queue_head_t data_avail;
+	bool got_data;
+	bool use_irq;
+	bool autonomous;
+	struct iio_trigger *trig;
+};
+
+/* expand 8 bit compressed value to 16 bit, see Silabs AN498 */
+static u16 si114x_uncompress(u8 x)
+{
+	u16 result = 0;
+	u8 exponent = 0;
+
+	if (x < 8)
+		return 0;
+
+	exponent = (x & 0xf0) >> 4;
+	result = 0x10 | (x & 0x0f);
+
+	if (exponent >= 4)
+		return result << (exponent - 4);
+	return result >> (4 - exponent);
+}
+
+/* compress 16 bit to 8 bit using 4 bit exponent and 4 bit fraction,
+ * see Silabs AN498 */
+static u8 si114x_compress(u16 x)
+{
+	u32 exponent = 0;
+	u32 significand = 0;
+	u32 tmp = x;
+
+	if (x == 0x0000)
+		return 0x00;
+	if (x == 0x0001)
+		return 0x08;
+
+	while (1) {
+		tmp >>= 1;
+		exponent += 1;
+		if (tmp == 1)
+			break;
+	}
+
+	if (exponent < 5) {
+		significand = x << (4 - exponent);
+		return (exponent << 4) | (significand & 0xF);
+	}
+
+	significand = x >> (exponent - 5);
+	if (significand & 1) {
+		significand += 2;
+		if (significand & 0x0040) {
+			exponent += 1;
+			significand >>= 1;
+		}
+	}
+
+	return (exponent << 4) | ((significand >> 1) & 0xF);
+}
+
+/* helper function to operate on parameter values: op can be query/set/or/and */
+static int si114x_param_op(struct si114x_data *data, u8 op, u8 param, u8 value)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	mutex_lock(&data->mutex);
+
+	if (op != SI114X_CMD_PARAM_QUERY) {
+		ret = i2c_smbus_write_byte_data(client,
+			SI114X_REG_PARAM_WR, value);
+		if (ret < 0)
+			goto error;
+	}
+
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_COMMAND,
+		op | (param & 0x1F));
+	if (ret < 0)
+		goto error;
+
+	ret = i2c_smbus_read_byte_data(client, SI114X_REG_PARAM_RD);
+	if (ret < 0)
+		return ret;
+
+	mutex_unlock(&data->mutex);
+
+	return ret & 0xff;
+error:
+	mutex_unlock(&data->mutex);
+	return ret;
+}
+
+static irqreturn_t si114x_trigger_handler(int irq, void *private)
+{
+	struct iio_poll_func *pf = private;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct si114x_data *data = iio_priv(indio_dev);
+	/* maximum buffer size:
+	 * 6*2 bytes channels data + 4 bytes alignment +
+	 * 8 bytes timestamp
+	 */
+	u8 buffer[24];
+	int len = 0;
+	int i, j = 0;
+	int ret;
+
+	if (!data->autonomous) {
+		ret = i2c_smbus_write_byte_data(data->client,
+			SI114X_REG_COMMAND, SI114X_CMD_PSALS_FORCE);
+		if (ret < 0)
+			goto done;
+		msleep(20);
+	}
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		ret = i2c_smbus_read_word_data(data->client,
+			indio_dev->channels[i].address);
+		if (ret < 0)
+			goto done;
+
+		((u16 *) buffer)[j++] = ret & 0xffff;
+		len += 2;
+	}
+
+	if (indio_dev->scan_timestamp)
+		*(s64 *)(buffer + ALIGN(len, sizeof(s64)))
+			= iio_get_time_ns();
+	iio_push_to_buffers(indio_dev, buffer);
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t si114x_irq(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(data->client,
+		SI114X_REG_IRQ_STATUS);
+	if (ret < 0 || !(ret & (SI114X_PS3_IE | SI114X_PS2_IE | SI114X_PS1_IE |
+		SI114X_ALS_INT1_IE | SI114X_ALS_INT0_IE)))
+		return IRQ_HANDLED;
+
+	if (iio_buffer_enabled(indio_dev))
+		iio_trigger_poll_chained(indio_dev->trig);
+	else {
+		data->got_data = true;
+		wake_up_interruptible(&data->data_avail);
+	}
+
+	/* clearing IRQ */
+	ret = i2c_smbus_write_byte_data(data->client,
+		SI114X_REG_IRQ_STATUS, ret & 0x1f);
+	if (ret < 0)
+		dev_err(&data->client->dev, "clearing irq failed\n");
+
+	return IRQ_HANDLED;
+}
+
+static int si114x_trigger_set_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+	int cmd;
+
+	/* configure autonomous mode */
+	cmd = state ? SI114X_CMD_PSALS_AUTO :
+		SI114X_CMD_PSALS_PAUSE;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+		SI114X_REG_COMMAND, cmd);
+	if (ret < 0)
+		return ret;
+
+	data->autonomous = state;
+
+	return 0;
+}
+
+static const struct iio_trigger_ops si114x_trigger_ops = {
+	.owner = THIS_MODULE,
+	.set_trigger_state = si114x_trigger_set_state,
+};
+
+static int si114x_probe_trigger(struct iio_dev *indio_dev)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+
+	data->trig = iio_trigger_alloc("si114x-dev%d", indio_dev->id);
+	if (!data->trig)
+		return -ENOMEM;
+
+	data->trig->dev.parent = &data->client->dev;
+	data->trig->ops = &si114x_trigger_ops;
+	iio_trigger_set_drvdata(data->trig, indio_dev);
+	ret = iio_trigger_register(data->trig);
+	if (ret)
+		goto error_free_trig;
+
+	/* select default trigger */
+	indio_dev->trig = data->trig;
+
+	return 0;
+
+error_free_trig:
+	iio_trigger_free(data->trig);
+	return ret;
+}
+
+static void si114x_remove_trigger(struct iio_dev *indio_dev)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+
+	iio_trigger_unregister(data->trig);
+	iio_trigger_free(data->trig);
+}
+
+static int si114x_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret = -EINVAL;
+	u8 cmd, reg;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_PROXIMITY:
+		case IIO_TEMP:
+			if (iio_buffer_enabled(indio_dev))
+				return -EBUSY;
+
+			if (chan->type == IIO_PROXIMITY)
+				cmd = SI114X_CMD_PS_FORCE;
+			else
+				cmd = SI114X_CMD_ALS_FORCE;
+			ret = i2c_smbus_write_byte_data(data->client,
+				SI114X_REG_COMMAND, cmd);
+			if (ret < 0)
+				return ret;
+			if (data->use_irq) {
+				ret = wait_event_interruptible_timeout(
+					data->data_avail, data->got_data,
+					msecs_to_jiffies(1000));
+				data->got_data = false;
+				if (ret == 0)
+					ret = -ETIMEDOUT;
+			} else
+				msleep(20);
+			if (ret < 0)
+				return ret;
+
+			ret = i2c_smbus_read_word_data(data->client,
+				chan->address);
+			if (ret < 0)
+				return ret;
+
+			*val = ret & 0xffff;
+
+			ret = IIO_VAL_INT;
+			break;
+		case IIO_CURRENT:
+			ret = i2c_smbus_read_byte_data(data->client,
+				SI114X_PS_LED_REG(chan->channel));
+			if (ret < 0)
+				return ret;
+
+			*val = (ret >> SI114X_PS_LED_SHIFT(chan->channel))
+				& 0x0f;
+
+			ret = IIO_VAL_INT;
+			break;
+		default:
+			break;
+		}
+		break;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			reg = SI114X_PARAM_PS_ADC_GAIN;
+			break;
+		case IIO_INTENSITY:
+			if (chan->channel2 == IIO_MOD_LIGHT_IR)
+				reg = SI114X_PARAM_ALSIR_ADC_GAIN;
+			else
+				reg = SI114X_PARAM_ALSVIS_ADC_GAIN;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = si114x_param_op(data, SI114X_CMD_PARAM_QUERY, reg, 0);
+		if (ret < 0)
+			return ret;
+
+		*val = ret & 0x07;
+
+		ret = IIO_VAL_INT;
+		break;
+	}
+
+	return ret;
+}
+
+static int si114x_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	u8 reg1, reg2, shift;
+	int ret = -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			if (val < 0 || val > 5)
+				return -EINVAL;
+			reg1 = SI114X_PARAM_PS_ADC_GAIN;
+			reg2 = SI114X_PARAM_PS_ADC_COUNTER;
+			break;
+		case IIO_INTENSITY:
+			if (val < 0 || val > 7)
+				return -EINVAL;
+			if (chan->channel2 == IIO_MOD_LIGHT_IR) {
+				reg1 = SI114X_PARAM_ALSIR_ADC_GAIN;
+				reg2 = SI114X_PARAM_ALSIR_ADC_COUNTER;
+			} else {
+				reg1 = SI114X_PARAM_ALSVIS_ADC_GAIN;
+				reg2 = SI114X_PARAM_ALSVIS_ADC_COUNTER;
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = si114x_param_op(data, SI114X_CMD_PARAM_SET,
+			reg1, val);
+		if (ret < 0)
+			return ret;
+		/* set recovery period to one's complement of gain */
+		ret = si114x_param_op(data, SI114X_CMD_PARAM_SET,
+			reg2, (~val & 0x07) << 4);
+		break;
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_CURRENT)
+			return -EINVAL;
+
+		if (val < 0 || val > 0xf)
+			return -EINVAL;
+
+		reg1 = SI114X_PS_LED_REG(chan->channel);
+		shift = SI114X_PS_LED_SHIFT(chan->channel);
+		ret = i2c_smbus_read_byte_data(data->client, reg1);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(data->client, reg1,
+			(ret & ~(0x0f << shift)) |
+			((val & 0x0f) << shift));
+		break;
+	}
+	return ret;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static int si114x_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (readval) {
+		ret = i2c_smbus_read_byte_data(data->client, reg);
+		if (ret < 0)
+			return ret;
+		*readval = ret;
+		ret = 0;
+	} else
+		ret = i2c_smbus_write_byte_data(data->client, reg, writeval);
+
+	return ret;
+}
+#endif
+
+static int si114x_revisions(struct si114x_data *data)
+{
+	int ret = i2c_smbus_read_byte_data(data->client, SI114X_REG_PART_ID);
+	if (ret < 0)
+		return ret;
+
+	switch (ret) {
+	case 0x41:
+	case 0x42:
+	case 0x43:
+		data->part = ret;
+		break;
+	default:
+		dev_err(&data->client->dev, "invalid part\n");
+		return -EINVAL;
+	}
+
+	ret = i2c_smbus_read_byte_data(data->client, SI114X_REG_SEQ_ID);
+	if (ret < 0)
+		return ret;
+	data->seq = ret;
+
+	if (data->seq < SI114X_SEQ_REV_A03)
+		dev_info(&data->client->dev, "WARNING: old sequencer revision\n");
+
+	return 0;
+}
+
+static inline unsigned int si114x_leds(struct si114x_data *data)
+{
+	return data->part - 0x40;
+}
+
+#define SI114X_INTENSITY_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = (BIT(IIO_CHAN_INFO_RAW) | \
+			       BIT(IIO_CHAN_INFO_HARDWAREGAIN)), \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+	}, \
+	.scan_index = _si, \
+	.address = SI114X_REG_ALSVIS_DATA0, \
+}
+
+#define SI114X_INTENSITY_IR_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = (BIT(IIO_CHAN_INFO_RAW) | \
+			       BIT(IIO_CHAN_INFO_HARDWAREGAIN)), \
+	.modified = 1, \
+	.channel2 = IIO_MOD_LIGHT_IR, \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+	}, \
+	.scan_index = _si, \
+	.address = SI114X_REG_ALSIR_DATA0 \
+}
+
+#define SI114X_TEMP_CHANNEL(_si) { \
+	.type = IIO_TEMP, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+	}, \
+	.scan_index = _si, \
+	.address = SI114X_REG_AUX_DATA0 \
+}
+
+#define SI114X_PROXIMITY_CHANNEL(_si, _ch) { \
+	.type = IIO_PROXIMITY, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+	}, \
+	.scan_index = _si, \
+	.address = SI114X_REG_PS1_DATA0 + _ch*2 \
+}
+
+#define SI114X_CURRENT_CHANNEL(_ch) { \
+	.type = IIO_CURRENT, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.output = 1, \
+	.scan_index = -1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec si114x_channels[] = {
+	IIO_CHAN_SOFT_TIMESTAMP(0),
+	SI114X_INTENSITY_CHANNEL(1),
+	SI114X_INTENSITY_IR_CHANNEL(2),
+	SI114X_TEMP_CHANNEL(3),
+	SI114X_PROXIMITY_CHANNEL(4, 0),
+	SI114X_CURRENT_CHANNEL(0),
+	SI114X_PROXIMITY_CHANNEL(5, 1),
+	SI114X_CURRENT_CHANNEL(1),
+	SI114X_PROXIMITY_CHANNEL(6, 2),
+	SI114X_CURRENT_CHANNEL(2),
+};
+
+static ssize_t si114x_range_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct iio_dev_attr *dev_attr = to_iio_dev_attr(attr);
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (sysfs_streq(buf, "normal"))
+		ret = si114x_param_op(data, SI114X_CMD_PARAM_AND,
+			dev_attr->address, ~SI114X_MISC_RANGE);
+	else if (sysfs_streq(buf, "high"))
+		ret = si114x_param_op(data, SI114X_CMD_PARAM_OR,
+			dev_attr->address, SI114X_MISC_RANGE);
+	else
+		return -EINVAL;
+
+	return ret ? ret : len;
+}
+
+static ssize_t si114x_range_show(struct device *dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct iio_dev_attr *dev_attr = to_iio_dev_attr(attr);
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = si114x_param_op(data, SI114X_CMD_PARAM_QUERY,
+		dev_attr->address, 0);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%s\n",
+		(ret & SI114X_MISC_RANGE) ? "high" : "normal");
+}
+
+static IIO_DEVICE_ATTR(in_proximity_range, S_IRUGO | S_IWUSR,
+			si114x_range_show,
+			si114x_range_store,
+			SI114X_PARAM_PS_ADC_MISC);
+
+static IIO_DEVICE_ATTR(in_intensity_range, S_IRUGO | S_IWUSR,
+			si114x_range_show,
+			si114x_range_store,
+			SI114X_PARAM_ALSVIS_ADC_MISC);
+
+static IIO_DEVICE_ATTR(in_intensity_ir_range, S_IRUGO | S_IWUSR,
+			si114x_range_show,
+			si114x_range_store,
+			SI114X_PARAM_ALSIR_ADC_MISC);
+
+static IIO_CONST_ATTR(in_proximity_range_available, "normal high");
+static IIO_CONST_ATTR(in_intensity_range_available, "normal high");
+static IIO_CONST_ATTR(in_intensity_ir_range_available, "normal high");
+
+static int si114x_set_chlist(struct iio_dev *indio_dev, bool all)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	u8 reg = 0;
+	int i;
+
+	if (all) {
+		reg = SI114X_CHLIST_EN_ALSVIS | SI114X_CHLIST_EN_ALSIR |
+			SI114X_CHLIST_EN_AUX;
+		switch (si114x_leds(data)) {
+		case 3:
+			reg |= SI114X_CHLIST_EN_PS3;
+		case 2:
+			reg |= SI114X_CHLIST_EN_PS2;
+		case 1:
+			reg |= SI114X_CHLIST_EN_PS1;
+			break;
+		}
+	} else
+		for_each_set_bit(i, indio_dev->active_scan_mask,
+			indio_dev->masklength) {
+			switch (indio_dev->channels[i].address) {
+			case SI114X_REG_ALSVIS_DATA0:
+				reg |= SI114X_CHLIST_EN_ALSVIS;
+				break;
+			case SI114X_REG_ALSIR_DATA0:
+				reg |= SI114X_CHLIST_EN_ALSIR;
+				break;
+			case SI114X_REG_PS1_DATA0:
+				reg |= SI114X_CHLIST_EN_PS1;
+				break;
+			case SI114X_REG_PS2_DATA0:
+				reg |= SI114X_CHLIST_EN_PS2;
+				break;
+			case SI114X_REG_PS3_DATA0:
+				reg |= SI114X_CHLIST_EN_PS3;
+				break;
+			case SI114X_REG_AUX_DATA0:
+				reg |= SI114X_CHLIST_EN_AUX;
+				break;
+			}
+		}
+
+	return si114x_param_op(data, SI114X_CMD_PARAM_SET,
+		SI114X_PARAM_CHLIST, reg);
+}
+
+static int si114x_initialize(struct iio_dev *indio_dev)
+{
+	struct si114x_data *data = iio_priv(indio_dev);
+	struct i2c_client *client = data->client;
+	int ret;
+
+	/* send reset command */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_COMMAND,
+		SI114X_CMD_RESET);
+	if (ret < 0)
+		return ret;
+	msleep(20);
+
+	/* hardware key, magic value */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_HW_KEY, 0x17);
+	if (ret < 0)
+		return ret;
+	msleep(20);
+
+	/* interrupt configuration, interrupt output enable */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_INT_CFG,
+		data->use_irq ? SI114X_INT_CFG_OE : 0);
+	if (ret < 0)
+		return ret;
+
+	/* enable interrupt for certain activities */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_IRQ_ENABLE,
+		SI114X_PS3_IE | SI114X_PS2_IE | SI114X_PS1_IE |
+		SI114X_ALS_INT0_IE);
+	if (ret < 0)
+		return ret;
+
+	/* in autonomous mode, wakeup every 100 ms */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_MEAS_RATE,
+	    SI114X_MEAS_RATE_100MS);
+	if (ret < 0)
+		return ret;
+
+	/* measure ALS every time device wakes up */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_ALS_RATE,
+		SI114X_ALS_RATE_1X);
+	if (ret < 0)
+		return ret;
+
+	/* measure proximity every time device wakes up */
+	ret = i2c_smbus_write_byte_data(client, SI114X_REG_PS_RATE,
+		SI114X_PS_RATE_1X);
+	if (ret < 0)
+		return ret;
+
+	/* set LED currents to maximum */
+	switch (si114x_leds(data)) {
+	case 3:
+		ret = i2c_smbus_write_byte_data(client,
+			SI114X_REG_PS_LED3, 0x0f);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(client,
+			SI114X_REG_PS_LED21, 0xff);
+		break;
+	case 2:
+		ret = i2c_smbus_write_byte_data(client,
+			SI114X_REG_PS_LED21, 0xff);
+		break;
+	case 1:
+		ret = i2c_smbus_write_byte_data(client,
+			SI114X_REG_PS_LED21, 0x0f);
+		break;
+	}
+	if (ret < 0)
+		return ret;
+
+	ret = si114x_set_chlist(indio_dev, true);
+	if (ret < 0)
+		return ret;
+
+	/* set normal proximity measurement mode, set high signal range
+	 * PS measurement */
+	ret = si114x_param_op(data, SI114X_CMD_PARAM_SET,
+		SI114X_PARAM_PS_ADC_MISC, 0x20 | 0x04);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static ssize_t si114x_read_frequency(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct si114x_data *data = iio_priv(indio_dev);
+	int ret;
+	u16 rate;
+
+	ret = i2c_smbus_read_byte_data(data->client, SI114X_REG_MEAS_RATE);
+	if (ret < 0)
+		return ret;
+
+	if (ret == 0)
+		rate = 0;
+	else
+		rate = 32000 / si114x_uncompress(ret);
+
+	return sprintf(buf, "%d\n", rate);
+}
+
+static ssize_t si114x_write_frequency(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct si114x_data *data = iio_priv(indio_dev);
+	unsigned long val;
+	int ret;
+	u8 rate;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 250:
+	case 100:
+	case 50:
+	case 25:
+	case 10:
+	case 5:
+	case 2:
+	case 1:
+		rate = si114x_compress(32000 / val);
+		break;
+	case 0:
+		rate = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, SI114X_REG_MEAS_RATE,
+		rate);
+
+	return ret ? ret : len;
+}
+
+/* sysfs attributes if IRQ available */
+static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
+			      si114x_read_frequency,
+			      si114x_write_frequency);
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1 2 5 10 25 50 100 250");
+
+static struct attribute *si114x_attrs_trigger[] = {
+	&iio_dev_attr_in_proximity_range.dev_attr.attr,
+	&iio_const_attr_in_proximity_range_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_range.dev_attr.attr,
+	&iio_const_attr_in_intensity_range_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_ir_range.dev_attr.attr,
+	&iio_const_attr_in_intensity_ir_range_available.dev_attr.attr,
+	&iio_dev_attr_sampling_frequency.dev_attr.attr,
+	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group si114x_attr_group_trigger = {
+	.attrs = si114x_attrs_trigger
+};
+
+static const struct iio_info si114x_info_trigger = {
+	.read_raw = si114x_read_raw,
+	.write_raw = si114x_write_raw,
+#if defined(CONFIG_DEBUG_FS)
+	.debugfs_reg_access = si114x_reg_access,
+#endif
+	.driver_module = THIS_MODULE,
+	.attrs = &si114x_attr_group_trigger
+};
+
+/* sysfs attributes if no IRQ available */
+static struct attribute *si114x_attrs_no_trigger[] = {
+	&iio_dev_attr_in_proximity_range.dev_attr.attr,
+	&iio_const_attr_in_proximity_range_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_range.dev_attr.attr,
+	&iio_const_attr_in_intensity_range_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_ir_range.dev_attr.attr,
+	&iio_const_attr_in_intensity_ir_range_available.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group si114x_attr_group_no_trigger = {
+	.attrs = si114x_attrs_no_trigger
+};
+
+static const struct iio_info si114x_info_no_trigger = {
+	.read_raw = si114x_read_raw,
+	.write_raw = si114x_write_raw,
+#if defined(CONFIG_DEBUG_FS)
+	.debugfs_reg_access = si114x_reg_access,
+#endif
+	.driver_module = THIS_MODULE,
+	.attrs = &si114x_attr_group_no_trigger
+};
+
+static int si114x_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct iio_buffer *buffer = indio_dev->buffer;
+
+	/* at least one channel besides the timestamp must be enabled */
+	if (!bitmap_weight(buffer->scan_mask, indio_dev->masklength))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int si114x_buffer_postenable(struct iio_dev *indio_dev)
+{
+	int ret;
+
+	ret = iio_triggered_buffer_postenable(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	/* measure only enabled channels */
+	ret = si114x_set_chlist(indio_dev, false);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int si114x_buffer_predisable(struct iio_dev *indio_dev)
+{
+	int ret;
+
+	/* measure all channels */
+	ret = si114x_set_chlist(indio_dev, true);
+	if (ret < 0)
+		return ret;
+
+	return iio_triggered_buffer_predisable(indio_dev);
+}
+
+static const struct iio_buffer_setup_ops si114x_buffer_setup_ops = {
+	.preenable = si114x_buffer_preenable,
+	.postenable = si114x_buffer_postenable,
+	.predisable = si114x_buffer_predisable,
+};
+
+static int si114x_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct si114x_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = iio_device_alloc(sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->use_irq = client->irq != 0;
+
+	ret = si114x_revisions(data);
+	if (ret < 0)
+		goto error_free_dev;
+
+	dev_info(&client->dev, "Si11%02x Ambient light/proximity sensor, Seq: %02x\n",
+		data->part, data->seq);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->name = SI114X_DRV_NAME;
+	indio_dev->channels = si114x_channels;
+	/* Compute number of channels: use of last six channels depends on
+	 * the number of proximity LEDs supported (determined by the part
+	 * number: si1141, si1142, si114x).
+	 * Each proximity LED has an input intensity and output voltage
+	 * channel.
+	 */
+	indio_dev->num_channels = ARRAY_SIZE(si114x_channels) -
+		6 + si114x_leds(data)*2;
+	indio_dev->info = data->use_irq ?
+		&si114x_info_trigger : &si114x_info_no_trigger;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	mutex_init(&data->mutex);
+	init_waitqueue_head(&data->data_avail);
+
+	ret = si114x_initialize(indio_dev);
+	if (ret < 0)
+		goto error_free_dev;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		si114x_trigger_handler, &si114x_buffer_setup_ops);
+	if (ret < 0)
+		goto error_free_dev;
+
+	if (data->use_irq) {
+		ret = request_threaded_irq(client->irq,
+			   NULL, si114x_irq,
+			   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			   "si114x_irq", indio_dev);
+		if (ret < 0) {
+			dev_err(&client->dev, "irq request failed\n");
+			goto error_free_buffer;
+		}
+
+		ret = si114x_probe_trigger(indio_dev);
+		if (ret < 0)
+			goto error_free_irq;
+	} else
+		dev_info(&client->dev, "no irq, using polling\n");
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto error_free_trigger;
+
+	return 0;
+
+error_free_trigger:
+	if (data->use_irq)
+		si114x_remove_trigger(indio_dev);
+error_free_irq:
+	if (data->use_irq)
+		free_irq(client->irq, indio_dev);
+error_free_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+error_free_dev:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static const struct i2c_device_id si114x_id[] = {
+	{ "si114x", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si114x_id);
+
+static int si114x_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct si114x_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	if (data->use_irq) {
+		si114x_remove_trigger(indio_dev);
+		free_irq(client->irq, indio_dev);
+	}
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+static struct i2c_driver si114x_driver = {
+	.driver = {
+		.name   = SI114X_DRV_NAME,
+		.owner  = THIS_MODULE,
+	},
+	.probe  = si114x_probe,
+	.remove = si114x_remove,
+	.id_table = si114x_id,
+};
+
+module_i2c_driver(si114x_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Silabs si114x proximity/ambient light sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [RFC] iio: Add driver for Silabs si1132, si1141/2/3 and si1145/6/7 ambient light, uv index and proximity sensors
  2016-05-19 17:53 [RFC] iio: add driver for si114x ambient light / proximity sensors Crestez Dan Leonard
@ 2016-05-19 21:59 ` Peter Meerwald-Stadler
  2016-05-24 17:51   ` Crestez Dan Leonard
  0 siblings, 1 reply; 3+ messages in thread
From: Peter Meerwald-Stadler @ 2016-05-19 21:59 UTC (permalink / raw)
  To: Crestez Dan Leonard; +Cc: linux-iio, linux-kernel, jic23, Peter Meerwald

From: Peter Meerwald <pmeerw@pmeerw.net>

The si114x supports x=1,2,3 IR LEDs for proximity sensing together with
visible and IR ambient light sensing (ALS).

Newer parts (si1132, si1145/6/7) can measure UV light and compute an UV index

Arranging 3 IR LEDs in a triangular shape can be used for detection of swipe
gestures (the present driver only measures the intensities, it does not process
the data); there is an affordable reference design (via Digikey), see
http://www.silabs.com/products/sensors/Pages/HID-USB-to-IR-Reference-Design.aspx

Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net>
---
This is the code I am currently working on, partly tested. I've stripped some
features from earlier versions and added support for newer chips.
I hope this is useful to Crestez Dan Leonard. I'm happy to review stuff
as the code gets forged into shape and appreciate any testing.

 drivers/iio/light/Kconfig  |  13 +
 drivers/iio/light/Makefile |   1 +
 drivers/iio/light/si1145.c | 855 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 869 insertions(+)
 create mode 100644 drivers/iio/light/si1145.c

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 7c566f5..c362e63 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -266,6 +266,19 @@ config PA12203001
          This driver can also be built as a module.  If so, the module
          will be called pa12203001.
 
+config SI1145
+	tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say Y here if you want to build a driver for the Silicon Labs SI1132 or
+	  SI1141/2/3/5/6/7 combined ambient light, UV index and proximity sensor
+	  chips.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called si1145.
+
 config STK3310
 	tristate "STK3310 ALS and proximity sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 6f2a3c6..c5768df 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_OPT3001)		+= opt3001.o
 obj-$(CONFIG_PA12203001)	+= pa12203001.o
 obj-$(CONFIG_RPR0521)		+= rpr0521.o
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
+obj-$(CONFIG_SI1145)		+= si1145.o
 obj-$(CONFIG_STK3310)          += stk3310.o
 obj-$(CONFIG_TCS3414)		+= tcs3414.o
 obj-$(CONFIG_TCS3472)		+= tcs3472.o
diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c
new file mode 100644
index 0000000..b35e5b2
--- /dev/null
+++ b/drivers/iio/light/si1145.c
@@ -0,0 +1,855 @@
+/*
+ * si1145.c - Support for Silabs SI1132 and SI1141/2/3/5/6/7 combined ambient
+ * light, UV index and proximity sensors
+ *
+ * Copyright 2014 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License.  See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * SI1132 (7-bit I2C slave address 0x60)
+ * SI1141/2/3 (7-bit I2C slave address 0x5a)
+ * SI1145/6/6 (7-bit I2C slave address 0x60)
+ *
+ * TODO: sample freq, IRQ, power management
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/buffer.h>
+
+#define SI1145_REG_PART_ID		0x00
+#define SI1145_REG_REV_ID		0x01
+#define SI1145_REG_SEQ_ID		0x02
+#define SI1145_REG_INT_CFG		0x03
+#define SI1145_REG_IRQ_ENABLE		0x04
+#define SI1145_REG_IRQ_MODE		0x05
+#define SI1145_REG_HW_KEY		0x07
+#define SI1145_REG_MEAS_RATE		0x08
+#define SI1145_REG_PS_LED21		0x0f
+#define SI1145_REG_PS_LED3		0x10
+#define SI1145_REG_PARAM_WR		0x17
+#define SI1145_REG_COMMAND		0x18
+#define SI1145_REG_RESPONSE		0x20
+#define SI1145_REG_IRQ_STATUS		0x21
+#define SI1145_REG_ALSVIS_DATA		0x22
+#define SI1145_REG_ALSIR_DATA		0x24
+#define SI1145_REG_PS1_DATA		0x26
+#define SI1145_REG_PS2_DATA		0x28
+#define SI1145_REG_PS3_DATA		0x2a
+#define SI1145_REG_AUX_DATA		0x2c
+#define SI1145_REG_PARAM_RD		0x2e
+#define SI1145_REG_CHIP_STAT		0x30
+
+/* Helper to figure out PS_LED register / shift per channel */
+#define SI1145_PS_LED_REG(ch) \
+	(((ch) == 2) ? SI1145_REG_PS_LED3 : SI1145_REG_PS_LED21)
+#define SI1145_PS_LED_SHIFT(ch) \
+	(((ch) == 1) ? 4 : 0)
+
+/* Parameter offsets */
+#define SI1145_PARAM_CHLIST		0x01
+#define SI1145_PARAM_PSLED12_SELECT	0x02
+#define SI1145_PARAM_PSLED3_SELECT	0x03
+#define SI1145_PARAM_PS_ENCODING	0x05
+#define SI1145_PARAM_ALS_ENCODING	0x06
+#define SI1145_PARAM_PS1_ADC_MUX	0x07
+#define SI1145_PARAM_PS2_ADC_MUX	0x08
+#define SI1145_PARAM_PS3_ADC_MUX	0x09
+#define SI1145_PARAM_PS_ADC_COUNTER	0x0a
+#define SI1145_PARAM_PS_ADC_GAIN	0x0b
+#define SI1145_PARAM_PS_ADC_MISC	0x0c
+#define SI1145_PARAM_ALS_ADC_MUX	0x0d
+#define SI1145_PARAM_ALSIR_ADC_MUX	0x0e
+#define SI1145_PARAM_AUX_ADC_MUX	0x0f
+#define SI1145_PARAM_ALSVIS_ADC_COUNTER	0x10
+#define SI1145_PARAM_ALSVIS_ADC_GAIN	0x11
+#define SI1145_PARAM_ALSVIS_ADC_MISC	0x12
+#define SI1145_PARAM_LED_RECOVERY	0x1c
+#define SI1145_PARAM_ALSIR_ADC_COUNTER	0x1d
+#define SI1145_PARAM_ALSIR_ADC_GAIN	0x1e
+#define SI1145_PARAM_ALSIR_ADC_MISC	0x1f
+
+/* Channel enable masks for CHLIST parameter */
+#define SI1145_CHLIST_EN_PS1		0x01
+#define SI1145_CHLIST_EN_PS2		0x02
+#define SI1145_CHLIST_EN_PS3		0x04
+#define SI1145_CHLIST_EN_ALSVIS		0x10
+#define SI1145_CHLIST_EN_ALSIR		0x20
+#define SI1145_CHLIST_EN_AUX		0x40
+#define SI1145_CHLIST_EN_UV		0x80
+
+/* Signal range mask for ADC_MISC parameter */
+#define SI1145_ADC_MISC_RANGE		0x20
+
+/* Commands for REG_COMMAND */
+#define SI1145_CMD_NOP			0x00
+#define SI1145_CMD_RESET		0x01
+#define SI1145_CMD_PS_FORCE		0x05
+#define SI1145_CMD_ALS_FORCE		0x06
+#define SI1145_CMD_PSALS_FORCE		0x07
+#define SI1145_CMD_PS_PAUSE		0x09
+#define SI1145_CMD_ALS_PAUSE		0x0a
+#define SI1145_CMD_PSALS_PAUSE		0x0b
+#define SI1145_CMD_PS_AUTO		0x0d
+#define SI1145_CMD_ALS_AUTO		0x0e
+#define SI1145_CMD_PSALS_AUTO		0x0f
+#define SI1145_CMD_PARAM_QUERY		0x80
+#define SI1145_CMD_PARAM_SET		0xa0
+
+/* Interrupt configuration masks for INT_CFG register */
+#define SI1145_INT_CFG_OE		0x01 /* enable interrupt */
+#define SI1145_INT_CFG_MODE		0x02 /* auto reset interrupt pin */
+
+/* Interrupt enable masks for IRQ_ENABLE register */
+#define SI1145_PS3_IE			0x10
+#define SI1145_PS2_IE			0x08
+#define SI1145_PS1_IE			0x04
+#define SI1145_ALS_IE			0x01
+
+#define SI1145_MUX_TEMP			0x65
+#define SI1145_MUX_VDD			0x75
+
+enum {
+	SI1132,
+	SI1141,
+	SI1142,
+	SI1143,
+	SI1145,
+	SI1146,
+	SI1147,
+};
+
+struct si1145_part_info {
+	u8 part;
+	const struct iio_chan_spec *channels;
+	unsigned int num_channels;
+	unsigned int num_leds;
+	bool new;
+};
+
+/**
+ * struct si1145_data - si1145 chip state data
+ * @client:	I2C client
+ * @lock:	mutex to protect multi-step I2C accesses and state changes
+ * @part:	chip part number (0x45, 0x46, 0x47) for 1 to 3 LEDs version
+ *
+ **/
+struct si1145_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	const struct si1145_part_info *part_info;
+	unsigned long scan_mask;
+};
+
+/*
+ * Helper function to operate on parameter values: op can be query or set
+ * Function returns (modified) value and needs locking
+ */
+static int __si1145_param(struct si1145_data *data, u8 op, u8 param, u8 value)
+{
+	int ret;
+
+	if (op != SI1145_CMD_PARAM_QUERY) {
+		ret = i2c_smbus_write_byte_data(data->client,
+			SI1145_REG_PARAM_WR, value);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND,
+		op | (param & 0x1F));
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_read_byte_data(data->client, SI1145_REG_PARAM_RD);
+}
+
+static int si1145_param(struct si1145_data *data, u8 op, u8 param, u8 value)
+{
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = __si1145_param(data, op, param, value);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static irqreturn_t si1145_trigger_handler(int irq, void *private)
+{
+	struct iio_poll_func *pf = private;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct si1145_data *data = iio_priv(indio_dev);
+	/*
+	 * Maximum buffer size:
+	 *   6*2 bytes channels data + 4 bytes alignment +
+	 *   8 bytes timestamp
+	 */
+	u8 buffer[24];
+	int i, j = 0;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+		SI1145_REG_COMMAND, SI1145_CMD_PSALS_FORCE);
+	if (ret < 0)
+		goto done;
+	msleep(10);
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		int run = 1;
+
+		while (i + run < indio_dev->masklength) {
+			if (test_bit(i + run, indio_dev->active_scan_mask))
+				run++;
+			else
+				break;
+		}
+
+		if (run > 1) {
+			ret = i2c_smbus_read_i2c_block_data(data->client,
+				indio_dev->channels[i].address,
+				sizeof(u16) * run, &buffer[j]);
+		} else {
+			ret = i2c_smbus_read_word_data(data->client,
+				indio_dev->channels[i].address);
+			*(u16 *) &buffer[j] = ret;
+		}
+		if (ret < 0)
+			goto done;
+		j += run * sizeof(u16);
+		i += run - 1;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, buffer,
+		iio_get_time_ns());
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int si1145_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 reg = 0, mux;
+	int ret;
+	int i;
+
+	/* channel list already set, no need to reprogram */
+	if (data->scan_mask == scan_mask)
+		return 0;
+
+	for_each_set_bit(i, &scan_mask, indio_dev->masklength) {
+		switch (indio_dev->channels[i].address) {
+		case SI1145_REG_ALSVIS_DATA:
+			reg |= SI1145_CHLIST_EN_ALSVIS;
+			break;
+		case SI1145_REG_ALSIR_DATA:
+			reg |= SI1145_CHLIST_EN_ALSIR;
+			break;
+		case SI1145_REG_PS1_DATA:
+			reg |= SI1145_CHLIST_EN_PS1;
+			break;
+		case SI1145_REG_PS2_DATA:
+			reg |= SI1145_CHLIST_EN_PS2;
+			break;
+		case SI1145_REG_PS3_DATA:
+			reg |= SI1145_CHLIST_EN_PS3;
+			break;
+		case SI1145_REG_AUX_DATA:
+			switch (indio_dev->channels[i].type) {
+			case IIO_UVINDEX:
+				reg |= SI1145_CHLIST_EN_UV;
+				break;
+			default:
+				reg |= SI1145_CHLIST_EN_AUX;
+				if (indio_dev->channels[i].type == IIO_TEMP)
+					mux = SI1145_MUX_TEMP;
+				else
+					mux = SI1145_MUX_VDD;
+				ret = __si1145_param(data, SI1145_CMD_PARAM_SET,
+					SI1145_PARAM_AUX_ADC_MUX, mux);
+				if (ret < 0)
+					return ret;
+
+				break;
+			}
+		}
+	}
+
+	data->scan_mask = scan_mask;
+	return __si1145_param(data, SI1145_CMD_PARAM_SET, SI1145_PARAM_CHLIST,
+		reg);
+}
+
+static int si1145_measure(struct iio_dev *indio_dev,
+			  struct iio_chan_spec const *chan)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 cmd;
+	int ret;
+
+	ret = si1145_set_chlist(indio_dev, BIT(chan->scan_index));
+	if (ret < 0)
+		return ret;
+
+	cmd = (chan->type == IIO_PROXIMITY) ? SI1145_CMD_PS_FORCE :
+		SI1145_CMD_ALS_FORCE;
+	ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, cmd);
+	if (ret < 0)
+		return ret;
+
+	msleep(20);
+
+	return i2c_smbus_read_word_data(data->client, chan->address);
+}
+
+static int si1145_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	int ret;
+	u8 reg;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_PROXIMITY:
+		case IIO_VOLTAGE:
+		case IIO_TEMP:
+			if (iio_buffer_enabled(indio_dev))
+				return -EBUSY;
+
+			mutex_lock(&data->lock);
+			ret = si1145_measure(indio_dev, chan);
+			mutex_unlock(&data->lock);
+			if (ret < 0)
+				return ret;
+
+			*val = ret;
+
+			return IIO_VAL_INT;
+		case IIO_CURRENT:
+			ret = i2c_smbus_read_byte_data(data->client,
+				SI1145_PS_LED_REG(chan->channel));
+			if (ret < 0)
+				return ret;
+
+			*val = (ret >> SI1145_PS_LED_SHIFT(chan->channel))
+				& 0x0f;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			reg = SI1145_PARAM_PS_ADC_GAIN;
+			break;
+		case IIO_INTENSITY:
+			if (chan->channel2 == IIO_MOD_LIGHT_IR)
+				reg = SI1145_PARAM_ALSIR_ADC_GAIN;
+			else
+				reg = SI1145_PARAM_ALSVIS_ADC_GAIN;
+			break;
+		case IIO_TEMP:
+			*val = 28;
+			*val2 = 571429;
+			return IIO_VAL_INT_PLUS_MICRO;
+		default:
+			return -EINVAL;
+		}
+
+		ret = si1145_param(data, SI1145_CMD_PARAM_QUERY, reg, 0);
+		if (ret < 0)
+			return ret;
+
+		*val = ret & 0x07;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_OFFSET:
+		/* -ADC offset - ADC counts @ 25°C - 35 * ADC counts / °C */
+		*val = -256 - 11136 + 25 * 35;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int si1145_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 reg1, reg2, shift;
+	int ret;
+
+	if (iio_buffer_enabled(indio_dev))
+		return -EBUSY;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			if (val < 0 || val > 5 || val2 != 0)
+				return -EINVAL;
+			reg1 = SI1145_PARAM_PS_ADC_GAIN;
+			reg2 = SI1145_PARAM_PS_ADC_COUNTER;
+			break;
+		case IIO_INTENSITY:
+			if (val < 0 || val > 7 || val2 != 0)
+				return -EINVAL;
+			if (chan->channel2 == IIO_MOD_LIGHT_IR) {
+				reg1 = SI1145_PARAM_ALSIR_ADC_GAIN;
+				reg2 = SI1145_PARAM_ALSIR_ADC_COUNTER;
+			} else {
+				reg1 = SI1145_PARAM_ALSVIS_ADC_GAIN;
+				reg2 = SI1145_PARAM_ALSVIS_ADC_COUNTER;
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = si1145_param(data, SI1145_CMD_PARAM_SET, reg1, val);
+		if (ret < 0)
+			return ret;
+		/* Set recovery period to one's complement of gain */
+		return si1145_param(data, SI1145_CMD_PARAM_SET,
+			reg2, (~val & 0x07) << 4);
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_CURRENT)
+			return -EINVAL;
+
+		if (val < 0 || val > 15 || val2 != 0)
+			return -EINVAL;
+
+		reg1 = SI1145_PS_LED_REG(chan->channel);
+		shift = SI1145_PS_LED_SHIFT(chan->channel);
+		ret = i2c_smbus_read_byte_data(data->client, reg1);
+		if (ret < 0)
+			return ret;
+		return i2c_smbus_write_byte_data(data->client, reg1,
+			(ret & ~(0x0f << shift)) |
+			((val & 0x0f) << shift));
+	}
+	return -EINVAL;
+}
+
+#define SI1145_ST { \
+	.sign = 'u', \
+	.realbits = 16, \
+	.storagebits = 16, \
+	.endianness = IIO_LE, \
+}
+
+#define SI1145_INTENSITY_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+		BIT(IIO_CHAN_INFO_SCALE), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_ALSVIS_DATA, \
+}
+
+#define SI1145_INTENSITY_IR_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+		BIT(IIO_CHAN_INFO_SCALE), \
+	.modified = 1, \
+	.channel2 = IIO_MOD_LIGHT_IR, \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_ALSIR_DATA, \
+}
+
+#define SI1145_TEMP_CHANNEL(_si) { \
+	.type = IIO_TEMP, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+		BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_UV_CHANNEL(_si) { \
+	.type = IIO_UVINDEX, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_PROXIMITY_CHANNEL(_si, _ch) { \
+	.type = IIO_PROXIMITY, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_PS1_DATA + _ch * 2, \
+}
+
+#define SI1145_VOLTAGE_CHANNEL(_si) { \
+	.type = IIO_VOLTAGE, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_CURRENT_CHANNEL(_ch) { \
+	.type = IIO_CURRENT, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.output = 1, \
+	.scan_index = -1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec si1132_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_TEMP_CHANNEL(2),
+	SI1145_VOLTAGE_CHANNEL(3),
+	SI1145_UV_CHANNEL(4),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+};
+
+static const struct iio_chan_spec si1141_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_TEMP_CHANNEL(3),
+	SI1145_VOLTAGE_CHANNEL(4),
+	IIO_CHAN_SOFT_TIMESTAMP(5),
+	SI1145_CURRENT_CHANNEL(0),
+};
+
+static const struct iio_chan_spec si1142_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_TEMP_CHANNEL(4),
+	SI1145_VOLTAGE_CHANNEL(5),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+};
+
+static const struct iio_chan_spec si1143_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_PROXIMITY_CHANNEL(4, 2),
+	SI1145_TEMP_CHANNEL(5),
+	SI1145_VOLTAGE_CHANNEL(6),
+	IIO_CHAN_SOFT_TIMESTAMP(7),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+	SI1145_CURRENT_CHANNEL(2),
+};
+
+static const struct iio_chan_spec si1145_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_TEMP_CHANNEL(3),
+	SI1145_VOLTAGE_CHANNEL(4),
+	SI1145_UV_CHANNEL(5),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+	SI1145_CURRENT_CHANNEL(0),
+};
+
+static const struct iio_chan_spec si1146_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_TEMP_CHANNEL(2),
+	SI1145_VOLTAGE_CHANNEL(3),
+	SI1145_UV_CHANNEL(4),
+	SI1145_PROXIMITY_CHANNEL(5, 0),
+	SI1145_PROXIMITY_CHANNEL(6, 1),
+	IIO_CHAN_SOFT_TIMESTAMP(7),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+};
+
+static const struct iio_chan_spec si1147_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_PROXIMITY_CHANNEL(4, 2),
+	SI1145_TEMP_CHANNEL(5),
+	SI1145_VOLTAGE_CHANNEL(6),
+	SI1145_UV_CHANNEL(7),
+	IIO_CHAN_SOFT_TIMESTAMP(8),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+	SI1145_CURRENT_CHANNEL(2),
+};
+
+#define SI1145_PART(id, chans, leds, new) \
+	{id, chans, ARRAY_SIZE(chans), leds, new}
+
+static const struct si1145_part_info si1145_part_info[] = {
+	[SI1132] = SI1145_PART(0x32, si1132_channels, 0, true),
+	[SI1141] = SI1145_PART(0x41, si1141_channels, 1, false),
+	[SI1142] = SI1145_PART(0x42, si1142_channels, 2, false),
+	[SI1143] = SI1145_PART(0x43, si1143_channels, 3, false),
+	[SI1145] = SI1145_PART(0x45, si1145_channels, 1, true),
+	[SI1146] = SI1145_PART(0x46, si1146_channels, 2, true),
+	[SI1147] = SI1145_PART(0x47, si1147_channels, 3, true),
+};
+
+static int si1145_set_meas_rate(struct si1145_data *data, int interval)
+{
+	/* newer parts use a 16-bit register instead of compression */
+	if (data->part_info->new)
+		return i2c_smbus_write_word_data(data->client,
+			SI1145_REG_MEAS_RATE, 0);
+	else
+		return i2c_smbus_write_byte_data(data->client,
+			SI1145_REG_MEAS_RATE, 0);
+}
+
+static int si1145_initialize(struct si1145_data *data)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, SI1145_REG_COMMAND,
+		SI1145_CMD_RESET);
+	if (ret < 0)
+		return ret;
+	msleep(20);
+
+	/* Hardware key, magic value */
+	ret = i2c_smbus_write_byte_data(client, SI1145_REG_HW_KEY, 0x17);
+	if (ret < 0)
+		return ret;
+	msleep(20);
+
+	/* Turn off autonomous mode */
+	ret = si1145_set_meas_rate(data, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Set LED currents to 45 mA */
+	switch (data->part_info->num_leds) {
+	case 1:
+		ret = i2c_smbus_write_byte_data(client,
+			SI1145_REG_PS_LED21, 0x03);
+		break;
+	case 2:
+		ret = i2c_smbus_write_byte_data(client,
+			SI1145_REG_PS_LED21, 0x43);
+		break;
+	case 3:
+		ret = i2c_smbus_write_byte_data(client,
+			SI1145_REG_PS_LED3, 0x03);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(client,
+			SI1145_REG_PS_LED21, 0x43);
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+	if (ret < 0)
+		return ret;
+
+	/* Set normal proximity measurement mode */
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_PS_ADC_MISC, 0x04);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_PS_ADC_GAIN, 0x01);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_PS_ADC_COUNTER, 0x03 << 4);
+	if (ret < 0)
+		return ret;
+
+	/* Set ALS visible measurement mode */
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSVIS_ADC_MISC, SI1145_ADC_MISC_RANGE);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSVIS_ADC_GAIN, 0x03);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSVIS_ADC_COUNTER, 0x04 << 4);
+	if (ret < 0)
+		return ret;
+
+	/* Set ALS IR measurement mode */
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSIR_ADC_MISC, SI1145_ADC_MISC_RANGE);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSIR_ADC_GAIN, 0x01);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param(data, SI1145_CMD_PARAM_SET,
+		SI1145_PARAM_ALSIR_ADC_COUNTER, 0x00 << 4);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static const struct iio_info si1145_info = {
+	.read_raw = si1145_read_raw,
+	.write_raw = si1145_write_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static int si1145_buffer_preenable(struct iio_dev *indio_dev)
+{
+	return si1145_set_chlist(indio_dev, *indio_dev->active_scan_mask);
+}
+
+bool si1145_validate_scan_mask(struct iio_dev *indio_dev,
+			       const unsigned long *scan_mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	unsigned int count = 0;
+	int i;
+
+	/* Check that at most one AUX channel is enabled */
+	for_each_set_bit(i, scan_mask, data->part_info->num_channels) {
+		if (indio_dev->channels[i].address == SI1145_REG_AUX_DATA)
+			count++;
+	}
+
+	return count <= 1;
+}
+
+static const struct iio_buffer_setup_ops si1145_buffer_setup_ops = {
+	.preenable = si1145_buffer_preenable,
+	.postenable = iio_triggered_buffer_postenable,
+	.predisable = iio_triggered_buffer_predisable,
+	.validate_scan_mask = si1145_validate_scan_mask,
+};
+
+static int si1145_probe(struct i2c_client *client,
+					const struct i2c_device_id *id)
+{
+	struct si1145_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->part_info = &si1145_part_info[id->driver_data];
+
+	ret = i2c_smbus_read_byte_data(data->client, SI1145_REG_PART_ID);
+	if (ret < 0)
+		return ret;
+	if (ret != data->part_info->part)
+		return -ENODEV;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->name = id->name;
+	indio_dev->channels = data->part_info->channels;
+	indio_dev->num_channels = data->part_info->num_channels;
+	indio_dev->info = &si1145_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	mutex_init(&data->lock);
+
+	ret = si1145_initialize(data);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		si1145_trigger_handler, &si1145_buffer_setup_ops);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto error_free_buffer;
+
+	return 0;
+
+error_free_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+	return ret;
+}
+
+static const struct i2c_device_id si1145_ids[] = {
+	{ "si1132", SI1132 },
+	{ "si1141", SI1141 },
+	{ "si1142", SI1142 },
+	{ "si1143", SI1143 },
+	{ "si1145", SI1145 },
+	{ "si1146", SI1146 },
+	{ "si1147", SI1147 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si1145_ids);
+
+static int si1145_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+
+	return 0;
+}
+
+static struct i2c_driver si1145_driver = {
+	.driver = {
+		.name   = "si1145",
+		.owner  = THIS_MODULE,
+	},
+	.probe  = si1145_probe,
+	.remove = si1145_remove,
+	.id_table = si1145_ids,
+};
+
+module_i2c_driver(si1145_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Silabs SI1132 and SI1141/2/3/5/6/7 proximity, ambient light and UV index sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [RFC] iio: Add driver for Silabs si1132, si1141/2/3 and si1145/6/7 ambient light, uv index and proximity sensors
  2016-05-19 21:59 ` [RFC] iio: Add driver for Silabs si1132, si1141/2/3 and si1145/6/7 ambient light, uv index and " Peter Meerwald-Stadler
@ 2016-05-24 17:51   ` Crestez Dan Leonard
  0 siblings, 0 replies; 3+ messages in thread
From: Crestez Dan Leonard @ 2016-05-24 17:51 UTC (permalink / raw)
  To: Peter Meerwald-Stadler; +Cc: linux-iio, linux-kernel, jic23, Daniel Baluta

On 05/20/2016 12:59 AM, Peter Meerwald-Stadler wrote:
> From: Peter Meerwald <pmeerw@pmeerw.net>
> 
> The si114x supports x=1,2,3 IR LEDs for proximity sensing together with
> visible and IR ambient light sensing (ALS).
> 
> Newer parts (si1132, si1145/6/7) can measure UV light and compute an UV index
> 
> Arranging 3 IR LEDs in a triangular shape can be used for detection of swipe
> gestures (the present driver only measures the intensities, it does not process
> the data); there is an affordable reference design (via Digikey), see
> http://www.silabs.com/products/sensors/Pages/HID-USB-to-IR-Reference-Design.aspx
> 
> Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net>
> ---
> This is the code I am currently working on, partly tested. I've stripped some
> features from earlier versions and added support for newer chips.
> I hope this is useful to Crestez Dan Leonard. I'm happy to review stuff
> as the code gets forged into shape and appreciate any testing.

This driver seems to have a somewhat divergent set of features compared
to the older one I found:
* https://www.spinics.net/lists/linux-iio/msg06468.html

Notable differences:
- si114x only support 41/42/43
- si114x supports dataready irqs and putting the device in "auto" mode.
- si114x supports controlling sampling frequency (which only makes sense
with irq support)
- si114x has sysfs entries for controlling normal/high signal range.
- si114x exposes parameter ram via debugfs
- si114x exposes HARDWAREGAIN instead of SCALE. The latter seems better
anyway.
- Driver id is different

There seems to be a driver based on your old si114x in the chromiumos
tree but I don't think it's used much. Still it makes me think if it's
worth using si114x as the name in the interest of devicetree binding
compatibility.

I also looked through the code and found some small issues (mentioned
inline). I guess I should fix them, add support for interrupts and post
it for inclusion again, right?

I have both a si1143 and a si1145 I can test on.

Another issue is that there is no support for "illuminance" in lux.
Datasheet recommends determining this on the based on some interpolation
of ALSVIS an ALSIR. I guess it would be possible to cobble something
together based on the sensitivities mentioned in the datasheet but I'd
rather not attempt this.

> +++ b/drivers/iio/light/si1145.c
> @@ -0,0 +1,855 @@
> +/*
> + * Helper function to operate on parameter values: op can be query or set
> + * Function returns (modified) value and needs locking
> + */
> +static int __si1145_param(struct si1145_data *data, u8 op, u8 param, u8 value)
> +{
> +	int ret;
> +
> +	if (op != SI1145_CMD_PARAM_QUERY) {
> +		ret = i2c_smbus_write_byte_data(data->client,
> +			SI1145_REG_PARAM_WR, value);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND,
> +		op | (param & 0x1F));
> +	if (ret < 0)
> +		return ret;
> +
> +	return i2c_smbus_read_byte_data(data->client, SI1145_REG_PARAM_RD);

What is the point of reading back the value that was just read? It would
be faster to avoid this when doing a PARAM_SET command. Better yet
si1145_param_query and si1145_param_set should be distinct functions.

The datasheet recommends always checking the 'response' register after
every command but this doesn't seem to be implemented anywhere.

> +static irqreturn_t si1145_trigger_handler(int irq, void *private)
> +{
> +	struct iio_poll_func *pf = private;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct si1145_data *data = iio_priv(indio_dev);
> +	/*
> +	 * Maximum buffer size:
> +	 *   6*2 bytes channels data + 4 bytes alignment +
> +	 *   8 bytes timestamp
> +	 */
> +	u8 buffer[24];
> +	int i, j = 0;
> +	int ret;
> +
> +	ret = i2c_smbus_write_byte_data(data->client,
> +		SI1145_REG_COMMAND, SI1145_CMD_PSALS_FORCE);
> +	if (ret < 0)
> +		goto done;
> +	msleep(10);

I guess this means only software triggers are supported, and at a
relatively low frequency?

> +	for_each_set_bit(i, indio_dev->active_scan_mask,
> +		indio_dev->masklength) {
> +		int run = 1;
> +
> +		while (i + run < indio_dev->masklength) {
> +			if (test_bit(i + run, indio_dev->active_scan_mask))
> +				run++;

Doesn't this assume that channels with consecutive scan indices will
have consecutive addresses as well? Looking at channel definitions that
doesn't seem to be always true. It is false for AUX channels on devices
with less than 3 proximity channels.

> +static int si1145_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	return si1145_set_chlist(indio_dev, *indio_dev->active_scan_mask);

The si1145_set_chlist function can return a positive value on success
but iio interprets any non-zero result from buffer_ops.preenable as an
error.

-- 
Regards,
Leonard

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2016-05-24 17:52 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-19 17:53 [RFC] iio: add driver for si114x ambient light / proximity sensors Crestez Dan Leonard
2016-05-19 21:59 ` [RFC] iio: Add driver for Silabs si1132, si1141/2/3 and si1145/6/7 ambient light, uv index and " Peter Meerwald-Stadler
2016-05-24 17:51   ` Crestez Dan Leonard

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).