RE: [PATCH v3 2/2] iio: accel: add ADXL367 driver

From: Tanislav, Cosmin
Date: Mon Dec 27 2021 - 08:01:10 EST




> -----Original Message-----
> From: Jonathan Cameron <jic23@xxxxxxxxxx>
> Sent: Thursday, December 23, 2021 3:02 PM
> To: Cosmin Tanislav <demonsingur@xxxxxxxxx>
> Cc: Tanislav, Cosmin <Cosmin.Tanislav@xxxxxxxxxx>; Lars-Peter Clausen
> <lars@xxxxxxxxxx>; Hennerich, Michael <Michael.Hennerich@xxxxxxxxxx>;
> Rob Herring <robh+dt@xxxxxxxxxx>; linux-iio@xxxxxxxxxxxxxxx;
> devicetree@xxxxxxxxxxxxxxx; linux-kernel@xxxxxxxxxxxxxxx
> Subject: Re: [PATCH v3 2/2] iio: accel: add ADXL367 driver
>
> [External]
>
> On Fri, 17 Dec 2021 13:45:48 +0200
> Cosmin Tanislav <demonsingur@xxxxxxxxx> wrote:
>
> > The ADXL367 is an ultralow power, 3-axis MEMS accelerometer.
> >
> > The ADXL367 does not alias input signals to achieve ultralow power
> > consumption, it samples the full bandwidth of the sensor at all
> > data rates. Measurement ranges of +-2g, +-4g, and +-8g are available,
> > with a resolution of 0.25mg/LSB on the +-2 g range.
> >
> > In addition to its ultralow power consumption, the ADXL367
> > has many features to enable true system level power reduction.
> > It includes a deep multimode output FIFO, a built-in micropower
> > temperature sensor, and an internal ADC for synchronous conversion
> > of an additional analog input.
> >
> > Signed-off-by: Cosmin Tanislav <cosmin.tanislav@xxxxxxxxxx>
>
> A few comments and questions inline.
>
> Would definitely be helpful if the datasheet becomes available though
> as would have saved some of the questions.
>
> Thanks,
>
> Jonathan

I asked people internally about the possibility of sharing the datasheet publicly,
but until after New Year we probably won't get a response.

>
>
>
> > diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c
> > new file mode 100644
> > index 000000000000..ce574f0446eb
> > --- /dev/null
> > +++ b/drivers/iio/accel/adxl367.c
>
> ...
>
> > +
> > +static bool adxl367_push_event(struct iio_dev *indio_dev, u8 status)
> > +{
> > + unsigned int ev_dir;
> > +
> > + if (FIELD_GET(ADXL367_STATUS_ACT_MASK, status))
> > + ev_dir = IIO_EV_DIR_RISING;
> > + else if (FIELD_GET(ADXL367_STATUS_INACT_MASK, status))
> > + ev_dir = IIO_EV_DIR_FALLING;
> > + else
> > + return false;
> > +
> > + iio_push_event(indio_dev,
> > + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
> IIO_MOD_X_OR_Y_OR_Z,
> > + IIO_EV_TYPE_THRESH, ev_dir),
> This is unusual for event detection as it's a simple or of separately
> applied thresholds on X, Y and Z axes. Given the effect of gravity that
> means you have to set the thresholds very wide.
>
> Also, I'd expect these to be magnitudes, not THRESH - no data sheet that
> I can find though so can't be sure.
>

Actually, the chip has a referenced, and an absolute mode. We use reference mode
in this driver, as configured in write_event_config.
The motion detection details are about the same as ADXL362 (page 14).
https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL362.pdf


> > + iio_get_time_ns(indio_dev));
> > +
> > + return true;
> > +}
> > +
> > +static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status,
> > + u16 fifo_entries)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > + int ret;
> > + int i;
> > +
> > + if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status))
> > + return false;
> > +
> > + fifo_entries -= fifo_entries % st->fifo_set_size;
> > +
> > + ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries);
> > + if (ret)
> > + return false;
>
> Odd corner cases - it doesn't mean IRQ_NONE is appropriate I think...
> Definitely print an error message if you hit this one but I think you should
> still be returning that IRQ_HANDLED from the caller to avoid getting stuck.
> IRQ_NONE doesn't mean error, it means a spurious IRQ.
>
> > +
> > + for (i = 0; i < fifo_entries; i += st->fifo_set_size)
> > + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
> > +
> > + return true;
> > +}
>
>
> > +
> > +static int adxl367_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long info)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_RAW:
> > + return adxl367_read_sample(indio_dev, chan, val);
> > + case IIO_CHAN_INFO_SCALE:
> > + switch (chan->type) {
> > + case IIO_ACCEL:
> > + mutex_lock(&st->lock);
> > + *val = adxl367_range_scale_tbl[st->range][0];
> > + *val2 = adxl367_range_scale_tbl[st->range][1];
> > + mutex_unlock(&st->lock);
> > + return IIO_VAL_INT_PLUS_NANO;
> > + case IIO_TEMP:
> > + *val = 1;
> > + *val2 = ADXL367_TEMP_PER_C;
> > + return IIO_VAL_FRACTIONAL;
>
> Base units of temp channels are milli degrees C. Given naming here, this
> looks
> like it might be the scale factor to degrees C.
> Always check Documentation/ABI/testing/sysfs-bus-iio.
> Unfortunately for historical reasons some of the units are not
> entirely obvious.
>
> > + case IIO_VOLTAGE:
> > + *val = ADXL367_VOLTAGE_MAX_MV;
> > + *val2 = ADXL367_VOLTAGE_MAX_RAW;
> > + return IIO_VAL_FRACTIONAL;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_OFFSET:
> > + switch (chan->type) {
> > + case IIO_TEMP:
> > + *val = 25 * ADXL367_TEMP_PER_C -
> ADXL367_TEMP_25C;
> > + return IIO_VAL_INT;
> > + case IIO_VOLTAGE:
> > + *val = ADXL367_VOLTAGE_OFFSET;
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + mutex_lock(&st->lock);
> > + *val = adxl367_samp_freq_tbl[st->odr][0];
> > + *val2 = adxl367_samp_freq_tbl[st->odr][1];
> > + mutex_unlock(&st->lock);
> > + return IIO_VAL_INT_PLUS_MICRO;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> ...
>
> > +static int adxl367_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 adxl367_state *st = iio_priv(indio_dev);
> > + bool en;
> > + int ret;
> > +
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + ret = adxl367_get_act_interrupt_en(st, ADXL367_ACTIVITY,
> &en);
> > + return ret ?: en;
> > + case IIO_EV_DIR_FALLING:
> > + ret = adxl367_get_act_interrupt_en(st,
> ADXL367_INACTIVITY, &en);
> > + return ret ?: en;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int adxl367_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 adxl367_state *st = iio_priv(indio_dev);
> > + enum adxl367_activity_type act;
> > + int ret;
> > +
> > + switch (dir) {
> > + case IIO_EV_DIR_RISING:
> > + act = ADXL367_ACTIVITY;
> > + break;
> > + case IIO_EV_DIR_FALLING:
> > + act = ADXL367_INACTIVITY;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + ret = iio_device_claim_direct_mode(indio_dev);
>
> It's unusual (though not unheard of) to have events that cannot be enabled
> at the same time as a fifo. If that's true here, please add some comments
> to explain why. Or is this just about the impact of having to disable
> the measurement to turn it on and the resulting interruption of data
> capture?
>
> If so that needs more thought as we have a situation where you can (I think)
> have events as long as you enable them before the fifo based capture is
> started,
> but cannot enable them after.
>

That is indeed the case. You mentioned in a previous patchset that various
attributes could toggle measurement mode while the FIFO capture was running,
so I checked all the possible places where that could happen and added claim
direct mode. Not too nice, but it's the nature of the chip...

> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&st->lock);
> > +
> > + ret = adxl367_set_measure_en(st, false);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_act_interrupt_en(st, act, state);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_act_en(st, act, state ?
> ADCL367_ACT_REF_ENABLED
> > + : ADXL367_ACT_DISABLED);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_measure_en(st, true);
> > +
> > +out:
> > + mutex_unlock(&st->lock);
> > +
> > + iio_device_release_direct_mode(indio_dev);
> > +
> > + return ret;
> > +}
> > +
>
> > +
> > +static ssize_t adxl367_get_fifo_watermark(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct adxl367_state *st = iio_priv(indio_dev);
> Trivial but in cases where you don't need the indio_dev, it
> is find to roll the above two lines into one as the function names
> express the types so no information is lost.
>
> struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev));
>
> > + unsigned int fifo_watermark;
> > +
> > + mutex_lock(&st->lock);
> > + fifo_watermark = st->fifo_watermark;
> > + mutex_unlock(&st->lock);
> > +
> > + return sysfs_emit(buf, "%d\n", fifo_watermark);
> > +}
> ...
> > +
> > +static IIO_CONST_ATTR(hwfifo_watermark_min, "1");
> > +static IIO_CONST_ATTR(hwfifo_watermark_max,
> > + __stringify(ADXL367_FIFO_MAX_WATERMARK));
> > +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
> > + adxl367_get_fifo_watermark, NULL, 0);
> > +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
> > + adxl367_get_fifo_enabled, NULL, 0);
> > +
> > +static const struct attribute *adxl367_fifo_attributes[] = {
> > + &iio_const_attr_hwfifo_watermark_min.dev_attr.attr,
> > + &iio_const_attr_hwfifo_watermark_max.dev_attr.attr,
> > + &iio_dev_attr_hwfifo_watermark.dev_attr.attr,
> > + &iio_dev_attr_hwfifo_enabled.dev_attr.attr,
> > + NULL,
> > +};
>
> ...
>
> > +static int adxl367_update_scan_mode(struct iio_dev *indio_dev,
> > + const unsigned long *active_scan_mask)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > + enum adxl367_fifo_format fifo_format;
> > + unsigned int fifo_set_size;
> > + int ret;
> > +
> > + if (!adxl367_find_mask_fifo_format(active_scan_mask,
> &fifo_format))
> > + return -EINVAL;
> > +
> > + fifo_set_size = bitmap_weight(active_scan_mask, indio_dev-
> >masklength);
> > +
> > + mutex_lock(&st->lock);
> > +
> > + ret = adxl367_set_measure_en(st, false);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_format(st, fifo_format);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_set_size(st, fifo_set_size);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_measure_en(st, true);
> > +
> > +out:
> > + mutex_unlock(&st->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int adxl367_buffer_preenable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > +
> > + return adxl367_set_temp_adc_mask_en(st, indio_dev-
> >active_scan_mask,
> > + true);
>
> Why the logical separation of the channel enable to here and fifo setup to
> post_enable? Reality is there is very little reason any more to have
> separate preenable/posteenable. If there is a reason to do it here, please
> add a comment to explain.
> Is it because this needs to occur before update_scan_mode() is called?
>
>

No particular reason, as far as I can remember. I think I was tired at the time.

> > +}
> > +
> > +static int adxl367_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + mutex_lock(&st->lock);
> > +
> > + ret = adxl367_set_measure_en(st, false);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_watermark_interrupt_en(st, true);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_STREAM);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_measure_en(st, true);
> > +
> > +out:
> > + mutex_unlock(&st->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int adxl367_buffer_predisable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + mutex_lock(&st->lock);
> > +
> > + ret = adxl367_set_measure_en(st, false);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_DISABLED);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_fifo_watermark_interrupt_en(st, false);
> > + if (ret)
> > + goto out;
> > +
> > + ret = adxl367_set_measure_en(st, true);
> > +
> > +out:
> > + mutex_unlock(&st->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int adxl367_buffer_postdisable(struct iio_dev *indio_dev)
> > +{
> > + struct adxl367_state *st = iio_priv(indio_dev);
> > +
> > + return adxl367_set_temp_adc_mask_en(st, indio_dev-
> >active_scan_mask,
> > + false);
> > +}
> > +
>
> ...
>
>
> > +static int adxl367_setup(struct adxl367_state *st)
> > +{
> > + int ret;
> > +
> > + ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY,
> > + ADXL367_2G_RANGE_1G);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY,
> > + ADXL367_2G_RANGE_100MG);
>
> Should one of this pair be inactivity?
>

Indeed. I must have replaced the correct variant somewhere along the line.

> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl367_set_act_proc_mode(st, ADXL367_LOOPED);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl367_set_odr(st, ADXL367_ODR_400HZ);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl367_set_act_time_ms(st, 10);
> > + if (ret)
> > + return ret;
> > +
> > + ret = _adxl367_set_inact_time_ms(st, 10000);
> > + if (ret)
> > + return ret;
> > +
> > + return adxl367_set_measure_en(st, true);
> > +}
> > +
> > +static void adxl367_disable_regulators(void *data)
> > +{
> > + struct adxl367_state *st = data;
> > +
> > + regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators);
> > +}
> > +
> > +int adxl367_probe(struct device *dev, const struct adxl367_ops *ops,
> > + void *context, struct regmap *regmap, int irq)
> > +{
> > + struct iio_dev *indio_dev;
> > + struct adxl367_state *st;
> > + int ret;
> > +
> > + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + st = iio_priv(indio_dev);
> > + st->dev = dev;
> > + st->regmap = regmap;
> > + st->context = context;
> > + st->ops = ops;
> > +
> > + mutex_init(&st->lock);
> > +
> > + indio_dev->channels = adxl367_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(adxl367_channels);
> > + indio_dev->available_scan_masks = adxl367_channel_masks;
> > + indio_dev->name = "adxl367";
> > + indio_dev->info = &adxl367_info;
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > + st->regulators[0].supply = "vdd";
> > + st->regulators[1].supply = "vddio";
> > +
> > + ret = devm_regulator_bulk_get(st->dev, ARRAY_SIZE(st-
> >regulators),
> > + st->regulators);
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to get regulators\n");
> > +
> > + ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st-
> >regulators);
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to enable regulators\n");
> > +
> > + ret = devm_add_action_or_reset(st->dev,
> adxl367_disable_regulators, st);
> > + if (ret)
> > + return dev_err_probe(st->dev, ret,
> > + "Failed to add regulators disable
> action\n");
> > +
> > + ret = adxl367_verify_devid(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl367_reset(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = adxl367_setup(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
> > + INDIO_BUFFER_SOFTWARE,
> > + &adxl367_buffer_ops,
> > + adxl367_fifo_attributes);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_request_threaded_irq(st->dev, irq, NULL,
> > + adxl367_irq_handler,
> IRQF_ONESHOT,
> > + indio_dev->name, indio_dev);
> > + if (ret)
> > + return dev_err_probe(st->dev, ret, "Failed to request
> irq\n");
> > +
> > + return devm_iio_device_register(dev, indio_dev);
> > +}
> > +EXPORT_SYMBOL_GPL(adxl367_probe);
>
> Something Andy raised in a recent review was that for cases like this
> we should be using the NS variants so that we move stuff used only between
> a smalls set of drivers into it's own namespace.
>
> I think it is an excellent idea, and will hopefully convert a few drivers
> over shortly. In the meantime it would be good to ensure no new drivers
> go in without using the NS support
> (EXPORT_SYMBOL_NS_GPL(adxl367_probe, adxl367) etc.
>

I'll do it for the next patchset.

> > +
> > +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@xxxxxxxxxx>");
> > +MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer
> driver");
> > +MODULE_LICENSE("GPL");