On Tue, 10 Apr 2018 17:28:02 +0200 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 is too big for the ADC to handle. > Likewise, if an ADC channel measures the voltage across a shunt > 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> So I 'think' the only outstanding question is Andrew's one about the driver name. We aren't in a hurry at this point in the kernel cycle, so lets wait until that discussion has ended. Assuming that we do possibly end up with a change, then please roll all the patches up into a single series to avoid me getting confusion. Thanks, Jonathan > --- > MAINTAINERS | 1 + > drivers/iio/Kconfig | 1 + > drivers/iio/Makefile | 1 + > drivers/iio/afe/Kconfig | 18 +++ > drivers/iio/afe/Makefile | 6 + > drivers/iio/afe/iio-unit-converter.c | 291 +++++++++++++++++++++++++++++++++++ > 6 files changed, 318 insertions(+) > create mode 100644 drivers/iio/afe/Kconfig > create mode 100644 drivers/iio/afe/Makefile > create mode 100644 drivers/iio/afe/iio-unit-converter.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 237fcdfdddc6..d2a28b915fca 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6893,6 +6893,7 @@ L: linux-iio@xxxxxxxxxxxxxxx > S: Maintained > F: Documentation/devicetree/bindings/iio/afe/current-sense-shunt.txt > F: Documentation/devicetree/bindings/iio/afe/voltage-divider.txt > +F: drivers/iio/afe/iio-unit-converter.c > > IKANOS/ADI EAGLE ADSL USB DRIVER > M: Matthieu Castet <castet.matthieu@xxxxxxx> > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig > index b3c8c6ef0dff..d69e85a8bdc3 100644 > --- a/drivers/iio/Kconfig > +++ b/drivers/iio/Kconfig > @@ -70,6 +70,7 @@ config IIO_TRIGGERED_EVENT > > source "drivers/iio/accel/Kconfig" > source "drivers/iio/adc/Kconfig" > +source "drivers/iio/afe/Kconfig" > source "drivers/iio/amplifiers/Kconfig" > source "drivers/iio/chemical/Kconfig" > source "drivers/iio/common/Kconfig" > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile > index b16b2e9ddc40..d8cba9c229c0 100644 > --- a/drivers/iio/Makefile > +++ b/drivers/iio/Makefile > @@ -15,6 +15,7 @@ obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o > > obj-y += accel/ > obj-y += adc/ > +obj-y += afe/ > obj-y += amplifiers/ > obj-y += buffer/ > obj-y += chemical/ > diff --git a/drivers/iio/afe/Kconfig b/drivers/iio/afe/Kconfig > new file mode 100644 > index 000000000000..642ce4eb12a6 > --- /dev/null > +++ b/drivers/iio/afe/Kconfig > @@ -0,0 +1,18 @@ > +# > +# Analog Front End drivers > +# > +# When adding new entries keep the list in alphabetical order > + > +menu "Analog Front Ends" > + > +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 > + that handles voltage dividers and current sense shunts. > + > + To compile this driver as a module, choose M here: the > + module will be called iio-unit-converter. > + > +endmenu > diff --git a/drivers/iio/afe/Makefile b/drivers/iio/afe/Makefile > new file mode 100644 > index 000000000000..7691cc5b809a > --- /dev/null > +++ b/drivers/iio/afe/Makefile > @@ -0,0 +1,6 @@ > +# > +# Makefile for industrial I/O Analog Front Ends (AFE) > +# > + > +# When adding new entries keep the list in alphabetical order > +obj-$(CONFIG_IIO_UNIT_CONVERTER) += iio-unit-converter.o > diff --git a/drivers/iio/afe/iio-unit-converter.c b/drivers/iio/afe/iio-unit-converter.c > new file mode 100644 > index 000000000000..fc50290d7e5e > --- /dev/null > +++ b/drivers/iio/afe/iio-unit-converter.c > @@ -0,0 +1,291 @@ > +// 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/gcd.h> > +#include <linux/iio/consumer.h> > +#include <linux/iio/iio.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/platform_device.h> > +#include <linux/property.h> > + > +struct unit_converter; > + > +struct unit_converter_cfg { > + enum iio_chan_type type; > + int (*props)(struct device *dev, struct unit_converter *uc); > +}; > + > +struct unit_converter { > + const struct unit_converter_cfg *cfg; > + struct iio_channel *source; > + 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->source, val); > + > + case IIO_CHAN_INFO_SCALE: > + ret = iio_read_channel_scale(uc->source, 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; > + default: > + return -EOPNOTSUPP; > + } > + 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->source, vals, length); > + default: > + return -EINVAL; > + } > +} > + > +static const struct iio_info unit_converter_info = { > + .read_raw = unit_converter_read_raw, > + .read_avail = unit_converter_read_avail, > +}; > + > +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->source, > + 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->source, > + uc->ext_info[private].name, > + buf, len); > +} > + > +static int unit_converter_configure_channel(struct device *dev, > + struct unit_converter *uc) > +{ > + struct iio_chan_spec *chan = &uc->chan; > + struct iio_chan_spec const *schan = uc->source->channel; > + > + chan->indexed = 1; > + chan->output = schan->output; > + chan->ext_info = uc->ext_info; > + chan->type = uc->cfg->type; > + > + if (!iio_channel_has_info(schan, IIO_CHAN_INFO_RAW) || > + !iio_channel_has_info(schan, IIO_CHAN_INFO_SCALE)) { > + dev_err(dev, "source channel does not support raw/scale\n"); > + return -EINVAL; > + } > + > + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_SCALE); > + > + if (iio_channel_has_available(schan, IIO_CHAN_INFO_RAW)) > + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW); > + > + return 0; > +} > + > +static int unit_converter_current_sense_shunt_props(struct device *dev, > + struct unit_converter *uc) > +{ > + u32 shunt; > + u32 factor; > + int ret; > + > + ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", > + &shunt); > + if (ret) { > + dev_err(dev, "failed to read the shunt resistance: %d\n", ret); > + return ret; > + } > + > + factor = gcd(shunt, 1000000); > + uc->numerator = 1000000 / factor; > + uc->denominator = shunt / factor; > + > + return 0; > +} > + > +static int unit_converter_voltage_divider_props(struct device *dev, > + struct unit_converter *uc) > +{ > + device_property_read_u32(dev, "numerator", &uc->numerator); > + device_property_read_u32(dev, "denominator", &uc->denominator); > + > + return 0; > +} > + > +enum unit_converter_variant { > + CURRENT_SENSE_SHUNT, > + VOLTAGE_DIVIDER, > +}; > + > +static const struct unit_converter_cfg unit_converter_cfg[] = { > + [CURRENT_SENSE_SHUNT] = { > + .type = IIO_CURRENT, > + .props = unit_converter_current_sense_shunt_props, > + }, > + [VOLTAGE_DIVIDER] = { > + .type = IIO_VOLTAGE, > + .props = unit_converter_voltage_divider_props, > + }, > +}; > + > +static const struct of_device_id unit_converter_match[] = { > + { .compatible = "current-sense-shunt", > + .data = &unit_converter_cfg[CURRENT_SENSE_SHUNT], }, > + { .compatible = "voltage-divider", > + .data = &unit_converter_cfg[VOLTAGE_DIVIDER], }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, unit_converter_match); > + > +static int unit_converter_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct iio_dev *indio_dev; > + struct iio_channel *source; > + struct unit_converter *uc; > + int sizeof_ext_info; > + int sizeof_priv; > + int i; > + int ret; > + > + source = devm_iio_channel_get(dev, NULL); > + if (IS_ERR(source)) { > + if (PTR_ERR(source) != -EPROBE_DEFER) > + dev_err(dev, "failed to get source channel\n"); > + return PTR_ERR(source); > + } > + > + sizeof_ext_info = iio_get_channel_ext_info_count(source); > + 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->cfg = of_device_get_match_data(dev); > + uc->numerator = 1; > + uc->denominator = 1; > + > + ret = uc->cfg->props(dev, uc); > + if (ret) > + return ret; > + > + if (!uc->numerator || !uc->denominator) { > + dev_err(dev, "invalid scaling factor.\n"); > + return -EINVAL; > + } > + > + platform_set_drvdata(pdev, indio_dev); > + > + uc->source = source; > + > + 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, > + source->channel->ext_info, > + sizeof_ext_info, GFP_KERNEL); > + if (!uc->ext_info) > + return -ENOMEM; > + > + for (i = 0; uc->ext_info[i].name; ++i) { > + if (source->channel->ext_info[i].read) > + uc->ext_info[i].read = unit_converter_read_ext_info; > + if (source->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); > + if (ret) > + return ret; > + > + return devm_iio_device_register(dev, indio_dev); > +} > + > +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 devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html