Re: [PATCH 2/6] iio: adc: ad4030: add driver for ad4030-24

Esteban Blanc
2024-08-27T16:45:53Z

On Sat Aug 24, 2024 at 1:21 PM CEST, Jonathan Cameron wrote:
> On Thu, 22 Aug 2024 14:45:18 +0200
> Esteban Blanc <eblanc@baylibre.com> wrote:
>
> > This adds a new driver for the Analog Devices INC. AD4030-24 ADC.
> >
> > The driver implements basic support for the AD4030-24 1 channel
> > differential ADC with hardware gain and offset control.
> >
> > Signed-off-by: Esteban Blanc <eblanc@baylibre.com>
> Hi Esteban
>
> Some additional comments. David did a good review already so
> I've tried not to duplicate too much of that.
>
> The big one in here is don't use extended_name.
> It's effectively deprecated for new drivers plus
> it would have required you add a lot of ABI docs as every
> sysfs file would have a strange name.
>
> > diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> > new file mode 100644
> > index 000000000000..a981dce988e5
> > --- /dev/null
> > +++ b/drivers/iio/adc/ad4030.c
>
> > +struct ad4030_state {
> > + struct spi_device *spi;
> > + struct regmap *regmap;
> > + const struct ad4030_chip_info *chip;
> > + struct gpio_desc *cnv_gpio;
> > + int vref_uv;
> > + int vio_uv;
> > + int offset_avail[3];
> > + u32 conversion_speed_hz;
> > + enum ad4030_out_mode mode;
> > +
> > + /*
> > + * DMA (thus cache coherency maintenance) requires the transfer buffers
> > + * to live in their own cache lines.
> > + */
> > + u8 tx_data[AD4030_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN);
> > + struct {
> > + union {
> > + u8 raw[AD4030_MAXIMUM_RX_BUFFER_SIZE];
> > + struct {
> > + s32 val;
> > + u32 common;
> > + } __packed buffered[AD4030_MAX_HARDWARE_CHANNEL_NB];
>
> David pointed out this doesn't need to be packed.
> Given you have a union here, add __beXX as needed to avoid casts below.

They also pointed out that I should reduce the size for the common field.
I was planing to use an u32 bitfield here, 8 bits for common and 24 bits for
padding. As far as I understood, the C standard is quite flexible on the
size used for bitfield, so I should probably keep the __packed, right?

> > +};
> > +
> > +#define AD4030_CHAN_CMO(_idx) { \
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> > + .type = IIO_VOLTAGE, \
> > + .indexed = 1, \
> > + .channel = _idx * 2 + 2, \
> > + .scan_index = _idx * 2 + 1, \
> > + .extend_name = "Channel" #_idx " common byte part", \
>
> We more or less never use extend name any more because it makes writing
> userspace code much harder. Use the label callback to assign a label instead.
>
> If we were still using this, it would need to be a lot simpler than that
> and no spaces etc as it ends up int he sysfs file names.

> > + .scan_type = { \
> > + .sign = 'u', \
> > + .storagebits = 32, \
> > + .realbits = 8, \
> > + .endianness = IIO_BE, \
> > + }, \
> > +}
> > +
> > +#define AD4030_CHAN_IN(_idx, _storage, _real, _shift) { \
> > + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
> > + BIT(IIO_CHAN_INFO_CALIBBIAS) | \
> > + BIT(IIO_CHAN_INFO_RAW), \
> > + .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
> > + BIT(IIO_CHAN_INFO_CALIBSCALE), \
> > + .type = IIO_VOLTAGE, \
> > + .indexed = 1, \
> > + .channel = _idx * 2, \
> > + .channel2 = _idx * 2 + 1, \
> > + .scan_index = _idx * 2, \
> > + .extend_name = "Channel" #_idx " differential part", \
>
> As above, no to this for same reason.
> This will generate a crazy ABI so I'm a bit surprised that didn't show
> up in your testing. Would have needed a lot of docs even if we did
> still do things this way.

I'm using ADI IIO oscilloscope to check the signals so I didn't get
impacted by the sysfs change. Anyway I will use labels.

> > + .differential = true, \
> > + .scan_type = { \
> > + .sign = 's', \
> > + .storagebits = _storage, \
> > + .realbits = _real, \
> > + .shift = _shift, \
> > + .endianness = IIO_BE, \
> > + }, \
> > +}
> > +
> > +static int ad4030_spi_read(void *context, const void *reg, size_t reg_size,
> > + void *val, size_t val_size)
> > +{
> > + struct ad4030_state *st = context;
> > +
> > + struct spi_transfer xfer = {
> > + .tx_buf = st->tx_data,
> > + .rx_buf = st->rx_data.raw,
> > + .len = reg_size + val_size,
> > + };
> > + int ret;
> > +
> > + memcpy(st->tx_data, reg, reg_size);
> > +
> > + /*
> > + * This should use spi_write_the_read but when doing so, CS never get
> > + * deasserted.
>
> I'm confused. As a single transfer it won't be deasserted in the transfer
> whereas spi_write_then_read() will. So is this comment backwards or
> is it referring to something else?

So, with a single transfer (what is done now), the transfer is working
as expected: CS goes low, the data is transferred, CS goes high again.
With spi_write_then_read(), CS goes low, data is transferred but CS never
goes high again. After some time I get a timeout error in the kernel logs.

> > +static int ad4030_conversion(struct ad4030_state *st,
> > + const struct iio_chan_spec *chan)
> > +{
> > + unsigned int bytes_to_read;
> > + unsigned char byte_index;
> > + unsigned int i;
> > + int ret;
> > +
> > + /* Number of bytes for one differential channel */
> > + bytes_to_read = BITS_TO_BYTES(chan->scan_type.realbits);
> > + /* Add one byte if we are using a differential + common byte mode */
> > + bytes_to_read += (st->mode == AD4030_OUT_DATA_MD_24_DIFF_8_COM ||
> > + st->mode == AD4030_OUT_DATA_MD_16_DIFF_8_COM) ? 1 : 0;
> > + /* Mulitiply by the number of hardware channels */
> > + bytes_to_read *= st->chip->num_channels;
> > +
> > + gpiod_set_value_cansleep(st->cnv_gpio, 1);
> > + ndelay(AD4030_TCNVH_NS);
> > + gpiod_set_value_cansleep(st->cnv_gpio, 0);
> > + ndelay(st->chip->tcyc);
> > +
> > + ret = spi_read(st->spi, st->rx_data.raw, bytes_to_read);
> > + if (ret)
> > + return ret;
> > +
> > + if (st->mode != AD4030_OUT_DATA_MD_24_DIFF_8_COM)
> > + return 0;
> > +
> > + byte_index = BITS_TO_BYTES(chan->scan_type.realbits);
> > + for (i = 0; i < st->chip->num_channels; i++)
> > + st->rx_data.buffered[i].common = ((u8 *)&st->rx_data.buffered[i].val)[byte_index];
> break line after =.
>
> When it doesn't significantly hurt readability we still try to keep to 80
> chars for IIO drivers. People have big screens but a lot of kernel devs
> love to have lots of windows across them - or have bad eyesight due to
> years of code review!

I keep forgeting that checkpatch now defaults at 100 chars...

> > +static int ad4030_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan, int *val,
> > + int *val2, long info)
> > +{
> > + struct ad4030_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
> > + switch (info) {
> > + case IIO_CHAN_INFO_RAW:
> > + return ad4030_single_conversion(indio_dev, chan, val);
> > +
> > + case IIO_CHAN_INFO_SCALE:
> > + *val = (st->vref_uv * 2) / MILLI;
> > + *val2 = st->chip->precision_bits;
> > + return IIO_VAL_FRACTIONAL_LOG2;
>
> No reason you can't read this whilst buffered capture in progress.
> Maybe it's not worth the effort of special casing though.
>
> It is the one thing people do read whilst doing buffered capture
> though because they didn't cache it before starting the buffer
> and it's needed for data interpretation unlike all the other controls.
>
> Maybe just do a
> if (info == IIO_CHAN_INFO_SCALE) {
> }
> block at top of function?

Good catch. I will check for IIO_CHAN_INFO_SCALE before the whole block

> > +static int ad4030_reset(struct ad4030_state *st)
> > +{
> > + struct device *dev = &st->spi->dev;
> > + struct gpio_desc *reset;
> > + int ret;
> > +
> > + /* Use GPIO if available ... */
> > + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > + if (IS_ERR(reset))
> > + return dev_err_probe(dev, PTR_ERR(reset),
> > + "Failed to get reset GPIO\n");
> > +
> > + if (reset) {
> > + ndelay(50);
> > + gpiod_set_value_cansleep(reset, 0);
> > + } else {
> > + /* ... falback to software reset otherwise */
> > + ret = ad4030_enter_config_mode(st);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, AD4030_REG_INTERFACE_CONFIG_A,
> > + AD4030_REG_INTERFACE_CONFIG_A_SW_RESET);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + /* Wait for reset to complete before communicating to it */
>
> I'd rather see a reference for the value than a generic comment
> like this. Also pull the actual value down here. Not particularly
> useful to have a define for what is a real time unless you are going
> to have some combined docs for a bunch of timings (i.e a datasheet
> table reference)

I will put the real value in fsleep call directly. When you say "I'd
rather see a reference for the value", you ment a reference to the place
the value is defined in the datasheet, right?

> > +static int ad4030_detect_chip_info(const struct ad4030_state *st)
> > +{
> > + unsigned int grade;
> > + int ret;
> > +
> > + ret = regmap_read(st->regmap, AD4030_REG_CHIP_GRADE, &grade);
> > + if (ret)
> > + return ret;
> > +
> > + grade = FIELD_GET(AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE, grade);
> > + if (grade != st->chip->grade)
> > + return dev_err_probe(&st->spi->dev, -EINVAL,
> > + "Unknown grade(0x%x) for %s\n", grade,
> > + st->chip->name);
>
> Is this similar to a missmatch on a whoami value?

Yes. It also saved me multiple hours of debuging when the eval board
was not connected porperly and the SPI link was just not working.

> I.e. should we print a message and carry on in the interests of providing
> some degree of support for newer devices on older kernel?
> (fallback compatibles in DT)

Ok, let's go with a warning then.

> > +static const struct spi_device_id ad4030_id_table[] = {
> > + { "ad4030-24", (kernel_ulong_t)&ad4030_24_chip_info },
> > + {}
>
> I'm going to assume you have a bunch of other parts you plan to
> support soon. Otherwise we normally don't add the chip specific
> support until it is needed. It tends to complicate initial driver
> review a little and experience says that sometimes no other devices
> are ever added.

I'm sending the other devices in the same series (patch 4 and 5).
For the sake of reducing noise in the later patches, I've put it in
the initial driver. If you feel like I should wait and do it in the
following patch (patch 4), I can do that.

Thanks for your time,

--
Esteban Blanc
BayLibre