Hello, > There is a slightly confusing divide between the trigger and the buffer > configuration but this is hardly the first device where that line has > been blurred. Also why run with all channels enabled whenever the buffer > is turned off? thank you for the comments and the suggestion regarding the irq top/bottom half handler; I'll change the all-channels-enabled default; more replies inline p. > > 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 > > > > v2: based on comments by Jonathan Cameron and Lars-Peter Clausen > > * description of the chip's functionality (above) as requested > > * kerneldocs for the data structure > > * drop platform data (use of irq is determined by i2c_client->irq; > > irq_flags is TRIGGER_FALLING always) > > * provide comment and referene for _compress()/_uncompress() > > * drop timestamp from iio_push_to_buffer() call > > * select from constant attribute structs, no dynamic allocation > > * use constant channels array with proximity channels last, > > compute num_channels based on chip part > > * drop update_scan_mode(), use local buffer in trigger function > > * cleanup access function to chip parameter values > > * simplify switches for readability > > * more testing and fixes > > > > Signed-off-by: Peter Meerwald <pmeerw@xxxxxxxxxx> > > --- > > drivers/iio/light/Kconfig | 13 + > > drivers/iio/light/si114x.c | 1185 ++++++++++++++++++++++++++++++++++++++++++++ > > 2 files changed, 1198 insertions(+) > > create mode 100644 drivers/iio/light/si114x.c > > > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > > index 1763c9b..9ad512f 100644 > > --- a/drivers/iio/light/Kconfig > > +++ b/drivers/iio/light/Kconfig > > @@ -32,6 +32,19 @@ config SENSORS_LM3533 > > changes. The ALS-control output values can be set per zone for the > > three current output channels. > > > > +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 VCNL4000 > > tristate "VCNL4000 combined ALS and proximity sensor" > > depends on I2C > > diff --git a/drivers/iio/light/si114x.c b/drivers/iio/light/si114x.c > > new file mode 100644 > > index 0000000..1636c0e > > --- /dev/null > > +++ b/drivers/iio/light/si114x.c > > @@ -0,0 +1,1185 @@ > > +/* > > + * si114x.c - Support for Silabs si114x combined ambient light and > > + * proximity sensor > > + * > > + * Copyright 2012 Peter Meerwald <pmeerw@xxxxxxxxxx> > > + * > > + * 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 > > Out of curiosity are thresholds the reason you enable all channels > when not in buffered mode? Thinking about it, with the multiple buffer > code I haven't covered the case where the scan mode changes for reasons > other than the channels being wanted by some buffer. That's going > to be 'interesting' to handle properly. Might have to cheat with some > pseudo buffer just to enforce channels being read and then do rip down > and setup as the enabled threshold interrupts > changes. Oh well, one for another day. the interrupt can be configured to fired (i) upon each completed measurement (this is supported), or (ii) when a measurement value if above a threshold, or (iii) a measurement value crosses a threshold threshold can be per-channel further, the chip supports different measurement rates: e.g. the proximity channels are sampled 10 times/sec while the ALS is sampled only every 10th measurement (not supported by the driver) > > + * 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; > Can this actually happen? Autonomous seems to be turned on as > part of trigger set state. Also the trigger handler is running > in response to an interrupt that I'd assumed was comming from the device? > Or is this to do with whether the device is using it's own trigger? > If so that should be checked directly rather than via this route > that just means something is using that trigger (perhaps another driver). I am not sure if I understand correctly; the "if (!data->autonomous)" block is here to handle the case when an external trigger is used (such as iio-trig-sysfs) so si114x_trigger_handler() does not necessarily run in response to an interrupt generated by the device "should be checked directly" is not clear to me > > + 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_buffer(indio_dev->buffer, 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, 0); > > + 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 = trig->private_data; > > + 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; > > + data->trig->private_data = 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 = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ > > + IIO_CHAN_INFO_HARDWAREGAIN_SEPARATE_BIT, \ > > + .scan_type = IIO_ST('u', 16, 16, 0), \ > > + .scan_index = _si, \ > > + .address = SI114X_REG_ALSVIS_DATA0, \ > > +} > > + > > +#define SI114X_INTENSITY_IR_CHANNEL(_si) { \ > > + .type = IIO_INTENSITY, \ > > + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ > > + IIO_CHAN_INFO_HARDWAREGAIN_SEPARATE_BIT, \ > > + .modified = 1, \ > > + .channel2 = IIO_MOD_LIGHT_IR, \ > > + .scan_type = IIO_ST('u', 16, 16, 0), \ > > + .scan_index = _si, \ > > + .address = SI114X_REG_ALSIR_DATA0 \ > > +} > > + > > +#define SI114X_TEMP_CHANNEL(_si) { \ > > + .type = IIO_TEMP, \ > > + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, \ > > + .scan_type = IIO_ST('u', 16, 16, 0), \ > > + .scan_index = _si, \ > > + .address = SI114X_REG_AUX_DATA0 \ > > +} > > + > > +#define SI114X_PROXIMITY_CHANNEL(_si, _ch) { \ > > + .type = IIO_PROXIMITY, \ > > + .indexed = 1, \ > > + .channel = _ch, \ > > + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ > > + IIO_CHAN_INFO_HARDWAREGAIN_SHARED_BIT, \ > > + .scan_type = IIO_ST('u', 16, 16, 0), \ > > + .scan_index = _si, \ > > + .address = SI114X_REG_PS1_DATA0 + _ch*2 \ > > +} > > + > > +#define SI114X_CURRENT_CHANNEL(_ch) { \ > > + .type = IIO_CURRENT, \ > > + .indexed = 1, \ > > + .channel = _ch, \ > > + .output = 1, \ > > + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT \ > > +} > > + > > +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); > Range is normally defined as numeric. Any way of mapping these to appropriate > numbers? range is a bit that influences the ADC stage (there are two modes: 'normal signal range' and 'high signal range', in the later case the gain is internally divided by 14.5) -- so maybe rename the attribute to 'mode' instead of 'range'? > > +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 */ > A little odd if the interrupt isn't there? I'll rearrange the code to setup interrupts only when use_irq is true > > + 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; > Given this only effects slow sysfs reading I'd be tempted not > to put the device in autonomous mode but rather trigger a sample > whenever one is requested... Is there a reason not to do this? set_chlist() configures the channels to sample during a measurement event (a measurement can be forced (as is done for a sysfs read) or performed automomously at a configured rate); the idea was to enable all channels per default so that no reprogramming is necessary for a sysfs channel read (a bit wasteful since all channels are sampled, but only one value is reported) I can change this behaviour: default to no enabled channels and do the chlist() setup specific to each sysfs read (with proper locking) > > + > > + /* 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, > If sampling frequency has not other effect on the reading (other than > how often it is taken) it should probably be an attribute of the trigger > not the full device. Often we have it here because it also effects > filtering etc and hence readings even when not using the trigger. I'll move the attribute to the trigger > > + 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 */ > Why? Presumably because the interrupt won't occur otherwise, but that > should be stated here. Might be better to have the trigger start fail > unless the channel is enabled as it is there that the restriction occurs > not here in the buffer code. correct: no measurement, to interrupt > > + if (!bitmap_weight(buffer->scan_mask, indio_dev->masklength)) > > + return -EINVAL; > > + > > + return iio_sw_buffer_preenable(indio_dev); > > +} > > + > > +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 */ > Why would we measure other channels? Very similar otherwise to > using the update_scan_mode callback. chlist is configured to all channels per default; this will change... > > + 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 */ > Why? Or do you mean set it to measure all channels on next request? > Why can't we set it to the approriate value on read request? will do as suggested (issue also discussed above) > > + 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 __devinit 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 > > > + /* 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; > Do you ever add the buffered mode to this? > Without that you won't get the trigger consumer stuff so won't be able > to change the trigger... indio_dev->modes |= INDIO_BUFFER_TRIGGERED; is done in iio_triggered_buffer_setup(), called below > > + > > + 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"); > I wonder if more detail would be useful in this message. What > is it polling for? my suggestion: "no irq, using data ready polling" > > + > > + 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 __devexit 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); > > + } > small point but I'd normally expect the ordering in here to be the same as the > error conditions for probe (and hence reverse of the setup in probe). That > would I think mean the iio_triggered_buffer_cleanup would be after the > irq cleanup? will do > > + 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 = __devexit_p(si114x_remove), > > + .id_table = si114x_id, > > +}; > > + > > +module_i2c_driver(si114x_driver); > > + > > +MODULE_AUTHOR("Peter Meerwald <pmeerw@xxxxxxxxxx>"); > > +MODULE_DESCRIPTION("Silabs si114x proximity/ambient light sensor driver"); > > +MODULE_LICENSE("GPL"); > > -- Peter Meerwald +43-664-2444418 (mobile) -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html