Re: [PATCH v5 3/3] drivers: iio: adc: add support for ad777x family

Andy Shevchenko
2024-09-12T15:16:08Z

On Thu, Sep 12, 2024 at 3:17 PM Ramona Alexandra Nechita
<ramona.nechita@analog.com> wrote:
>
> Add support for AD7770, AD7771, AD7779 ADCs. The device is capable of

"..., and AD7779..."

> sending out data both on DOUT lines interface,as on the SDO line.
> The driver currently implements only the SDO data streaming mode. SPI
> communication is used alternatively for accessing registers and streaming
> data. Register access are protected by crc8.

accesses

...

> +/*
> + * AD7770, AD7771, AD7779 ADC

"..., and AD7779..."

> + *
> + * Copyright 2023-2024 Analog Devices Inc.
> + */

...

> +#define AD7779_MAXCLK_LOWPOWER 4096000

Units? _HZ? _uV?

...

> +#define GAIN_REL 0x555555

Is it something like making value for 12 channels? Can you elaborate a
bit (perhaps in the comment)?

...

> +struct ad7779_state {
> + struct spi_device *spi;
> + const struct ad7779_chip_info *chip_info;
> + struct clk *mclk;
> + struct iio_trigger *trig;
> + struct completion completion;
> + unsigned int sampling_freq;
> + enum ad7779_filter filter_enabled;
> + /*
> + * DMA (thus cache coherency maintenance) requires the
> + * transfer buffers to live in their own cache lines.
> + */
> + u8 reg_rx_buf[3] __aligned(IIO_DMA_MINALIGN);
> + u8 reg_tx_buf[3];
> + u32 spidata_rx[8];
> + u32 spidata_tx[8];
> + u8 reset_buf[8];
> +};

Have you run `pahole` to check if this can be optimised in size?

...

> +static int ad7779_spi_read(struct ad7779_state *st, u8 reg, u8 *rbuf)
> +{
> + int ret;
> + int length = 3;
> + u8 crc_buf[2];
> + u8 exp_crc = 0;
> + struct spi_transfer reg_read_tr[] = {
> + {
> + .tx_buf = st->reg_tx_buf,
> + .rx_buf = st->reg_rx_buf,
> + },
> + };
> +
> + if (reg == AD7779_REG_GEN_ERR_REG_1_EN)
> + length = 2;

Does it mean the crc byte will be ignored? If so, why do we even
bother to spend resources on calculating it in that case? Same Q for
other similar cases.

> + reg_read_tr[0].len = length;
> +
> + st->reg_tx_buf[0] = AD7779_SPI_READ_CMD | FIELD_GET(AD7779_REG_MSK, reg);
> + st->reg_tx_buf[1] = 0;
> + st->reg_tx_buf[2] = crc8(ad7779_crc8_table, st->reg_tx_buf, 2, 0);
> +
> + ret = spi_sync_transfer(st->spi, reg_read_tr, ARRAY_SIZE(reg_read_tr));
> + if (ret)
> + return ret;
> +
> + crc_buf[0] = AD7779_SPI_READ_CMD | FIELD_GET(AD7779_REG_MSK, reg);
> + crc_buf[1] = st->reg_rx_buf[1];
> + exp_crc = crc8(ad7779_crc8_table, crc_buf, 2, 0);
> + if (reg != AD7779_REG_GEN_ERR_REG_1_EN && exp_crc != st->reg_rx_buf[2]) {
> + dev_err(&st->spi->dev, "Bad CRC %x, expected %x",
> + st->reg_rx_buf[2], exp_crc);
> + return -EINVAL;
> + }
> + *rbuf = st->reg_rx_buf[1];
> +
> + return 0;
> +}

...

> + regval = data;
> + regval &= ~mask;
> + regval |= val;

Traditional pattern is to have this as

regval = (data & ~mask) | (val & mask);

...

> +static int ad7779_set_sampling_frequency(struct ad7779_state *st,
> + unsigned int sampling_freq)
> +{
> + int ret;
> + unsigned int dec;
> + unsigned int div;
> + unsigned int decimal;
> + int temp;
> + unsigned int kfreq;

freq_khz will be better name

> + if (st->filter_enabled == AD7779_SINC3 &&
> + sampling_freq > AD7779_SINC3_MAXFREQ)
> + return -EINVAL;
> +
> + if (st->filter_enabled == AD7779_SINC5 &&
> + sampling_freq > AD7779_SINC5_MAXFREQ)
> + return -EINVAL;
> +
> + if (sampling_freq > AD7779_SPIMODE_MAX_SAMP_FREQ)
> + return -EINVAL;
> +
> + div = AD7779_HIGHPOWER_DIV;
> +
> + kfreq = sampling_freq / KILO;
> + dec = div / kfreq;
> +
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB,
> + FIELD_GET(AD7779_FREQ_MSB_MSK, dec));
> + if (ret)
> + return ret;
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB,
> + FIELD_GET(AD7779_FREQ_LSB_MSK, dec));
> + if (ret)
> + return ret;

> + if (div % kfreq) {

It's better to combine with / above, because some ISAs may have a
single assembly instruction to do both at the same time. I'm not sure
compiler (os some versions of it) may see this.

dec = div / freq_khz;
frac = div % freq_khz;
...
if (frac) {
...
}

> + temp = (div * KILO) / kfreq;
> + decimal = ((temp - dec * KILO) << 16) / KILO;

I would add a small comment to show the formula behind this.

> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB,
> + FIELD_GET(AD7779_FREQ_MSB_MSK, decimal));
> + if (ret)
> + return ret;
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB,
> + FIELD_GET(AD7779_FREQ_LSB_MSK, decimal));
> + if (ret)
> + return ret;
> + } else {
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB,
> + FIELD_GET(AD7779_FREQ_MSB_MSK, 0x0));
> + if (ret)
> + return ret;
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB,
> + FIELD_GET(AD7779_FREQ_LSB_MSK, 0x0));
> + if (ret)
> + return ret;
> + }
> + ret = ad7779_spi_write(st, AD7779_REG_SRC_UPDATE, 0x1);
> + if (ret)
> + return ret;
> +
> + /* SRC update settling time */
> + fsleep(15);

+ Blank line, otherwise it's unclear how fsleep() is applied (or to
which sections).

> + ret = ad7779_spi_write(st, AD7779_REG_SRC_UPDATE, 0x0);
> + if (ret)
> + return ret;
> +
> + /* SRC update settling time */
> + fsleep(15);

Alternatively make a helper to avoid potential desynchronisation in
the code pieces

statuc int ad7779_update_src(..., val)
{
...
/* ... */
fsleep(...);

return 0;
}

> + st->sampling_freq = sampling_freq;
> +
> + return 0;
> +}

...

> +static int ad7779_set_calibscale(struct ad7779_state *st, int channel, int val)
> +{
> + int ret;
> + unsigned int gain;
> + unsigned long long tmp;
> + u8 gain_bytes[3];
> +
> + tmp = val * 5592405LL;
> + gain = DIV_ROUND_CLOSEST_ULL(tmp, MEGA);

Formula to be explained?
Magic number to be described (in the same comment), please?

> + put_unaligned_be24(gain, gain_bytes);
> + ret = ad7779_spi_write(st,
> + AD7779_REG_CH_GAIN_UPPER_BYTE(channel),
> + gain_bytes[0]);
> + if (ret)
> + return ret;
> +
> + ret = ad7779_spi_write(st,
> + AD7779_REG_CH_GAIN_MID_BYTE(channel),
> + gain_bytes[1]);
> + if (ret)
> + return ret;
> +
> + return ad7779_spi_write(st,
> + AD7779_REG_CH_GAIN_LOWER_BYTE(channel),
> + gain_bytes[2]);
> +}

...

> + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
> + switch (mask) {
> + case IIO_CHAN_INFO_CALIBSCALE:
> + *val = ad7779_get_calibscale(st, chan->channel);
> + if (*val < 0)
> + return -EINVAL;
> + *val2 = GAIN_REL;
> + return IIO_VAL_FRACTIONAL;
> + case IIO_CHAN_INFO_CALIBBIAS:
> + *val = ad7779_get_calibbias(st, chan->channel);
> + if (*val < 0)
> + return -EINVAL;
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = st->sampling_freq;
> + if (*val < 0)
> + return -EINVAL;
> + return IIO_VAL_INT;
> + }
> + return -EINVAL;
> + }

> + unreachable();

Hmm... Is it necessary? Same Q for other similar cases. I.o.w. what
will be if we don't add this line?

> +}

...

> +static irqreturn_t ad7779_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ad7779_state *st = iio_priv(indio_dev);
> + int ret;
> + int bit;
> + int k = 0;
> + /*
> + * Each channel shifts out HEADER + 24 bits of data therefore 8 * u32
> + * for the data and 64 bits for the timestamp
> + */

May you do the respective structure and use aligned_s64 for the timestamp?

> + u32 tmp[10];
> +
> + struct spi_transfer sd_readback_tr[] = {
> + {
> + .rx_buf = st->spidata_rx,
> + .tx_buf = st->spidata_tx,
> + .len = AD7779_NUM_CHANNELS * AD7779_CHAN_DATA_SIZE,
> + }
> + };
> +
> + if (!iio_buffer_enabled(indio_dev))
> + goto exit_handler;
> +
> + st->spidata_tx[0] = AD7779_SPI_READ_CMD;
> + ret = spi_sync_transfer(st->spi, sd_readback_tr,
> + ARRAY_SIZE(sd_readback_tr));
> + if (ret) {
> + dev_err(&st->spi->dev,
> + "spi transfer error in irq handler");
> + goto exit_handler;
> + }
> +
> + for_each_set_bit(bit, indio_dev->active_scan_mask, AD7779_NUM_CHANNELS - 1)
> + tmp[k++] = st->spidata_rx[bit];
> +
> + iio_push_to_buffers_with_timestamp(indio_dev, &tmp[0], pf->timestamp);
> +
> +exit_handler:
> + iio_trigger_notify_done(indio_dev->trig);
> + return IRQ_HANDLED;
> +}

...

> + st->mclk = devm_clk_get_enabled(&spi->dev, "mclk");
> + if (IS_ERR(st->mclk))
> + return PTR_ERR(st->mclk);

> + if (!spi->irq)
> + return dev_err_probe(&spi->dev, ret,
> + "DRDY irq not present\n");

This can be done much earlier and avoid unneeded resource allocations.

...

> +static int ad7779_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct ad7779_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ad7779_spi_write_mask(st, AD7779_REG_GENERAL_USER_CONFIG_1,
> + AD7779_MOD_POWERMODE_MSK,
> + FIELD_PREP(AD7779_MOD_POWERMODE_MSK,
> + AD7779_LOW_POWER));
> + if (ret)
> + return ret;
> +
> + return 0;

return ad7779_spi_write_mask(...);

> +}
> +
> +static int ad7779_resume(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct ad7779_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ad7779_spi_write_mask(st, AD7779_REG_GENERAL_USER_CONFIG_1,
> + AD7779_MOD_POWERMODE_MSK,
> + FIELD_PREP(AD7779_MOD_POWERMODE_MSK,
> + AD7779_HIGH_POWER));
> + if (ret)
> + return ret;
> +
> + return 0;

Ditto.

> +}

...

> +static const struct spi_device_id ad7779_id[] = {
> + {
> + .name = "ad7770",
> + .driver_data = (kernel_ulong_t)&ad7770_chip_info

Leave trailing comma.

> + },
> + {
> + .name = "ad7771",
> + .driver_data = (kernel_ulong_t)&ad7771_chip_info

Ditto.

> + },
> + {
> + .name = "ad7779",
> + .driver_data = (kernel_ulong_t)&ad7779_chip_info

Ditto.

> + },
> + { }
> +};

--
With Best Regards,
Andy Shevchenko