> -----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");