On 19/04/15 10:10, Kuppuswamy Sathyanarayanan wrote: > This patch adds interrupt support for Liteon 501 chip. > > Interrupt will be generated whenever ALS or proximity > data exceeds values given in upper and lower threshold > register settings. > > Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@xxxxxxxxxxxxxxx> Applied to the togreg branch of iio.git (testing) Thanks, Jonathan > --- > drivers/iio/light/ltr501.c | 319 ++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 313 insertions(+), 6 deletions(-) > > diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c > index 8092604..593dbb1 100644 > --- a/drivers/iio/light/ltr501.c > +++ b/drivers/iio/light/ltr501.c > @@ -9,7 +9,7 @@ > * > * 7-bit I2C slave address 0x23 > * > - * TODO: interrupt, threshold, measurement rate, IR LED characteristics > + * TODO: measurement rate, IR LED characteristics > */ > > #include <linux/module.h> > @@ -19,6 +19,7 @@ > #include <linux/regmap.h> > > #include <linux/iio/iio.h> > +#include <linux/iio/events.h> > #include <linux/iio/sysfs.h> > #include <linux/iio/trigger_consumer.h> > #include <linux/iio/buffer.h> > @@ -35,6 +36,11 @@ > #define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ > #define LTR501_ALS_PS_STATUS 0x8c > #define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ > +#define LTR501_INTR 0x8f /* output mode, polarity, mode */ > +#define LTR501_PS_THRESH_UP 0x90 /* 11 bit, ps upper threshold */ > +#define LTR501_PS_THRESH_LOW 0x92 /* 11 bit, ps lower threshold */ > +#define LTR501_ALS_THRESH_UP 0x97 /* 16 bit, ALS upper threshold */ > +#define LTR501_ALS_THRESH_LOW 0x99 /* 16 bit, ALS lower threshold */ > #define LTR501_MAX_REG 0x9f > > #define LTR501_ALS_CONTR_SW_RESET BIT(2) > @@ -43,10 +49,14 @@ > #define LTR501_CONTR_ALS_GAIN_MASK BIT(3) > #define LTR501_CONTR_ACTIVE BIT(1) > > +#define LTR501_STATUS_ALS_INTR BIT(3) > #define LTR501_STATUS_ALS_RDY BIT(2) > +#define LTR501_STATUS_PS_INTR BIT(1) > #define LTR501_STATUS_PS_RDY BIT(0) > > #define LTR501_PS_DATA_MASK 0x7ff > +#define LTR501_PS_THRESH_MASK 0x7ff > +#define LTR501_ALS_THRESH_MASK 0xffff > > #define LTR501_REGMAP_NAME "ltr501_regmap" > > @@ -54,6 +64,10 @@ static const int int_time_mapping[] = {100000, 50000, 200000, 400000}; > > static const struct reg_field reg_field_it = > REG_FIELD(LTR501_ALS_MEAS_RATE, 3, 4); > +static const struct reg_field reg_field_als_intr = > + REG_FIELD(LTR501_INTR, 0, 0); > +static const struct reg_field reg_field_ps_intr = > + REG_FIELD(LTR501_INTR, 1, 1); > > struct ltr501_data { > struct i2c_client *client; > @@ -61,6 +75,8 @@ struct ltr501_data { > u8 als_contr, ps_contr; > struct regmap *regmap; > struct regmap_field *reg_it; > + struct regmap_field *reg_als_intr; > + struct regmap_field *reg_ps_intr; > }; > > static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask) > @@ -161,7 +177,41 @@ static int ltr501_read_ps(struct ltr501_data *data) > return status; > } > > -#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \ > +static const struct iio_event_spec ltr501_als_event_spec[] = { > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_RISING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_FALLING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_EITHER, > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + }, > + > +}; > + > +static const struct iio_event_spec ltr501_pxs_event_spec[] = { > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_RISING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_FALLING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_EITHER, > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + }, > +}; > + > +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared, \ > + _evspec, _evsize) { \ > .type = IIO_INTENSITY, \ > .modified = 1, \ > .address = (_addr), \ > @@ -174,14 +224,19 @@ static int ltr501_read_ps(struct ltr501_data *data) > .realbits = 16, \ > .storagebits = 16, \ > .endianness = IIO_CPU, \ > - } \ > + }, \ > + .event_spec = _evspec,\ > + .num_event_specs = _evsize,\ > } > > static const struct iio_chan_spec ltr501_channels[] = { > - LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0), > + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, > + ltr501_als_event_spec, > + ARRAY_SIZE(ltr501_als_event_spec)), > LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, > BIT(IIO_CHAN_INFO_SCALE) | > - BIT(IIO_CHAN_INFO_INT_TIME)), > + BIT(IIO_CHAN_INFO_INT_TIME), > + NULL, 0), > { > .type = IIO_PROXIMITY, > .address = LTR501_PS_DATA, > @@ -194,6 +249,8 @@ static const struct iio_chan_spec ltr501_channels[] = { > .storagebits = 16, > .endianness = IIO_CPU, > }, > + .event_spec = ltr501_pxs_event_spec, > + .num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec), > }, > IIO_CHAN_SOFT_TIMESTAMP(3), > }; > @@ -329,6 +386,185 @@ static int ltr501_write_raw(struct iio_dev *indio_dev, > return -EINVAL; > } > > +static int ltr501_read_thresh(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, > + int *val, int *val2) > +{ > + struct ltr501_data *data = iio_priv(indio_dev); > + int ret, thresh_data; > + > + switch (chan->type) { > + case IIO_INTENSITY: > + switch (dir) { > + case IIO_EV_DIR_RISING: > + ret = regmap_bulk_read(data->regmap, > + LTR501_ALS_THRESH_UP, > + &thresh_data, 2); > + if (ret < 0) > + return ret; > + *val = thresh_data & LTR501_ALS_THRESH_MASK; > + return IIO_VAL_INT; > + case IIO_EV_DIR_FALLING: > + ret = regmap_bulk_read(data->regmap, > + LTR501_ALS_THRESH_LOW, > + &thresh_data, 2); > + if (ret < 0) > + return ret; > + *val = thresh_data & LTR501_ALS_THRESH_MASK; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > + case IIO_PROXIMITY: > + switch (dir) { > + case IIO_EV_DIR_RISING: > + ret = regmap_bulk_read(data->regmap, > + LTR501_PS_THRESH_UP, > + &thresh_data, 2); > + if (ret < 0) > + return ret; > + *val = thresh_data & LTR501_PS_THRESH_MASK; > + return IIO_VAL_INT; > + case IIO_EV_DIR_FALLING: > + ret = regmap_bulk_read(data->regmap, > + LTR501_PS_THRESH_LOW, > + &thresh_data, 2); > + if (ret < 0) > + return ret; > + *val = thresh_data & LTR501_PS_THRESH_MASK; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > + > + return -EINVAL; > +} > + > +static int ltr501_write_thresh(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, > + int val, int val2) > +{ > + struct ltr501_data *data = iio_priv(indio_dev); > + int ret; > + > + if (val < 0) > + return -EINVAL; > + > + switch (chan->type) { > + case IIO_INTENSITY: > + if (val > LTR501_ALS_THRESH_MASK) > + return -EINVAL; > + switch (dir) { > + case IIO_EV_DIR_RISING: > + mutex_lock(&data->lock_als); > + ret = regmap_bulk_write(data->regmap, > + LTR501_ALS_THRESH_UP, > + &val, 2); > + mutex_unlock(&data->lock_als); > + return ret; > + case IIO_EV_DIR_FALLING: > + mutex_lock(&data->lock_als); > + ret = regmap_bulk_write(data->regmap, > + LTR501_ALS_THRESH_LOW, > + &val, 2); > + mutex_unlock(&data->lock_als); > + return ret; > + default: > + return -EINVAL; > + } > + case IIO_PROXIMITY: > + switch (dir) { > + if (val > LTR501_PS_THRESH_MASK) > + return -EINVAL; > + case IIO_EV_DIR_RISING: > + mutex_lock(&data->lock_ps); > + ret = regmap_bulk_write(data->regmap, > + LTR501_PS_THRESH_UP, > + &val, 2); > + mutex_unlock(&data->lock_ps); > + return ret; > + case IIO_EV_DIR_FALLING: > + mutex_lock(&data->lock_ps); > + ret = regmap_bulk_write(data->regmap, > + LTR501_PS_THRESH_LOW, > + &val, 2); > + mutex_unlock(&data->lock_ps); > + return ret; > + default: > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > + > + return -EINVAL; > +} > + > +static int ltr501_read_event_config(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir) > +{ > + struct ltr501_data *data = iio_priv(indio_dev); > + int ret, status; > + > + switch (chan->type) { > + case IIO_INTENSITY: > + ret = regmap_field_read(data->reg_als_intr, &status); > + if (ret < 0) > + return ret; > + return status; > + case IIO_PROXIMITY: > + ret = regmap_field_read(data->reg_ps_intr, &status); > + if (ret < 0) > + return ret; > + return status; > + default: > + return -EINVAL; > + } > + > + return -EINVAL; > +} > + > +static int ltr501_write_event_config(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, int state) > +{ > + struct ltr501_data *data = iio_priv(indio_dev); > + int ret; > + > + /* only 1 and 0 are valid inputs */ > + if (state != 1 || state != 0) > + return -EINVAL; > + > + switch (chan->type) { > + case IIO_INTENSITY: > + mutex_lock(&data->lock_als); > + ret = regmap_field_write(data->reg_als_intr, state); > + mutex_unlock(&data->lock_als); > + return ret; > + case IIO_PROXIMITY: > + mutex_lock(&data->lock_ps); > + ret = regmap_field_write(data->reg_ps_intr, state); > + mutex_unlock(&data->lock_ps); > + return ret; > + default: > + return -EINVAL; > + } > + > + return -EINVAL; > +} > + > static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625"); > static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005"); > static IIO_CONST_ATTR_INT_TIME_AVAIL("0.05 0.1 0.2 0.4"); > @@ -344,10 +580,21 @@ static const struct attribute_group ltr501_attribute_group = { > .attrs = ltr501_attributes, > }; > > +static const struct iio_info ltr501_info_no_irq = { > + .read_raw = ltr501_read_raw, > + .write_raw = ltr501_write_raw, > + .attrs = <r501_attribute_group, > + .driver_module = THIS_MODULE, > +}; > + > static const struct iio_info ltr501_info = { > .read_raw = ltr501_read_raw, > .write_raw = ltr501_write_raw, > .attrs = <r501_attribute_group, > + .read_event_value = <r501_read_thresh, > + .write_event_value = <r501_write_thresh, > + .read_event_config = <r501_read_event_config, > + .write_event_config = <r501_write_event_config, > .driver_module = THIS_MODULE, > }; > > @@ -413,6 +660,36 @@ done: > return IRQ_HANDLED; > } > > +static irqreturn_t ltr501_interrupt_handler(int irq, void *private) > +{ > + struct iio_dev *indio_dev = private; > + struct ltr501_data *data = iio_priv(indio_dev); > + int ret, status; > + > + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); > + if (ret < 0) { > + dev_err(&data->client->dev, > + "irq read int reg failed\n"); > + return IRQ_HANDLED; > + } > + > + if (status & LTR501_STATUS_ALS_INTR) > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + iio_get_time_ns()); > + > + if (status & LTR501_STATUS_PS_INTR) > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + iio_get_time_ns()); > + > + return IRQ_HANDLED; > +} > + > static int ltr501_init(struct ltr501_data *data) > { > int ret, status; > @@ -492,6 +769,20 @@ static int ltr501_probe(struct i2c_client *client, > return PTR_ERR(data->reg_it); > } > > + data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap, > + reg_field_als_intr); > + if (IS_ERR(data->reg_als_intr)) { > + dev_err(&client->dev, "ALS intr mode reg field init failed\n"); > + return PTR_ERR(data->reg_als_intr); > + } > + > + data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap, > + reg_field_ps_intr); > + if (IS_ERR(data->reg_ps_intr)) { > + dev_err(&client->dev, "PS intr mode reg field init failed.\n"); > + return PTR_ERR(data->reg_ps_intr); > + } > + > ret = regmap_read(data->regmap, LTR501_PART_ID, &partid); > if (ret < 0) > return ret; > @@ -499,7 +790,6 @@ static int ltr501_probe(struct i2c_client *client, > return -ENODEV; > > indio_dev->dev.parent = &client->dev; > - indio_dev->info = <r501_info; > indio_dev->channels = ltr501_channels; > indio_dev->num_channels = ARRAY_SIZE(ltr501_channels); > indio_dev->name = LTR501_DRV_NAME; > @@ -509,6 +799,23 @@ static int ltr501_probe(struct i2c_client *client, > if (ret < 0) > return ret; > > + if (client->irq > 0) { > + indio_dev->info = <r501_info; > + ret = devm_request_threaded_irq(&client->dev, client->irq, > + NULL, ltr501_interrupt_handler, > + IRQF_TRIGGER_FALLING | > + IRQF_ONESHOT, > + "ltr501_thresh_event", > + indio_dev); > + if (ret) { > + dev_err(&client->dev, "request irq (%d) failed\n", > + client->irq); > + return ret; > + } > + } else { > + indio_dev->info = <r501_info_no_irq; > + } > + > ret = iio_triggered_buffer_setup(indio_dev, NULL, > ltr501_trigger_handler, NULL); > if (ret) > -- 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