Re: [PATCH 3/3] iio: meter: add ADE9078 driver

From: Jonathan Cameron
Date: Sun Apr 10 2022 - 11:34:53 EST


On Thu, 7 Apr 2022 12:56:47 +0000
"Hegbeli, Ciprian" <Ciprian.Hegbeli@xxxxxxxxxx> wrote:

> > Hi Ciprian
> >
> > I took a very quick end of day look so probably missed some stuff I'll
> > pick up on in v2. Biggest issue is new ABI without docs. Various
> > other comments inline. Plus as already observed, please use full name
> > in from and SoB as they form part of the Developer Certificate of Origin.
> >
> > Thanks,
> >
> > Jonathan
>
> Hi Jonathan,
>
> Thank you for the review. I wanted to answer the regmap related
> questions and maybe ask for some advice regarding them. There
> are some details in the messages below; the short versions is that
> it's doable but I will have to give up on some of the regmap
> functionality and mask some of the basic regmap functions in driver
> specific functions. These changes might make the code less
> readable.
>
> > > +static const struct reg_sequence ade9078_reg_sequence[] = {
> > > + { ADE9078_REG_PGA_GAIN, ADE9078_PGA_GAIN },
> > > + { ADE9078_REG_CONFIG0, ADE9078_CONFIG0 },
> > > + { ADE9078_REG_CONFIG1, ADE9078_CONFIG1 },
> > > + { ADE9078_REG_CONFIG2, ADE9078_CONFIG2 },
> > > + { ADE9078_REG_CONFIG3, ADE9078_CONFIG3 },
> > > + { ADE9078_REG_ACCMODE, ADE9078_ACCMODE },
> > > + { ADE9078_REG_ZX_LP_SEL, ADE9078_ZX_LP_SEL },
> > > + { ADE9078_REG_MASK0, ADE9078_MASK0 },
> > > + { ADE9078_REG_MASK1, ADE9078_MASK1 },
> > > + { ADE9078_REG_EVENT_MASK, ADE9078_EVENT_MASK },
> > > + { ADE9078_REG_WFB_CFG, ADE9078_WFB_CFG },
> > > + { ADE9078_REG_VLEVEL, ADE9078_VLEVEL },
> > > + { ADE9078_REG_DICOEFF, ADE9078_DICOEFF },
> > > + { ADE9078_REG_EGY_TIME, ADE9078_EGY_TIME },
> > > + { ADE9078_REG_EP_CFG, ADE9078_EP_CFG },
> > > + { ADE9078_REG_RUN, ADE9078_RUN_ON }
> > > +};
>
> CONFIG0, MASK0, MASK1, EVENT_MASK, VLEVEL, DICOEFF are
> 32bit registers.

Ah. I'd missed that. Indeed changes the whole discussion.

>
> > > +/*
> >
> > As mentioned below, I don't immediately understand why this can't
> > be done with appropriate standard regmap. Perhaps you could give
> > more details of what is missing. I'd like to see that added to
> > regmap if possible.
> >
>
> I started implementing the driver with a standard SPI and using
> bulk read/write for the 32 bit registers. This however renders some
> of the standard features of the regmap unusable (as far as I know).
> For example I can't use regmap_update_bits for 32 bit registers,
> which happens a few times, which I have marked in the code below.
> I could implement a substitute function in my driver for this, but that
> would complicate the code (in my opinion).

Agreed - regmap doesn't fit well in my opinion when there are a mixture
of register sizes. This may just be a case where you indeed should
just not use regmap at all. It's challenging to make it work
and sometimes it's not sensible to bother.


>
> > > + * ade9078_spi_write_reg() - ade9078 write register over SPI
> > > + * the data format for communicating with the ade9078 over SPI
> > > + * is very specific and can access both 32bit and 16bit registers
> > > + * @context: void pointer to the SPI device
> > > + * @reg: address of the of desired register
> > > + * @val: value to be written to the ade9078
> > > + */
> > > +static int ade9078_spi_write_reg(void *context,
> > > + unsigned int reg,
> > > + unsigned int val)
> > > +{
> > > + struct device *dev = context;
> > > + struct spi_device *spi = to_spi_device(dev);
> > > + struct ade9078_state *st = spi_get_drvdata(spi);
> >
> > > +
> > > + u16 addr;
> > > + int ret = 0;
> > > + struct spi_transfer xfer[] = {
> > > + {
> > > + .tx_buf = st->tx,
> > > + },
> > > + };
> > > +
> > > + addr = FIELD_PREP(ADE9078_REG_ADDR_MASK, reg);
> > > +
> > > + put_unaligned_be16(addr, st->tx);
> > > + put_unaligned_be32(val, &st->tx[2]);
> > > +
> > > + if (reg > ADE9078_REG_RUN && reg < ADE9078_REG_VERSION) {
> > > + put_unaligned_be16(val, &st->tx[2]);
> > > + xfer[0].len = 4;
> > > + } else {
> > > + xfer[0].len = 6;
> > > + }
> > > +
> > > + ret = spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
> > > + if (ret) {
> > > + dev_err(&st->spi->dev, "problem when writing register
> > 0x%x",
> > > + reg);
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +/*
> > > + * ade9078_spi_write_reg() - ade9078 read register over SPI
> > > + * the data format for communicating with the ade9078 over SPI
> > > + * is very specific and can access both 32bit and 16bit registers
> > > + * @context: void pointer to the SPI device
> > > + * @reg: address of the of desired register
> > > + * @val: value to be read to the ade9078
> > > + */
> > > +static int ade9078_spi_read_reg(void *context,
> > > + unsigned int reg,
> > > + unsigned int *val)
> > > +{
> > > + struct device *dev = context;
> > > + struct spi_device *spi = to_spi_device(dev);
> > > + struct ade9078_state *st = spi_get_drvdata(spi);
> > > +
> > > + u16 addr;
> > > + int ret = 0;
> > > + struct spi_transfer xfer[] = {
> > > + {
> > > + .tx_buf = st->tx,
> > > + .len = 2,
> > > + },
> > > + {
> > > + .rx_buf = st->rx,
> > > + },
> > > + };
> > > +
> > > + addr = FIELD_PREP(ADE9078_REG_ADDR_MASK, reg) |
> > > + ADE9078_REG_READ_BIT_MASK;
> > > +
> > > + put_unaligned_be16(addr, st->tx);
> > > +
> > > + if (reg > ADE9078_REG_RUN && reg < ADE9078_REG_VERSION)
> > > + xfer[1].len = 4;
> > > + else
> > > + xfer[1].len = 6;
> >
> > This doesn't look like a fixed length register which is expected
> > for regmap... Also that len should just be the rx register which
> > you treat below as 16 bit and 32 bit (so 2 and 4, not 4 and 6).
> >
> > Can the larger registers be treated as bulk reads of pairs of smaller ones?
> >
>
> It could, but I would still need an if or a type of "decoder" to determine
> if it is a 16 bit or 32 bit register, which means I still need a form of
> ade9078_read_reg which selects between a regmap_read function
> or regmap_bulk_read. This goes for write as well.

My assumption (wrong :) was that this was the normal 'large register' case
where we were actually looking at 2 registers that had to always be
read as a pair to make up the large register. That isn't true here.

>
> > > +
> > > + ret = spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
> > > + if (ret) {
> > > + dev_err(&st->spi->dev, "problem when reading register
> > 0x%x",
> > > + reg);
> > > + goto err_ret;
> > > + }
> > > +
> > > + //registers which are 16 bits
> > > + if (reg > 0x480 && reg < 0x4FE)
> > > + *val = get_unaligned_be16(st->rx);
> > > + else
> > > + *val = get_unaligned_be32(st->rx);
> > > +
> > > +err_ret:
> > > + return ret;
> > > +}
> > > +
> > > +/*
> > > + * ade9078_is_volatile_reg() - list of ade9078 registers which should use
> > > + * caching
> > > + * @dev: device data
> > > + * @reg: address of the of desired register
> > > + */
> > > +static bool ade9078_is_volatile_reg(struct device *dev, unsigned int reg)
> > > +{
> > > + switch (reg) {
> > > + case ADE9078_REG_STATUS0:
> > > + case ADE9078_REG_STATUS1:
> > > + case ADE9078_REG_MASK0:
> > > + case ADE9078_REG_MASK1:
> > > + return true;
> > > + default:
> > > + return false;
> > > + }
> > > +}
> > > +
>
> I left this part in for context.
>
> > > +/*
> > > + * ade9078_write_event_config() - IIO event configure to enable zero-
> > crossing
> >
> > All these function description comments should be valid kernel-doc.
> >
> > > + * and zero-crossing timeout on voltage and current for each phases.
> > These
> > > + * events will also influence the trigger conditions for the buffer capture.
> > > + */
> > > +static int ade9078_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 ade9078_state *st = iio_priv(indio_dev);
> > > + u32 interrupts;
> > > + u32 number;
> > > + int ret;
> > > +
> > > + number = chan->channel;
> > > +
> > > + switch (number) {
> > > + case ADE9078_PHASE_A_NR:
> >
> > I would use a lookup on the phase into an array of structures.
> > The structure would then have fields for which bit to set etc
> > for a voltage channel and for a current channel.
> >
> > That way this all becomes one bit of code and some const data
> > rather that 3 sets of near identical code. If you can make
> > this sort of thing data rather than code that is almost always
> > the best choice.
> >
> > > + if (chan->type == IIO_VOLTAGE) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXVA_BIT;
> > > + interrupts |= ADE9078_ST1_ZXTOVA_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXVA_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXVA_BIT;
> > > + interrupts &= ~ADE9078_ST1_ZXTOVA_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXVA_BIT;
> > > + }
> > > + } else if (chan->type == IIO_CURRENT) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXIA_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXIA_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXIA_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXIA_BIT;
> > > + }
> > > + }
> > > + break;
> > > + case ADE9078_PHASE_B_NR:
> > > + if (chan->type == IIO_VOLTAGE) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXVB_BIT;
> > > + interrupts |= ADE9078_ST1_ZXTOVB_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXVB_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXVB_BIT;
> > > + interrupts &= ~ADE9078_ST1_ZXTOVB_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXVB_BIT;
> > > + }
> > > + } else if (chan->type == IIO_CURRENT) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXIB_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXIB_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXIB_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXIB_BIT;
> > > + }
> > > + }
> > > + break;
> > > + case ADE9078_PHASE_C_NR:
> > > + if (chan->type == IIO_VOLTAGE) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXVC_BIT;
> > > + interrupts |= ADE9078_ST1_ZXTOVC_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXVC_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXVC_BIT;
> > > + interrupts &= ~ADE9078_ST1_ZXTOVC_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXVC_BIT;
> > > + }
> > > + } else if (chan->type == IIO_CURRENT) {
> > > + if (state) {
> > > + interrupts |= ADE9078_ST1_ZXIC_BIT;
> > > + st->wfb_trg |=
> > ADE9078_WFB_TRG_ZXIC_BIT;
> > > + } else {
> > > + interrupts &= ~ADE9078_ST1_ZXIC_BIT;
> > > + st->wfb_trg &=
> > ~ADE9078_WFB_TRG_ZXIC_BIT;
> > > + }
> > > + }
> > > + break;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_STATUS1,
> > GENMASK(31, 0));
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return regmap_update_bits(st->regmap, ADE9078_REG_MASK1,
> > interrupts,
> > > + interrupts);
> > > +}
>
> MASK1 is a 32 bit registers.
>
> > > +/*
> > > + * ade9078_wfb_interrupt_setup() - Configures the wave form buffer
> > interrupt
> > > + * according to modes
> > > + * @st: ade9078 device data
> > > + * @mode: modes according to datasheet; values [0-2]
> > > + *
> > > + * This sets the interrupt register and other registers related to the
> > > + * interrupts according to mode [0-2] from the datasheet
> > > + */
> > > +static int ade9078_wfb_interrupt_setup(struct ade9078_state *st, u8
> > mode)
> > > +{
> > > + int ret;
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_WFB_TRG_CFG, 0x0);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + if (mode == ADE9078_WFB_FULL_MODE || mode ==
> > ADE9078_WFB_EN_TRIG_MODE) {
> >
> > A switch statement would be cleaner here.
> >
> > > + ret = regmap_write(st->regmap,
> > ADE9078_REG_WFB_PG_IRQEN,
> > > + ADE9078_MODE_0_1_PAGE_BIT);
> > > + if (ret)
> > > + return ret;
> > > + } else if (mode == ADE9078_WFB_C_EN_TRIG_MODE) {
> > > + ret = regmap_write(st->regmap,
> > ADE9078_REG_WFB_PG_IRQEN,
> > > + ADE9078_MODE_2_PAGE_BIT);
> > > + if (ret)
> > > + return ret;
> > > + }
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_STATUS0,
> > GENMASK(31, 0));
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return regmap_update_bits(st->regmap, ADE9078_REG_MASK0,
> > > + ADE9078_ST0_PAGE_FULL_BIT,
> > > + ADE9078_ST0_PAGE_FULL_BIT);
> > > + if (ret)
> >
> > Unreachable code...
> >
> > > + return ret;
> > > +
> > > + return 0;
> > > +}
>
> MASK0 is a 32 bit registers.
>
> > > +/*
> > > + * ade9078_buffer_postdisable() - after the iio is disable
> > > + * this will disable the ade9078 internal buffer for acquisition
> > > + * @indio_dev: the IIO device
> > > + */
> > > +static int ade9078_buffer_postdisable(struct iio_dev *indio_dev)
> > > +{
> > > + struct ade9078_state *st = iio_priv(indio_dev);
> > > + u32 interrupts = 0;
> > > + int ret;
> > > +
> > > + ret = ade9078_en_wfb(st, false);
> > > + if (ret) {
> > > + dev_err(&st->spi->dev, "Post-disable wfb disable fail");
> > > + return ret;
> > > + }
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_WFB_TRG_CFG, 0x0);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + interrupts |= ADE9078_ST0_WFB_TRIG_BIT;
> > > + interrupts |= ADE9078_ST0_PAGE_FULL_BIT;
> > > +
> > > + return regmap_update_bits(st->regmap, ADE9078_REG_MASK0,
> > interrupts, 0);
> > > + if (ret) {
> > > + dev_err(&st->spi->dev, "Post-disable update maks0 fail");
> > > + return ret;
> > > + }
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_STATUS0,
> > GENMASK(31, 0));
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return 0;
> >
> > return regmap_write()...
> >
> > > +}
>
> MASK0 is updated again.
>
> > > +/*
> > > + * ade9078_setup() - initial register setup of the ade9078
> > > + * @st: ade9078 device data
> > > + */
> > > +static int ade9078_setup(struct ade9078_state *st)
> > > +{
> > > + int ret = 0;
> > The value here is never used.
> >
> > Make sure you run a bunch of build tests including setting W=1 and ensuring
> > it
> > is clean + run at very least sparse. Some of those tests would warn about
> > this.
> >
> > > +
> > > + ret = regmap_multi_reg_write(st->regmap, ade9078_reg_sequence,
> > > + ARRAY_SIZE(ade9078_reg_sequence));
> > > + if (ret)
> > > + return ret;
> > > +
>
> regmap_multi_reg_write is another function I don't think I could use at its
> full extent if I switch to standard regmap with bulk transfers, because I
> configure both 16 bit and 32 bit registres. The 32 bit registers configured
> in this set are listed above, near the begging of the e-mail.
>
> > > + msleep_interruptible(2);
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_STATUS0,
> > GENMASK(31, 0));
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = regmap_write(st->regmap, ADE9078_REG_STATUS1,
> > GENMASK(31, 0));
> >
> > return regmap_write()
> >
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return ret;
> > > +}
> > > +
>
> I left this part in for context.
>
> > > +/*
> > > + * Regmap configuration
> > > + * The register access of the ade9078 requires a 16 bit address
> > > + * with the read flag on bit 3. This is not supported by default
> > > + * regmap functionality, thus reg_read and reg_write have been
> > > + * replaced with custom functions
> >
> > How big would the changes needed to support this in the regmap
> > core be? Superficially I can't immediately see why it won't work.
> > regmap appears to support setting flags in any of the address bytes
> > as it uses regmap_set_work_buf_flag_mask() internally and
> > that will set bits in any of the reg_bits/8 bytes.
> >
> > > + */
> > > +static const struct regmap_config ade9078_regmap_config = {
> > > + .reg_bits = 16,
> > > + .val_bits = 32,
> > > + .zero_flag_mask = true,
> > > + .cache_type = REGCACHE_RBTREE,
> > > + .reg_read = ade9078_spi_read_reg,
> > > + .reg_write = ade9078_spi_write_reg,
> > > + .volatile_reg = ade9078_is_volatile_reg,
> > > +};
>
> It dose work but as mentioned above it comes with an additional set
> of driver specific functions which will ultimately mask the underlying
> regmap functions in the best case scenario.
>
> All in all, I could implement the changes for V2 but given the fact that
> this would mean replacing some regmap functions, I decide to ask for your
> opinion and some guidance first.

I'd just drop regmap and do things the old fashions (and more flexible)
way. Whilst the device is register based - mixed sized registers are
a horrible hardware quirk so it doesn't map (afaik) to regmap.

Jonathan

>
> - Ciprian