On 08/11/16 11:58, Peter Rosin wrote: > It is assumed that the dpot is used as a voltage divider between the > current dpot wiper setting and the maximum resistance of the dpot. The > divided voltage is provided by a vref regulator. > > .------. > .-----------. | | > | vref |--' .---. > | regulator |--. | | > '-----------' | | d | > | | p | > | | o | wiper > | | t |<---------+ > | | | > | '---' dac output voltage > | | > '------+------------+ > > Signed-off-by: Peter Rosin <peda@xxxxxxxxxx> Applied to the togreg branch of iio.git - shortly to be pushed out as testing for the autobuilders to play with it. Thanks, Jonathan > --- > .../ABI/testing/sysfs-bus-iio-dac-dpot-dac | 8 + > MAINTAINERS | 2 + > drivers/iio/dac/Kconfig | 10 + > drivers/iio/dac/Makefile | 1 + > drivers/iio/dac/dpot-dac.c | 266 +++++++++++++++++++++ > 5 files changed, 287 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac > create mode 100644 drivers/iio/dac/dpot-dac.c > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac b/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac > new file mode 100644 > index 000000000000..580e93f373f6 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac > @@ -0,0 +1,8 @@ > +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_raw_available > +Date: October 2016 > +KernelVersion: 4.9 > +Contact: Peter Rosin <peda@xxxxxxxxxx> > +Description: > + The range of available values represented as the minimum value, > + the step and the maximum value, all enclosed in square brackets. > + Example: [0 1 256] > diff --git a/MAINTAINERS b/MAINTAINERS > index 4ff9c12b1313..583c6c93b6f3 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6128,7 +6128,9 @@ IIO DIGITAL POTENTIOMETER DAC > M: Peter Rosin <peda@xxxxxxxxxx> > L: linux-iio@xxxxxxxxxxxxxxx > S: Maintained > +F: Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac > F: Documentation/devicetree/bindings/iio/dac/dpot-dac.txt > +F: drivers/iio/dac/dpot-dac.c > > IIO SUBSYSTEM AND DRIVERS > M: Jonathan Cameron <jic23@xxxxxxxxxx> > diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig > index 120b24478469..d3084028905b 100644 > --- a/drivers/iio/dac/Kconfig > +++ b/drivers/iio/dac/Kconfig > @@ -200,6 +200,16 @@ config AD8801 > To compile this driver as a module choose M here: the module will be called > ad8801. > > +config DPOT_DAC > + tristate "DAC emulation using a DPOT" > + depends on OF > + help > + Say yes here to build support for DAC emulation using a digital > + potentiometer. > + > + To compile this driver as a module, choose M here: the module will be > + called dpot-dac. > + > config LPC18XX_DAC > tristate "NXP LPC18xx DAC driver" > depends on ARCH_LPC18XX || COMPILE_TEST > diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile > index 27642bbf75f2..f01bf4a99867 100644 > --- a/drivers/iio/dac/Makefile > +++ b/drivers/iio/dac/Makefile > @@ -22,6 +22,7 @@ obj-$(CONFIG_AD5686) += ad5686.o > obj-$(CONFIG_AD7303) += ad7303.o > obj-$(CONFIG_AD8801) += ad8801.o > obj-$(CONFIG_CIO_DAC) += cio-dac.o > +obj-$(CONFIG_DPOT_DAC) += dpot-dac.o > obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o > obj-$(CONFIG_M62332) += m62332.o > obj-$(CONFIG_MAX517) += max517.o > diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c > new file mode 100644 > index 000000000000..eb1f0509a3ec > --- /dev/null > +++ b/drivers/iio/dac/dpot-dac.c > @@ -0,0 +1,266 @@ > +/* > + * IIO DAC emulation driver using a digital potentiometer > + * > + * Copyright (C) 2016 Axentia Technologies AB > + * > + * Author: Peter Rosin <peda@xxxxxxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +/* > + * It is assumed that the dpot is used as a voltage divider between the > + * current dpot wiper setting and the maximum resistance of the dpot. The > + * divided voltage is provided by a vref regulator. > + * > + * .------. > + * .-----------. | | > + * | vref |--' .---. > + * | regulator |--. | | > + * '-----------' | | d | > + * | | p | > + * | | o | wiper > + * | | t |<---------+ > + * | | | > + * | '---' dac output voltage > + * | | > + * '------+------------+ > + */ > + > +#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/regulator/consumer.h> > + > +struct dpot_dac { > + struct regulator *vref; > + struct iio_channel *dpot; > + u32 max_ohms; > +}; > + > +static const struct iio_chan_spec dpot_dac_iio_channel = { > + .type = IIO_VOLTAGE, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) > + | BIT(IIO_CHAN_INFO_SCALE), > + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), > + .output = 1, > + .indexed = 1, > +}; > + > +static int dpot_dac_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct dpot_dac *dac = iio_priv(indio_dev); > + int ret; > + unsigned long long tmp; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + return iio_read_channel_raw(dac->dpot, val); > + > + case IIO_CHAN_INFO_SCALE: > + ret = iio_read_channel_scale(dac->dpot, val, val2); > + switch (ret) { > + case IIO_VAL_FRACTIONAL_LOG2: > + tmp = *val * 1000000000LL; > + do_div(tmp, dac->max_ohms); > + tmp *= regulator_get_voltage(dac->vref) / 1000; > + do_div(tmp, 1000000000LL); > + *val = tmp; > + return ret; > + case IIO_VAL_INT: > + /* > + * Convert integer scale to fractional scale by > + * setting the denominator (val2) to one... > + */ > + *val2 = 1; > + ret = IIO_VAL_FRACTIONAL; > + /* ...and fall through. */ > + case IIO_VAL_FRACTIONAL: > + *val *= regulator_get_voltage(dac->vref) / 1000; > + *val2 *= dac->max_ohms; > + break; > + } > + > + return ret; > + } > + > + return -EINVAL; > +} > + > +static int dpot_dac_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + const int **vals, int *type, int *length, > + long mask) > +{ > + struct dpot_dac *dac = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + *type = IIO_VAL_INT; > + return iio_read_avail_channel_raw(dac->dpot, vals, length); > + } > + > + return -EINVAL; > +} > + > +static int dpot_dac_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct dpot_dac *dac = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + return iio_write_channel_raw(dac->dpot, val); > + } > + > + return -EINVAL; > +} > + > +static const struct iio_info dpot_dac_info = { > + .read_raw = dpot_dac_read_raw, > + .read_avail = dpot_dac_read_avail, > + .write_raw = dpot_dac_write_raw, > + .driver_module = THIS_MODULE, > +}; > + > +static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev) > +{ > + struct device *dev = &indio_dev->dev; > + struct dpot_dac *dac = iio_priv(indio_dev); > + unsigned long long tmp; > + int ret; > + int val; > + int val2; > + int max; > + > + ret = iio_read_max_channel_raw(dac->dpot, &max); > + if (ret < 0) { > + dev_err(dev, "dpot does not indicate its raw maximum value\n"); > + return ret; > + } > + > + switch (iio_read_channel_scale(dac->dpot, &val, &val2)) { > + case IIO_VAL_INT: > + return max * val; > + case IIO_VAL_FRACTIONAL: > + tmp = (unsigned long long)max * val; > + do_div(tmp, val2); > + return tmp; > + case IIO_VAL_FRACTIONAL_LOG2: > + tmp = val * 1000000000LL * max >> val2; > + do_div(tmp, 1000000000LL); > + return tmp; > + default: > + dev_err(dev, "dpot has a scale that is too weird\n"); > + } > + > + return -EINVAL; > +} > + > +static int dpot_dac_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct iio_dev *indio_dev; > + struct dpot_dac *dac; > + enum iio_chan_type type; > + int ret; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof(*dac)); > + if (!indio_dev) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, indio_dev); > + dac = iio_priv(indio_dev); > + > + indio_dev->name = dev_name(dev); > + indio_dev->dev.parent = dev; > + indio_dev->info = &dpot_dac_info; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = &dpot_dac_iio_channel; > + indio_dev->num_channels = 1; > + > + dac->vref = devm_regulator_get(dev, "vref"); > + if (IS_ERR(dac->vref)) { > + if (PTR_ERR(dac->dpot) != -EPROBE_DEFER) > + dev_err(&pdev->dev, "failed to get vref regulator\n"); > + return PTR_ERR(dac->vref); > + } > + > + dac->dpot = devm_iio_channel_get(dev, "dpot"); > + if (IS_ERR(dac->dpot)) { > + if (PTR_ERR(dac->dpot) != -EPROBE_DEFER) > + dev_err(dev, "failed to get dpot input channel\n"); > + return PTR_ERR(dac->dpot); > + } > + > + ret = iio_get_channel_type(dac->dpot, &type); > + if (ret < 0) > + return ret; > + > + if (type != IIO_RESISTANCE) { > + dev_err(dev, "dpot is of the wrong type\n"); > + return -EINVAL; > + } > + > + ret = dpot_dac_channel_max_ohms(indio_dev); > + if (ret < 0) > + return ret; > + dac->max_ohms = ret; > + > + ret = regulator_enable(dac->vref); > + if (ret) { > + dev_err(dev, "failed to enable the vref regulator\n"); > + return ret; > + } > + > + ret = iio_device_register(indio_dev); > + if (ret) { > + dev_err(dev, "failed to register iio device\n"); > + goto disable_reg; > + } > + > + return 0; > + > +disable_reg: > + regulator_disable(dac->vref); > + return ret; > +} > + > +static int dpot_dac_remove(struct platform_device *pdev) > +{ > + struct iio_dev *indio_dev = platform_get_drvdata(pdev); > + struct dpot_dac *dac = iio_priv(indio_dev); > + > + iio_device_unregister(indio_dev); > + regulator_disable(dac->vref); > + > + return 0; > +} > + > +static const struct of_device_id dpot_dac_match[] = { > + { .compatible = "dpot-dac" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, dpot_dac_match); > + > +static struct platform_driver dpot_dac_driver = { > + .probe = dpot_dac_probe, > + .remove = dpot_dac_remove, > + .driver = { > + .name = "iio-dpot-dac", > + .of_match_table = dpot_dac_match, > + }, > +}; > +module_platform_driver(dpot_dac_driver); > + > +MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer"); > +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