[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAHp75VfzX8u45J3634yN5p-QTeT7w0Bos27OxeWOsb3MQ2VRVw@mail.gmail.com>
Date: Wed, 13 Apr 2022 18:41:34 +0300
From: Andy Shevchenko <andy.shevchenko@...il.com>
To: Cosmin Tanislav <demonsingur@...il.com>
Cc: Jonathan Cameron <jic23@...nel.org>,
Rob Herring <robh+dt@...nel.org>,
Linus Walleij <linus.walleij@...aro.org>,
linux-iio <linux-iio@...r.kernel.org>,
"open list:GPIO SUBSYSTEM" <linux-gpio@...r.kernel.org>,
Linux Kernel Mailing List <linux-kernel@...r.kernel.org>,
devicetree <devicetree@...r.kernel.org>,
Cosmin Tanislav <cosmin.tanislav@...log.com>
Subject: Re: [PATCH v1 3/3] iio: adc: ad4130: add AD4130 driver
On Wed, Apr 13, 2022 at 1:41 PM Cosmin Tanislav <demonsingur@...il.com> 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
Powered by blists - more mailing lists