RE: [PATCH v2 2/2] one-bit-adc-dac: Add initial version of one bit ADC-DAC

From: Pop, Cristian
Date: Fri Jan 21 2022 - 05:24:49 EST


Hi Jonathan,

The additional functionality it is a little bit unclear to me.

> -----Original Message-----
> From: Jonathan Cameron <jic23@xxxxxxxxxx>
> Sent: Sunday, January 16, 2022 1:52 PM
> To: Pop, Cristian <Cristian.Pop@xxxxxxxxxx>
> Cc: linux-iio@xxxxxxxxxxxxxxx; linux-gpio@xxxxxxxxxxxxxxx; linux-
> kernel@xxxxxxxxxxxxxxx; devicetree@xxxxxxxxxxxxxxx; robh+dt@xxxxxxxxxx
> Subject: Re: [PATCH v2 2/2] one-bit-adc-dac: Add initial version of one bit
> ADC-DAC
>
> [External]
>
> On Tue, 11 Jan 2022 13:59:19 +0200
> Cristian Pop <cristian.pop@xxxxxxxxxx> wrote:
>
> > This allows remote reading and writing of the GPIOs. This is useful in
> > application that run on another PC, at system level, where multiple
> > iio devices and GPIO devices are integrated together.
> Hi Cristian,
>
> So I fully understand why this can be useful, but it is a software only
> construction so I'm not convinced that it makes sense to necessarily
> configure it via device tree. A better fit may well be configfs.
> (note I always meant to add configfs controls for iio_hwmon but haven't
> found the time to do it yet...)
>
> As it currently stands, doing only polled / sysfs reads this driver doesn't do
> enough to justify its existence. You could just do all this in userspace using
> the existing gpio interfaces. So to be useful I'd expect it to at least do
> triggered buffer capture.

What do you mean by triggered buffer capture? Should I save previous GPIO
states into a buffer? What should I do with them?

A useful use case that I see:
- Is to register a function callback (in kernel space, maybe user space that
should be used by a different driver), and called in the interrupt handler
when the state of an input GPIO changes.
- Output to a GPIO from a buffer using a clock, obtaining a PWM like signal.

>
> When we have talked about handling digital signals int he past we discussed
> whether the IIO channel description would also benefit from tighter packing
> than the current minimum of a byte per channel. Perhaps we don't
> technically 'need' it here but if we do add it in future it will be hard to retrofit
> into this driver without big userspace ABI changes (i.e. breaking all existing
> users).
>
> I've not repeated stuff Andy got it in his review (assuming I remembered it
> was something Andy raised).
>
> Conclusion:
> 1) Creation interface needs a rethink or strong argument why it belongs in
> DT.
> 2) Driver needs to do more to justify it's existence.
>
> Jonathan
>
> >
> > Signed-off-by: Cristian Pop <cristian.pop@xxxxxxxxxx>
> > ---
> > drivers/iio/addac/Kconfig | 8 +
> > drivers/iio/addac/Makefile | 1 +
> > drivers/iio/addac/one-bit-adc-dac.c | 229
> > ++++++++++++++++++++++++++++
> > 3 files changed, 238 insertions(+)
> > create mode 100644 drivers/iio/addac/one-bit-adc-dac.c
> >
> > diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig
> > index 138492362f20..5f311f4a747e 100644
> > --- a/drivers/iio/addac/Kconfig
> > +++ b/drivers/iio/addac/Kconfig
> > @@ -17,4 +17,12 @@ config AD74413R
> > To compile this driver as a module, choose M here: the
> > module will be called ad74413r.
> >
> > +config ONE_BIT_ADC_DAC
> > + tristate "ONE_BIT_ADC_DAC driver"
> > + help
> > + Say yes here to build support for ONE_BIT_ADC_DAC driver.
>
> Needs a lot more detail here. Though naming it to be explicitly about GPIO to
> IIO binding would help.
>
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called one-bit-adc-dac.
> > +
> > endmenu
> > diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile
> > index cfd4bbe64ad3..0a33f0706b55 100644
> > --- a/drivers/iio/addac/Makefile
> > +++ b/drivers/iio/addac/Makefile
> > @@ -5,3 +5,4 @@
> >
> > # When adding new entries keep the list in alphabetical order
> > obj-$(CONFIG_AD74413R) += ad74413r.o
> > +obj-$(CONFIG_ONE_BIT_ADC_DAC) += one-bit-adc-dac.o
> > diff --git a/drivers/iio/addac/one-bit-adc-dac.c
> > b/drivers/iio/addac/one-bit-adc-dac.c
> > new file mode 100644
> > index 000000000000..5680de594429
> > --- /dev/null
> > +++ b/drivers/iio/addac/one-bit-adc-dac.c
> > @@ -0,0 +1,229 @@
> > +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
> > +/*
> > + * one-bit-adc-dac
>
> Improve description here as well. It's really just a wrapper for a gpio in an IIO
> channel, so just say that. Fine to say the representation is 1 bit ADC or DAC
> channels as well, but I think the GPIO part will be more obvious to the casual
> reader.
>
> > + *
> > + * Copyright 2022 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/gpio/consumer.h>
> > +
> > +enum ch_direction {
> > + CH_IN,
> > + CH_OUT,
> > +};
> > +
> > +struct one_bit_adc_dac_state {
> > + int in_num_ch;
> > + int out_num_ch;
> > + struct platform_device *pdev;
>
> Not used after probe so don't keep it around.
>
> > + struct gpio_descs *in_gpio_descs;
> > + struct gpio_descs *out_gpio_descs;
> > + const char **labels;
> > +};
> > +
> > +static int one_bit_adc_dac_read_raw(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan, int *val, int *val2, long info) {
> > + struct one_bit_adc_dac_state *st = iio_priv(indio_dev);
> > + struct gpio_descs *descs;
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_RAW:
> > + if (chan->output)
> > + descs = st->out_gpio_descs;
> > + else
> > + descs = st->in_gpio_descs;
> > + *val = gpiod_get_value_cansleep(descs->desc[chan-
> >channel]);
> > +
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int one_bit_adc_dac_write_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int val,
> > + int val2,
> > + long info)
> > +{
> > + struct one_bit_adc_dac_state *st = iio_priv(indio_dev);
> > + int channel = chan->channel;
> > +
> > + if (!chan->output)
> > + return 0;
>
> -EINVAL; It's an error that should be reported to userspace..
>
> > +
> > + switch (info) {
> > + case IIO_CHAN_INFO_RAW:
> > + gpiod_set_value_cansleep(st->out_gpio_descs-
> >desc[channel], val);
> > +
> > + return 0;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int one_bit_adc_dac_read_label(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan, char *label) {
> > + struct one_bit_adc_dac_state *st = iio_priv(indio_dev);
> > + int ch;
> > +
> > + if (chan->output)
> > + ch = chan->channel + st->in_num_ch;
> > + else
> > + ch = chan->channel;
> > +
> > + return sprintf(label, "%s\n", st->labels[ch]); }
> > +
> > +static const struct iio_info one_bit_adc_dac_info = {
> > + .read_raw = &one_bit_adc_dac_read_raw,
> > + .write_raw = &one_bit_adc_dac_write_raw,
> > + .read_label = &one_bit_adc_dac_read_label, };
> > +
> > +static int one_bit_adc_dac_set_ch(struct iio_chan_spec *channels,
> > + int num_ch,
> > + enum ch_direction direction)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < num_ch; i++) {
> > + channels[i].type = IIO_VOLTAGE;
> > + channels[i].indexed = 1;
> > + channels[i].channel = i;
> > + channels[i].info_mask_separate =
> BIT(IIO_CHAN_INFO_RAW);
> > + channels[i].output = direction;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int one_bit_adc_dac_set_channel_label(struct iio_dev *indio_dev,
> > + struct iio_chan_spec
> *channels,
> > + int num_channels)
> > +{
> > + struct device *device = indio_dev->dev.parent;
> > + struct one_bit_adc_dac_state *st = iio_priv(indio_dev);
> > + struct fwnode_handle *fwnode;
> > + struct fwnode_handle *child;
> > + struct iio_chan_spec *chan;
> > + const char *label;
> > + int crt_ch = 0, child_num, i = 0;
> > +
> > + fwnode = dev_fwnode(device);
> > + child_num = device_get_child_node_count(device);
> > +
> > + st->labels = devm_kzalloc(device, sizeof(*st->labels) * child_num,
> GFP_KERNEL);
> > + if (!st->labels)
> > + return -ENOMEM;
> > +
> > + i = child_num;
> > + fwnode_for_each_child_node(fwnode, child) {
> > + if (fwnode_property_read_u32(child, "reg", &crt_ch))
> > + continue;
> > +
> > + if (crt_ch >= num_channels)
> > + continue;
> > +
> > + if (fwnode_property_read_string(child, "label", &label))
> > + continue;
> > +
> > + chan = &channels[crt_ch];
> ? Not used.
>
> > + st->labels[--i] = label;
>
> I've no idea how this works... Should be looking for the chan->channel value
> as that's what your read uses to index.
>
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int one_bit_adc_dac_parse_dt(struct iio_dev *indio_dev) {
> > + struct one_bit_adc_dac_state *st = iio_priv(indio_dev);
> > + struct iio_chan_spec *channels;
> > + int ret, in_num_ch = 0, out_num_ch = 0;
> > +
> > + st->in_gpio_descs = devm_gpiod_get_array_optional(&st->pdev-
> >dev, "in", GPIOD_IN);
> > + if (IS_ERR(st->in_gpio_descs))
> > + return PTR_ERR(st->in_gpio_descs);
> > +
> > + if (st->in_gpio_descs)
> > + in_num_ch = st->in_gpio_descs->ndescs;
> > +
> > + st->out_gpio_descs = devm_gpiod_get_array_optional(&st->pdev-
> >dev, "out", GPIOD_OUT_LOW);
> > + if (IS_ERR(st->out_gpio_descs))
> > + return PTR_ERR(st->out_gpio_descs);
> > +
> > + if (st->out_gpio_descs)
> > + out_num_ch = st->out_gpio_descs->ndescs;
> > + st->in_num_ch = in_num_ch;
> > + st->out_num_ch = out_num_ch;
> > +
> > + channels = devm_kcalloc(indio_dev->dev.parent, in_num_ch +
> out_num_ch,
> > + sizeof(*channels), GFP_KERNEL);
> > + if (!channels)
> > + return -ENOMEM;
> > +
> > + ret = one_bit_adc_dac_set_ch(&channels[0], in_num_ch, CH_IN);
> > + if (ret)
> > + return ret;
> > +
> > + ret = one_bit_adc_dac_set_ch(&channels[in_num_ch],
> out_num_ch, CH_OUT);
> > + if (ret)
> > + return ret;
> > +
> > + ret = one_bit_adc_dac_set_channel_label(indio_dev, channels,
> in_num_ch + out_num_ch);
> > + if (ret)
> > + return ret;
> > +
> > + indio_dev->channels = channels;
> > + indio_dev->num_channels = in_num_ch + out_num_ch;
> > +
> > + return 0;
> > +}
> > +
> > +static int one_bit_adc_dac_probe(struct platform_device *pdev) {
> > + struct iio_dev *indio_dev;
> > + struct one_bit_adc_dac_state *st;
> > + int ret;
> > +
> > + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + st = iio_priv(indio_dev);
> > + st->pdev = pdev;
> > + indio_dev->name = "one-bit-adc-dac";
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > + indio_dev->info = &one_bit_adc_dac_info;
> > +
> > + ret = one_bit_adc_dac_parse_dt(indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + return devm_iio_device_register(indio_dev->dev.parent,
> indio_dev); }
> > +
> > +static const struct of_device_id one_bit_adc_dac_dt_match[] = {
> > + { .compatible = "one-bit-adc-dac" },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(of, one_bit_adc_dac_dt_match);
> > +
> > +static struct platform_driver one_bit_adc_dac_driver = {
> > + .driver = {
> > + .name = "one-bit-adc-dac",
> > + .of_match_table = one_bit_adc_dac_dt_match,
> > + },
> > + .probe = one_bit_adc_dac_probe,
> > +};
> > +module_platform_driver(one_bit_adc_dac_driver);
> > +
> > +MODULE_AUTHOR("Cristian Pop <cristian.pop@xxxxxxxxxx>");
> > +MODULE_DESCRIPTION("One bit ADC DAC converter");
> MODULE_LICENSE("Dual
> > +BSD/GPL");
> > \ No newline at end of file
>
> Make sure to eyeball your patches before sending as this sort of thing should
> be caught at that stage...
>