On 03/04/15 13:47, Vlad Dogaru wrote: > In the interest of lowering power usage, we only activate the proximity > channels and interrupts that we are currently using. > > For raw reads, we activate the corresponding channel and the data ready > interrupt and wait for the interrupt to trigger. Thus, it is no longer > optional to register an irq. This change bothers me. We may well have people with the device that don't have the interrupt wired and if they are already using the driver then we have a resulting regression. Any chance we could have an alternative of a registering polling or similar to identify when the interrupt would have occured? > > The following types of usage patterns may overlap: > > * raw proximity reads (need a single data ready interrupt) We often avoid this complexity by just failing raw reads when the buffer is running but the solution you have here is pretty elegant. > * trigger usage (needs data ready interrupts as long as active) > * proximity events (need near/far interrupts) > * triggered buffer reads (don't need any interrupts, but are usually > coupled with our own trigger. An alternative, perhaps more standard method, would be to handle this via an interrupt chip and let that infrastructure be responisble for the underlying real interrupts. I'm not sure which approach would be more complex though so perhaps, though I'd have gone that way, what you have here is the best way... > > To mitigate all possible patterns, we implement usage counting for all > the resources used: data ready interrupts, near/far interrupts and > individual channels. > > The device enters sleep mode as documented in the data sheet when its > buffer, trigger and events are disabled, and no raw reads are currently > running. > > Because of this new usage pattern, it is important that we give the > device a chance to perform an initial compensation for all its channels > at probe time. > > Signed-off-by: Vlad Dogaru <vlad.dogaru@xxxxxxxxx> > --- > drivers/iio/proximity/sx9500.c | 376 +++++++++++++++++++++++++++++++++-------- > 1 file changed, 302 insertions(+), 74 deletions(-) > > diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c > index f62d0b6..94c56f3 100644 > --- a/drivers/iio/proximity/sx9500.c > +++ b/drivers/iio/proximity/sx9500.c > @@ -19,6 +19,7 @@ > #include <linux/gpio/consumer.h> > #include <linux/regmap.h> > #include <linux/pm.h> > +#include <linux/delay.h> > > #include <linux/iio/iio.h> > #include <linux/iio/buffer.h> > @@ -74,6 +75,7 @@ > #define SX9500_CONVDONE_IRQ BIT(3) > > #define SX9500_PROXSTAT_SHIFT 4 > +#define SX9500_COMPSTAT_MASK GENMASK(3, 0) > > #define SX9500_NUM_CHANNELS 4 > > @@ -92,6 +94,9 @@ struct sx9500_data { > u16 *buffer; > /* Remember enabled channels and sample rate during suspend. */ > unsigned int suspend_ctrl0; > + struct completion completion; > + int data_rdy_users, close_far_users; > + int channel_users[SX9500_NUM_CHANNELS]; > }; > > static const struct iio_event_spec sx9500_events[] = { > @@ -194,7 +199,67 @@ static const struct regmap_config sx9500_regmap_config = { > .volatile_table = &sx9500_volatile_regs, > }; > > -static int sx9500_read_proximity(struct sx9500_data *data, > +static int sx9500_inc_users(struct sx9500_data *data, int *counter, > + unsigned int reg, unsigned int bitmask) > +{ > + (*counter)++; > + if (*counter != 1) > + /* Bit is already active, nothing to do. */ > + return 0; > + > + return regmap_update_bits(data->regmap, reg, bitmask, bitmask); > +} > + > +static int sx9500_dec_users(struct sx9500_data *data, int *counter, > + unsigned int reg, unsigned int bitmask) > +{ > + (*counter)--; > + if (*counter != 0) > + /* There are more users, do not deactivate. */ > + return 0; > + > + return regmap_update_bits(data->regmap, reg, bitmask, 0); > +} > + > +static int sx9500_inc_chan_users(struct sx9500_data *data, int chan) > +{ > + return sx9500_inc_users(data, &data->channel_users[chan], > + SX9500_REG_PROX_CTRL0, BIT(chan)); > +} > + > +static int sx9500_dec_chan_users(struct sx9500_data *data, int chan) > +{ > + return sx9500_dec_users(data, &data->channel_users[chan], > + SX9500_REG_PROX_CTRL0, BIT(chan)); > +} > + > +static int sx9500_inc_data_rdy_users(struct sx9500_data *data) > +{ > + return sx9500_inc_users(data, &data->data_rdy_users, > + SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ); > +} > + > +static int sx9500_dec_data_rdy_users(struct sx9500_data *data) > +{ > + return sx9500_dec_users(data, &data->data_rdy_users, > + SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ); > +} > + > +static int sx9500_inc_close_far_users(struct sx9500_data *data) > +{ > + return sx9500_inc_users(data, &data->close_far_users, > + SX9500_REG_IRQ_MSK, > + SX9500_CLOSE_IRQ | SX9500_FAR_IRQ); > +} > + > +static int sx9500_dec_close_far_users(struct sx9500_data *data) > +{ > + return sx9500_dec_users(data, &data->close_far_users, > + SX9500_REG_IRQ_MSK, > + SX9500_CLOSE_IRQ | SX9500_FAR_IRQ); > +} > + > +static int sx9500_read_prox_data(struct sx9500_data *data, > const struct iio_chan_spec *chan, > int *val) > { > @@ -203,15 +268,66 @@ static int sx9500_read_proximity(struct sx9500_data *data, > > ret = regmap_write(data->regmap, SX9500_REG_SENSOR_SEL, chan->channel); > if (ret < 0) > - return ret; > + goto out; > > ret = regmap_bulk_read(data->regmap, SX9500_REG_USE_MSB, ®val, 2); > if (ret < 0) > - return ret; > + goto out; > > *val = 32767 - (s16)be16_to_cpu(regval); > + ret = IIO_VAL_INT; > + > +out: > + return ret; > +} > + > +static int sx9500_read_proximity(struct sx9500_data *data, > + const struct iio_chan_spec *chan, > + int *val) > +{ > + int ret; > + > + mutex_lock(&data->mutex); > + > + ret = sx9500_inc_chan_users(data, chan->channel); > + if (ret < 0) > + goto out; > + > + ret = sx9500_inc_data_rdy_users(data); > + if (ret < 0) > + goto out_dec_chan; > + > + mutex_unlock(&data->mutex); > + > + ret = wait_for_completion_interruptible(&data->completion); > + if (ret < 0) > + return ret; > > - return IIO_VAL_INT; > + mutex_lock(&data->mutex); > + > + ret = sx9500_read_prox_data(data, chan, val); > + if (ret < 0) > + goto out; > + > + ret = sx9500_dec_chan_users(data, chan->channel); > + if (ret < 0) > + goto out; > + > + ret = sx9500_dec_data_rdy_users(data); > + if (ret < 0) > + goto out; > + > + ret = IIO_VAL_INT; > + > + goto out; > + > +out_dec_chan: > + sx9500_dec_chan_users(data, chan->channel); > +out: > + mutex_unlock(&data->mutex); > + reinit_completion(&data->completion); > + > + return ret; > } > > static int sx9500_read_samp_freq(struct sx9500_data *data, > @@ -239,7 +355,6 @@ static int sx9500_read_raw(struct iio_dev *indio_dev, > int *val, int *val2, long mask) > { > struct sx9500_data *data = iio_priv(indio_dev); > - int ret; > > switch (chan->type) { > case IIO_PROXIMITY: > @@ -247,10 +362,7 @@ static int sx9500_read_raw(struct iio_dev *indio_dev, > case IIO_CHAN_INFO_RAW: > if (iio_buffer_enabled(indio_dev)) > return -EBUSY; > - mutex_lock(&data->mutex); > - ret = sx9500_read_proximity(data, chan, val); > - mutex_unlock(&data->mutex); > - return ret; > + return sx9500_read_proximity(data, chan, val); > case IIO_CHAN_INFO_SAMP_FREQ: > return sx9500_read_samp_freq(data, val, val2); > default: > @@ -321,28 +433,16 @@ static irqreturn_t sx9500_irq_handler(int irq, void *private) > return IRQ_WAKE_THREAD; > } > > -static irqreturn_t sx9500_irq_thread_handler(int irq, void *private) > +static void sx9500_push_events(struct iio_dev *indio_dev) > { > - struct iio_dev *indio_dev = private; > - struct sx9500_data *data = iio_priv(indio_dev); > int ret; > unsigned int val, chan; > - > - mutex_lock(&data->mutex); > - > - ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); > - if (ret < 0) { > - dev_err(&data->client->dev, "i2c transfer error in irq\n"); > - goto out; > - } > - > - if (!(val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ))) > - goto out; > + struct sx9500_data *data = iio_priv(indio_dev); > > ret = regmap_read(data->regmap, SX9500_REG_STAT, &val); > if (ret < 0) { > dev_err(&data->client->dev, "i2c transfer error in irq\n"); > - goto out; > + return; > } > > val >>= SX9500_PROXSTAT_SHIFT; > @@ -357,15 +457,34 @@ static irqreturn_t sx9500_irq_thread_handler(int irq, void *private) > /* No change on this channel. */ > continue; > > - dir = new_prox ? IIO_EV_DIR_FALLING : > - IIO_EV_DIR_RISING; > - ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, > - chan, > - IIO_EV_TYPE_THRESH, > - dir); > + dir = new_prox ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; > + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, > + IIO_EV_TYPE_THRESH, dir); > iio_push_event(indio_dev, ev, iio_get_time_ns()); > data->prox_stat[chan] = new_prox; > } > +} > + > +static irqreturn_t sx9500_irq_thread_handler(int irq, void *private) > +{ > + struct iio_dev *indio_dev = private; > + struct sx9500_data *data = iio_priv(indio_dev); > + int ret; > + unsigned int val; > + > + mutex_lock(&data->mutex); > + > + ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); > + if (ret < 0) { > + dev_err(&data->client->dev, "i2c transfer error in irq\n"); > + goto out; > + } > + > + if (val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ)) > + sx9500_push_events(indio_dev); > + > + if (val & SX9500_CONVDONE_IRQ) > + complete_all(&data->completion); > > out: > mutex_unlock(&data->mutex); > @@ -394,9 +513,7 @@ static int sx9500_write_event_config(struct iio_dev *indio_dev, > int state) > { > struct sx9500_data *data = iio_priv(indio_dev); > - int ret, i; > - bool any_active = false; > - unsigned int irqmask; > + int ret; > > if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || > dir != IIO_EV_DIR_EITHER) > @@ -404,24 +521,32 @@ static int sx9500_write_event_config(struct iio_dev *indio_dev, > > mutex_lock(&data->mutex); > > - data->event_enabled[chan->channel] = state; > + if (state == 1) { Is if (state) not sufficient? > + ret = sx9500_inc_chan_users(data, chan->channel); > + if (ret < 0) > + goto out_unlock; > + ret = sx9500_inc_close_far_users(data); > + if (ret < 0) > + goto out_undo_chan; > + } else { > + ret = sx9500_dec_chan_users(data, chan->channel); > + if (ret < 0) > + goto out_unlock; > + ret = sx9500_dec_close_far_users(data); > + if (ret < 0) > + goto out_undo_chan; > + } > > - for (i = 0; i < SX9500_NUM_CHANNELS; i++) > - if (data->event_enabled[i]) { > - any_active = true; > - break; > - } > + data->event_enabled[chan->channel] = state; > + goto out_unlock; > > - irqmask = SX9500_CLOSE_IRQ | SX9500_FAR_IRQ; > - if (any_active) > - ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, > - irqmask, irqmask); > +out_undo_chan: > + if (state == 1) > + sx9500_dec_chan_users(data, chan->channel); > else > - ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, > - irqmask, 0); > - > + sx9500_inc_chan_users(data, chan->channel); > +out_unlock: > mutex_unlock(&data->mutex); > - > return ret; > } > > @@ -472,12 +597,16 @@ static int sx9500_set_trigger_state(struct iio_trigger *trig, > > mutex_lock(&data->mutex); > > - ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, > - SX9500_CONVDONE_IRQ, > - state ? SX9500_CONVDONE_IRQ : 0); > - if (ret == 0) > - data->trigger_enabled = state; > + if (state) > + ret = sx9500_inc_data_rdy_users(data); > + else > + ret = sx9500_dec_data_rdy_users(data); > + if (ret < 0) > + goto out; > + > + data->trigger_enabled = state; > > +out: > mutex_unlock(&data->mutex); > > return ret; > @@ -499,7 +628,7 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) > > for_each_set_bit(bit, indio_dev->buffer->scan_mask, > indio_dev->masklength) { > - ret = sx9500_read_proximity(data, &indio_dev->channels[bit], > + ret = sx9500_read_prox_data(data, &indio_dev->channels[bit], > &val); > if (ret < 0) > goto out; > @@ -518,6 +647,62 @@ out: > return IRQ_HANDLED; > } > > +static int sx9500_buffer_preenable(struct iio_dev *indio_dev) > +{ > + struct sx9500_data *data = iio_priv(indio_dev); > + int ret, i; > + > + mutex_lock(&data->mutex); > + > + for (i = 0; i < SX9500_NUM_CHANNELS; i++) > + if (test_bit(i, indio_dev->active_scan_mask)) { > + ret = sx9500_inc_chan_users(data, i); > + if (ret) > + break; > + } > + > + if (ret) > + for (i = i - 1; i >= 0; i--) > + if (test_bit(i, indio_dev->active_scan_mask)) > + sx9500_dec_chan_users(data, i); > + > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +static int sx9500_buffer_predisable(struct iio_dev *indio_dev) > +{ > + struct sx9500_data *data = iio_priv(indio_dev); > + int ret, i; > + > + iio_triggered_buffer_predisable(indio_dev); > + > + mutex_lock(&data->mutex); > + > + for (i = 0; i < SX9500_NUM_CHANNELS; i++) > + if (test_bit(i, indio_dev->active_scan_mask)) { > + ret = sx9500_dec_chan_users(data, i); > + if (ret) > + break; > + } > + > + if (ret) > + for (i = i - 1; i >= 0; i--) > + if (test_bit(i, indio_dev->active_scan_mask)) > + sx9500_inc_chan_users(data, i); > + > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +static const struct iio_buffer_setup_ops sx9500_buffer_setup_ops = { > + .preenable = sx9500_buffer_preenable, > + .postenable = iio_triggered_buffer_postenable, > + .predisable = sx9500_buffer_predisable, > +}; > + > struct sx9500_reg_default { > u8 reg; > u8 def; > @@ -573,11 +758,44 @@ static const struct sx9500_reg_default sx9500_default_regs[] = { > }, > { > .reg = SX9500_REG_PROX_CTRL0, > - /* Scan period: 30ms, all sensors enabled. */ > - .def = 0x0f, > + /* Scan period: 30ms, all sensors disabled. */ > + .def = 0x00, > }, > }; > > +/* Activate all channels and perform an initial compensation. */ > +static int sx9500_init_compensation(struct iio_dev *indio_dev) > +{ > + struct sx9500_data *data = iio_priv(indio_dev); > + int i, ret; > + unsigned int val; > + > + ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0, > + GENMASK(SX9500_NUM_CHANNELS, 0), > + GENMASK(SX9500_NUM_CHANNELS, 0)); > + if (ret < 0) > + return ret; > + > + for (i = 10; i >= 0; i--) { > + usleep_range(10000, 20000); > + ret = regmap_read(data->regmap, SX9500_REG_STAT, &val); > + if (ret < 0) > + goto out; > + if (!(val & SX9500_COMPSTAT_MASK)) > + break; > + } > + > + if (i < 0) { > + dev_err(&data->client->dev, "initial compensation timed out"); > + ret = -ETIMEDOUT; > + } > + > +out: > + regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0, > + GENMASK(SX9500_NUM_CHANNELS, 0), 0); > + return ret; > +} > + > static int sx9500_init_device(struct iio_dev *indio_dev) > { > struct sx9500_data *data = iio_priv(indio_dev); > @@ -605,6 +823,10 @@ static int sx9500_init_device(struct iio_dev *indio_dev) > return ret; > } > > + ret = sx9500_init_compensation(indio_dev); > + if (ret < 0) > + return ret; > + > return 0; > } > > @@ -652,6 +874,7 @@ static int sx9500_probe(struct i2c_client *client, > data = iio_priv(indio_dev); > data->client = client; > mutex_init(&data->mutex); > + init_completion(&data->completion); > data->trigger_enabled = false; > > data->regmap = devm_regmap_init_i2c(client, &sx9500_regmap_config); > @@ -671,30 +894,35 @@ static int sx9500_probe(struct i2c_client *client, > if (client->irq <= 0) > client->irq = sx9500_gpio_probe(client, data); > > - if (client->irq > 0) { > - ret = devm_request_threaded_irq(&client->dev, client->irq, > - sx9500_irq_handler, sx9500_irq_thread_handler, > - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, > - SX9500_IRQ_NAME, indio_dev); > - if (ret < 0) > - return ret; > + if (client->irq <= 0) { > + dev_err(&client->dev, "no valid irq found\n"); > + return -EINVAL; > + } > + > + ret = devm_request_threaded_irq(&client->dev, client->irq, > + sx9500_irq_handler, > + sx9500_irq_thread_handler, > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, > + SX9500_IRQ_NAME, indio_dev); > + if (ret < 0) > + return ret; > > - data->trig = devm_iio_trigger_alloc(&client->dev, > - "%s-dev%d", indio_dev->name, indio_dev->id); > - if (!data->trig) > - return -ENOMEM; > + data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", > + indio_dev->name, indio_dev->id); > + if (!data->trig) > + return -ENOMEM; > > - data->trig->dev.parent = &client->dev; > - data->trig->ops = &sx9500_trigger_ops; > - iio_trigger_set_drvdata(data->trig, indio_dev); > + data->trig->dev.parent = &client->dev; > + data->trig->ops = &sx9500_trigger_ops; > + iio_trigger_set_drvdata(data->trig, indio_dev); > > - ret = iio_trigger_register(data->trig); > - if (ret) > - return ret; > - } > + ret = iio_trigger_register(data->trig); > + if (ret) > + return ret; > > ret = iio_triggered_buffer_setup(indio_dev, NULL, > - sx9500_trigger_handler, NULL); > + sx9500_trigger_handler, > + &sx9500_buffer_setup_ops); > if (ret < 0) > goto out_trigger_unregister; > > -- 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