On Mon, 19 Mar 2018 18:02:46 +0100 Peter Rosin <peda@xxxxxxxxxx> wrote: > If an ADC channel measures the midpoint of a voltage divider, the > interesting voltage is often the voltage over the full resistance. > E.g. if the full voltage it too big for the ADC to handle. > Likewise, if an ADC channel measures the voltage across a resistor, > the interesting value is often the current through the resistor. > > This driver solves both problems by allowing to linearly scale a channel > and by allowing changes to the type of the channel. Or both. > > Signed-off-by: Peter Rosin <peda@xxxxxxxxxx> A few comments inline, but basically the code looks fine, just questions around naming and bindings to answer. Thanks, Jonathan > --- > MAINTAINERS | 1 + > drivers/iio/wrapper/Kconfig | 9 ++ > drivers/iio/wrapper/Makefile | 1 + > drivers/iio/wrapper/iio-unit-converter.c | 268 +++++++++++++++++++++++++++++++ > 4 files changed, 279 insertions(+) > create mode 100644 drivers/iio/wrapper/iio-unit-converter.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 5dd555c7b1b0..b879289f1318 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6889,6 +6889,7 @@ M: Peter Rosin <peda@xxxxxxxxxx> > L: linux-iio@xxxxxxxxxxxxxxx > S: Maintained > F: Documentation/devicetree/bindings/iio/wrapper/io-channel-unit-converter.txt > +F: drivers/iio/wrapper/iio-unit-converter.c > > IKANOS/ADI EAGLE ADSL USB DRIVER > M: Matthieu Castet <castet.matthieu@xxxxxxx> > diff --git a/drivers/iio/wrapper/Kconfig b/drivers/iio/wrapper/Kconfig > index f27de347c9b3..16554479264a 100644 > --- a/drivers/iio/wrapper/Kconfig > +++ b/drivers/iio/wrapper/Kconfig > @@ -15,4 +15,13 @@ config IIO_MUX > To compile this driver as a module, choose M here: the > module will be called iio-mux. > > +config IIO_UNIT_CONVERTER > + tristate "IIO unit converter" > + depends on OF || COMPILE_TEST > + help > + Say yes here to build support for the IIO unit converter. > + > + To compile this driver as a module, choose M here: the > + module will be called iio-unit-converter. > + > endmenu > diff --git a/drivers/iio/wrapper/Makefile b/drivers/iio/wrapper/Makefile > index 53a7b78734e3..1b9db53bd420 100644 > --- a/drivers/iio/wrapper/Makefile > +++ b/drivers/iio/wrapper/Makefile > @@ -4,3 +4,4 @@ > > # When adding new entries keep the list in alphabetical order > obj-$(CONFIG_IIO_MUX) += iio-mux.o > +obj-$(CONFIG_IIO_UNIT_CONVERTER) += iio-unit-converter.o > diff --git a/drivers/iio/wrapper/iio-unit-converter.c b/drivers/iio/wrapper/iio-unit-converter.c > new file mode 100644 > index 000000000000..53c126f39e4e > --- /dev/null > +++ b/drivers/iio/wrapper/iio-unit-converter.c > @@ -0,0 +1,268 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * IIO unit converter > + * > + * Copyright (C) 2018 Axentia Technologies AB > + * > + * Author: Peter Rosin <peda@xxxxxxxxxx> > + */ > + > +#include <linux/err.h> > +#include <linux/iio/consumer.h> > +#include <linux/iio/iio.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/property.h> > + > +struct unit_converter { > + struct iio_channel *parent; > + struct iio_dev *indio_dev; > + struct iio_chan_spec chan; > + struct iio_chan_spec_ext_info *ext_info; > + s32 numerator; > + s32 denominator; > +}; > + > +static int unit_converter_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct unit_converter *uc = iio_priv(indio_dev); > + unsigned long long tmp; > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + return iio_read_channel_raw(uc->parent, val); > + > + case IIO_CHAN_INFO_SCALE: > + ret = iio_read_channel_scale(uc->parent, val, val2); > + switch (ret) { > + case IIO_VAL_FRACTIONAL: > + *val *= uc->numerator; > + *val2 *= uc->denominator; > + return ret; > + case IIO_VAL_INT: > + *val *= uc->numerator; > + if (uc->denominator == 1) > + return ret; > + *val2 = uc->denominator; > + return IIO_VAL_FRACTIONAL; > + case IIO_VAL_FRACTIONAL_LOG2: > + tmp = *val * 1000000000LL; > + do_div(tmp, uc->denominator); > + tmp *= uc->numerator; > + do_div(tmp, 1000000000LL); > + *val = tmp; > + return ret; > + } > + return -EOPNOTSUPP; Slightly clearer and less likely to give warningss from static checkers if you put that return in a default: > + } > + > + return -EINVAL; > +} > + > +static int unit_converter_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + const int **vals, int *type, int *length, > + long mask) > +{ > + struct unit_converter *uc = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + *type = IIO_VAL_INT; > + return iio_read_avail_channel_raw(uc->parent, vals, length); > + } > + > + return -EINVAL; > +} > + > +static int unit_converter_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct unit_converter *uc = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + return iio_write_channel_raw(uc->parent, val); Talk me through the logic of having this... Supporting a DAC? Bindings don't talk about that possibility... > + } > + > + return -EINVAL; > +} > + > +static const struct iio_info unit_converter_info = { > + .read_raw = unit_converter_read_raw, > + .read_avail = unit_converter_read_avail, > + .write_raw = unit_converter_write_raw, > +}; > + > +static ssize_t unit_converter_read_ext_info(struct iio_dev *indio_dev, > + uintptr_t private, > + struct iio_chan_spec const *chan, > + char *buf) > +{ > + struct unit_converter *uc = iio_priv(indio_dev); > + > + return iio_read_channel_ext_info(uc->parent, > + uc->ext_info[private].name, > + buf); > +} > + > +static ssize_t unit_converter_write_ext_info(struct iio_dev *indio_dev, > + uintptr_t private, > + struct iio_chan_spec const *chan, > + const char *buf, size_t len) > +{ > + struct unit_converter *uc = iio_priv(indio_dev); > + > + return iio_write_channel_ext_info(uc->parent, > + uc->ext_info[private].name, > + buf, len); > +} > + > +static int unit_converter_configure_channel(struct device *dev, > + struct unit_converter *uc, > + enum iio_chan_type type) > +{ > + struct iio_chan_spec *chan = &uc->chan; > + struct iio_chan_spec const *pchan = uc->parent->channel; > + int ret; > + > + chan->indexed = 1; > + chan->output = pchan->output; > + chan->ext_info = uc->ext_info; > + > + if (type == -1) { > + ret = iio_get_channel_type(uc->parent, &type); > + if (ret < 0) { > + dev_err(dev, "failed to get parent channel type\n"); > + return ret; > + } > + } > + chan->type = type; > + > + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW)) > + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW); if the parent doesn't support RAW, is there a lot of point in carrying on? > + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE)) > + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE); > + > + if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW)) > + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW); > + > + chan->channel = 0; > + > + return 0; > +} > + > +static int unit_converter_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct iio_dev *indio_dev; > + struct iio_channel *parent; > + struct unit_converter *uc; > + const char *type_name; > + enum iio_chan_type type = -1; /* default to same as parent */ > + int sizeof_ext_info; > + int sizeof_priv; > + int i; > + int ret; > + > + if (!device_property_read_string(dev, "type", &type_name)) { > + if (!strcmp(type_name, "voltage")) > + type = IIO_VOLTAGE; > + else if (!strcmp(type_name, "current")) > + type = IIO_CURRENT; > + else > + return -EINVAL; > + } > + > + parent = devm_iio_channel_get(dev, "parent"); > + if (IS_ERR(parent)) { > + if (PTR_ERR(parent) != -EPROBE_DEFER) > + dev_err(dev, "failed to get parent channel\n"); > + return PTR_ERR(parent); > + } > + > + sizeof_ext_info = iio_get_channel_ext_info_count(parent); > + if (sizeof_ext_info) { > + sizeof_ext_info += 1; /* one extra entry for the sentinel */ > + sizeof_ext_info *= sizeof(*uc->ext_info); > + } > + > + sizeof_priv = sizeof(*uc) + sizeof_ext_info; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof_priv); > + if (!indio_dev) > + return -ENOMEM; > + > + uc = iio_priv(indio_dev); > + > + uc->numerator = 1; > + uc->denominator = 1; > + device_property_read_u32(dev, "numerator", &uc->numerator); > + device_property_read_u32(dev, "denominator", &uc->denominator); > + if (!uc->numerator || !uc->denominator) { > + dev_err(dev, "invalid scaling factor.\n"); > + return -EINVAL; > + } > + > + platform_set_drvdata(pdev, indio_dev); > + > + uc->parent = parent; > + > + indio_dev->name = dev_name(dev); > + indio_dev->dev.parent = dev; > + indio_dev->info = &unit_converter_info; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = &uc->chan; > + indio_dev->num_channels = 1; > + if (sizeof_ext_info) { > + uc->ext_info = devm_kmemdup(dev, > + parent->channel->ext_info, > + sizeof_ext_info, GFP_KERNEL); > + if (!uc->ext_info) > + return -ENOMEM; > + > + for (i = 0; uc->ext_info[i].name; ++i) { > + if (parent->channel->ext_info[i].read) > + uc->ext_info[i].read = unit_converter_read_ext_info; > + if (parent->channel->ext_info[i].write) > + uc->ext_info[i].write = unit_converter_write_ext_info; > + uc->ext_info[i].private = i; > + } > + } > + > + ret = unit_converter_configure_channel(dev, uc, type); > + if (ret < 0) > + return ret; > + > + ret = devm_iio_device_register(dev, indio_dev); > + if (ret) { > + dev_err(dev, "failed to register iio device\n"); > + return ret; > + } Drop the return out of the brackets and get rid of return 0 below. > + > + return 0; > +} > + > +static const struct of_device_id unit_converter_match[] = { > + { .compatible = "io-channel-unit-converter" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, unit_converter_match); > + > +static struct platform_driver unit_converter_driver = { > + .probe = unit_converter_probe, > + .driver = { > + .name = "iio-unit-converter", > + .of_match_table = unit_converter_match, > + }, > +}; > +module_platform_driver(unit_converter_driver); > + > +MODULE_DESCRIPTION("IIO unit converter driver"); > +MODULE_AUTHOR("Peter Rosin <peda@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html