Re: [PATCH v1 3/3] iio: adc: ad4130: add AD4130 driver

From: Andy Shevchenko
Date: Wed Apr 13 2022 - 11:45:56 EST


On Wed, Apr 13, 2022 at 1:41 PM Cosmin Tanislav <demonsingur@xxxxxxxxx> wrote:


Thanks for the contribution, my comments below.

> AD4130-8 is an ultra-low power, high precision,
> measurement solution for low bandwidth battery
> operated applications.
>
> The fully integrated AFE (Analog Front-End)
> includes a multiplexer for up to 16 single-ended
> or 8 differential inputs, PGA (Programmable Gain
> Amplifier), 24-bit Sigma-Delta ADC, on-chip
> reference and oscillator, selectable filter
> options, smart sequencer, sensor biasing and
> excitation options, diagnostics, and a FIFO
> buffer.

Indentation issue as per previous patches.

...

> +// SPDX-License-Identifier: GPL-2.0+

The

// SPDX-License-Identifier: GPL-2.0-or-later

can be a bit more explicit, but it's up to your company lawyers.

...

> +#include <asm/div64.h>
> +#include <asm/unaligned.h>

Please, move this after linux/*

> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>

> +#include <linux/iio/buffer.h>
> +#include <linux/iio/kfifo_buf.h>

Looks like iio.h is missed, in any case, can you split this group of
headers and put it after all the rest of linux/* and asm/* ? Ah, you
even have them below, so move these there.

> +#include <linux/module.h>

> +#include <linux/of_irq.h>

Get rid of this one.

> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>

...

> +#define AD4130_8_NAME "ad4130-8"

What the meaning of -8 ? Is it number of channels? Or is it part of
the official model (part number)? Can we see, btw, Datasheet: tag with
a corresponding link in the commit message?

...

> +#define AD4130_RESET_CLK_COUNT 64
> +#define AD4130_RESET_BUF_SIZE (AD4130_RESET_CLK_COUNT / 8)

To be more precise shouldn't the above need to have DIV_ROUND_UP() ?

...

> +#define AD4130_SOFT_RESET_SLEEP (160 * 1000000 / AD4130_MCLK_FREQ_76_8KHZ)

Units? Also, can you use definitions from units.h?

...

> +#define AD4130_FREQ_FACTOR 1000000000ull
> +#define AD4130_DB3_FACTOR 1000

Ditto.

...

> +enum ad4130_mclk_sel {
> + AD4130_MCLK_76_8KHZ,
> + AD4130_MCLK_76_8KHZ_OUT,
> + AD4130_MCLK_76_8KHZ_EXT,
> + AD4130_MCLK_153_6KHZ_EXT,
> + AD4130_MCLK_SEL_MAX,

No comma after MAX, if I understood correctly that it's a terminator.
Ditto for other MAXes in the other enums.

> +};

...

> +enum ad4130_fifo_mode {
> + AD4130_FIFO_MODE_DISABLED = 0b00,
> + AD4130_FIFO_MODE_WATERMARK = 0b01,
> +};
> +
> +enum ad4130_mode {
> + AD4130_MODE_CONTINUOUS = 0b0000,
> + AD4130_MODE_IDLE = 0b0100,
> +};

0b?! Hmm... Not that this is bad, just not so usual :-)

...

> +enum ad4130_pin_function {
> + AD4130_PIN_FN_NONE,
> + AD4130_PIN_FN_SPECIAL = 1 << 0,
> + AD4130_PIN_FN_DIFF = 1 << 1,
> + AD4130_PIN_FN_EXCITATION = 1 << 2,
> + AD4130_PIN_FN_VBIAS = 1 << 3,

Why not BIT()?

> +};

...

> +#define AD4130_SETUP_SIZE offsetof(struct ad4130_setup_info, \
> + enabled_channels)

It's uglier than simply

#define AD4130_SETUP_SIZE offsetof(struct
ad4130_setup_info, enabled_channels)

or

#define AD4130_SETUP_SIZE \
offsetof(struct ad4130_setup_info, enabled_channels)

...

> +struct ad4130_filter_config {
> + enum ad4130_filter_mode filter_mode;
> + unsigned int odr_div;
> + unsigned int fs_max;
> + unsigned int db3_div;
> + enum iio_available_type samp_freq_avail_type;
> + int samp_freq_avail_len;
> + int samp_freq_avail[3][2];
> + enum iio_available_type db3_freq_avail_type;
> + int db3_freq_avail_len;
> + int db3_freq_avail[3][2];

These 3:s can be defined?

> +};

...

> + int scale_tbls[AD4130_REF_SEL_MAX]
> + [AD4130_PGA_NUM][2];

Why not on one line?

...

> + u32 int_pin_sel;
> + bool int_ref_en;
> + u32 int_ref_uv;
> + u32 mclk_sel;
> + bool bipolar;

You may save a few bytes if you group bool:s.

...

> + u8 fifo_rx_buf[AD4130_FIFO_SIZE *
> + AD4130_FIFO_MAX_SAMPLE_SIZE];

One line?

Also it might be good to add a static_assert() to make sure that
multiplication don't overflow.

...


> +static int ad4130_get_reg_size(struct ad4130_state *st, unsigned int reg,
> + unsigned int *size)
> +{

> + if (reg >= ARRAY_SIZE(ad4130_reg_size))
> + return -EINVAL;

When this condition is true?

> + if (reg == AD4130_REG_DATA) {
> + *size = ad4130_data_reg_size(st);
> + return 0;
> + }
> +
> + *size = ad4130_reg_size[reg];

> +

Redundant blank line.

> + if (!*size)
> + return -EINVAL;
> +
> + return 0;
> +}

...

> + regmap_update_bits(st->regmap, AD4130_REG_IO_CONTROL, mask,
> + value ? mask : 0);

One line?

No error check?

> +}

...

> +static int ad4130_set_watermark_interrupt_en(struct ad4130_state *st, bool en)
> +{
> + return regmap_update_bits(st->regmap, AD4130_REG_FIFO_CONTROL,
> + AD4130_WATERMARK_INT_EN_MASK,
> + en ? AD4130_WATERMARK_INT_EN_MASK : 0);

I believe with temporary variable for mask it will be neater.

> +}

...

> + if (setup_info->enabled_channels)
> + return -EINVAL;

-EBUSY?

...

> + ret = regmap_update_bits(st->regmap, AD4130_REG_CHANNEL_X(channel),
> + AD4130_CHANNEL_EN_MASK,
> + status ? AD4130_CHANNEL_EN_MASK : 0);

Temporary variable for mask?

...

> +static void ad4130_freq_to_fs(enum ad4130_filter_mode filter_mode,
> + int val, int val2, unsigned int *fs, bool db3)
> +{
> + const struct ad4130_filter_config *filter_config =
> + &ad4130_filter_configs[filter_mode];
> + unsigned long long dividend, divisor;
> + int temp;
> +
> + dividend = filter_config->fs_max * filter_config->odr_div *
> + (val * AD4130_FREQ_FACTOR + val2);
> + divisor = AD4130_MAX_ODR * AD4130_FREQ_FACTOR;
> +
> + if (db3) {
> + dividend *= AD4130_DB3_FACTOR;
> + divisor *= filter_config->db3_div;
> + }
> +
> + temp = AD4130_FS_MIN + filter_config->fs_max -
> + DIV64_U64_ROUND_CLOSEST(dividend, divisor);
> +
> + if (temp < AD4130_FS_MIN)
> + temp = AD4130_FS_MIN;
> + else if (temp > filter_config->fs_max)
> + temp = filter_config->fs_max;
> +
> + *fs = temp;

Would be nice to put a comment explaining the math behind this code.

> +}
> +
> +static void ad4130_fs_to_freq(enum ad4130_filter_mode filter_mode,
> + unsigned int fs, int *val, int *val2, bool db3)
> +{
> + const struct ad4130_filter_config *filter_config =
> + &ad4130_filter_configs[filter_mode];
> + unsigned int dividend, divisor;
> + u64 temp;
> +
> + dividend = (filter_config->fs_max - fs + AD4130_FS_MIN) *
> + AD4130_MAX_ODR;
> + divisor = filter_config->fs_max * filter_config->odr_div;
> +
> + if (db3) {
> + dividend *= filter_config->db3_div;
> + divisor *= AD4130_DB3_FACTOR;
> + }
> +
> + temp = div_u64(dividend * AD4130_FREQ_FACTOR, divisor);
> + *val = div_u64_rem(temp, AD4130_FREQ_FACTOR, val2);


Ditto.

> +}

...

> + out:

out_unlock: ?
Ditto for similar cases.

> + mutex_unlock(&st->lock);
> +
> + return ret;

...

> +static const struct iio_enum ad4130_filter_mode_enum = {
> + .items = ad4130_filter_modes_str,
> + .num_items = ARRAY_SIZE(ad4130_filter_modes_str),
> + .set = ad4130_set_filter_mode,

> + .get = ad4130_get_filter_mode

+ Comma at the end.

> +};

...

> +static const struct iio_chan_spec_ext_info ad4130_filter_mode_ext_info[] = {
> + IIO_ENUM("filter_mode", IIO_SEPARATE, &ad4130_filter_mode_enum),
> + IIO_ENUM_AVAILABLE("filter_mode", IIO_SHARED_BY_TYPE,
> + &ad4130_filter_mode_enum),

> + { },

No comma for terminator.

> +};

...

> + *val = st->bipolar ? -(1 << (chan->scan_type.realbits - 1)) : 0;

Hmm... It seems like specific way to have a sign_extended, or actually
reduced) mask.
Can you rewrite it with the (potential)UB-free approach?

(Note, that if realbits == 32, this will have a lot of fun in
accordance with C standard.)

...

> + *vals = (int *)st->scale_tbls[setup_info->ref_sel];

Can we get rid of casting here and in the similar cases?

...

> + for (i = 0; i < indio_dev->num_channels; i++) {
> + bool status = test_bit(i, scan_mask);
> +
> + if (!status)
> + continue;

Can't you use for_each_set_bit() instead?

> + }

...

> +static int ad4130_set_fifo_watermark(struct iio_dev *indio_dev, unsigned int val)
> +{
> + struct ad4130_state *st = iio_priv(indio_dev);
> + unsigned int eff;

> + int ret = 0;

Redundant assignment

> +
> + if (val > AD4130_FIFO_SIZE)
> + return -EINVAL;
> +
> + /*
> + * Always set watermark to a multiple of the number of enabled channels
> + * to avoid making the FIFO unaligned.
> + */
> + eff = rounddown(val, st->num_enabled_channels);
> +
> + mutex_lock(&st->lock);
> +
> + ret = regmap_update_bits(st->regmap, AD4130_REG_FIFO_CONTROL,
> + AD4130_WATERMARK_MASK,
> + FIELD_PREP(AD4130_WATERMARK_MASK,
> + ad4130_watermark_reg_val(eff)));

Temporary variable for mask?

> + if (ret)
> + goto out;
> +
> + st->effective_watermark = eff;
> + st->watermark = val;
> +
> +out:

out_unlock: ?

> + mutex_unlock(&st->lock);
> +
> + return ret;
> +}

...

> +static IIO_CONST_ATTR(hwfifo_watermark_min, "1");
> +static IIO_CONST_ATTR(hwfifo_watermark_max,
> + __stringify(AD4130_FIFO_SIZE));
> +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
> + ad4130_get_fifo_watermark, NULL, 0);
> +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
> + ad4130_get_fifo_enabled, NULL, 0);

Can these all be oneliners?

...

> +static const struct attribute *ad4130_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,

No comma for terminator.

> +};

...

> +static int ad4130_get_ref_voltage(struct ad4130_state *st,
> + enum ad4130_ref_sel ref_sel,
> + unsigned int *ref_uv)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + switch (ref_sel) {
> + case AD4130_REF_REFIN1:
> + ret = regulator_get_voltage(st->regulators[2].consumer);
> + break;
> + case AD4130_REF_REFIN2:
> + ret = regulator_get_voltage(st->regulators[3].consumer);
> + break;
> + case AD4130_REF_AVDD_AVSS:
> + ret = regulator_get_voltage(st->regulators[0].consumer);
> + break;
> + case AD4130_REF_REFOUT_AVSS:

> + if (!st->int_ref_en) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + ret = st->int_ref_uv;
> + break;

Can be one if-else instead.

> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (ret <= 0)

= 0 ?! Can you elaborate, please, this case taking into account below?

> + return dev_err_probe(dev, ret, "Cannot use reference %u\n",
> + ref_sel);
> +
> + if (ref_uv)
> + *ref_uv = ret;
> +
> + return 0;
> +}

...

> + ret = ad4130_get_ref_voltage(st, setup_info->ref_sel, NULL);
> + if (ret)
> + return ret;
> +
> + return 0;

return ad4130_get_ref_voltage(st, setup_info->ref_sel, NULL);

...

> + fwnode_property_read_u32(child, "adi,excitation-pin-0",
> + &chan_info->iout0);

No default and/or error check?

...

> + fwnode_property_read_u32(child, "adi,excitation-pin-1",
> + &chan_info->iout1);

Ditto.

...

> +static int ad4130_parse_fw_children(struct iio_dev *indio_dev)
> +{
> + struct ad4130_state *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + struct fwnode_handle *child;
> + int ret;
> +
> + indio_dev->channels = st->chans;
> +
> + device_for_each_child_node(dev, child) {
> + ret = ad4130_parse_fw_channel(indio_dev, child);
> + if (ret)
> + break;
> + }

> + fwnode_handle_put(child);

There is no need to put fwnode if child is NULL. Moreover, the above
pattern might be percepted wrongly, i.e. one may think that
fwnode_handle_put() is a must after a loop.

> + return ret;
> +}

...

> + for (i = 0; i < ARRAY_SIZE(ad4130_int_pin_names); i++) {
> + irq = of_irq_get_byname(dev->of_node, ad4130_int_pin_names[i]);

fwnode_irq_get_byname()

> + if (irq > 0) {
> + st->int_pin_sel = i;
> + break;
> + }
> + }

...

> + st->num_vbias_pins = ret;

I haven't checked this, but be sure that it won't overflow any
preallocated array in the code.

> + ret = device_property_read_u32_array(dev, "adi,vbias-pins",
> + st->vbias_pins,
> + st->num_vbias_pins);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to read vbias pins\n");

...

> + for (j = 0; j < AD4130_PGA_NUM; j++) {
> + unsigned int pow = st->chip_info->resolution + j -
> + st->bipolar;
> + unsigned int nv = div_u64(((ref_uv * 1000000000ull) >>
> + pow), 1000);

Perhaps macros from units.h?

> + st->scale_tbls[i][j][0] = 0;
> + st->scale_tbls[i][j][1] = nv;
> + }

...

> + [ID_AD4130_8_24_LFCSP] = {
> + .name = AD4130_8_NAME,
> + .resolution = 24,

> + .has_int_pin = false,

No need.

> + },

...

> + [ID_AD4130_8_16_LFCSP] = {
> + .name = AD4130_8_NAME,
> + .resolution = 16,

> + .has_int_pin = false,

Ditto.

> + },

...

> +static const struct of_device_id ad4130_of_match[] = {

> + { },

No comma for terminator.

> +};

...

Can you explain why regmap locking is needed?

--
With Best Regards,
Andy Shevchenko