| /* | |
| * IIO DAC emulation driver using a digital potentiometer | |
| * | |
| * Copyright (C) 2016 Axentia Technologies AB | |
| * | |
| * Author: Peter Rosin <peda@axentia.se> | |
| * | |
| * 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->vref) != -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@axentia.se>"); | |
| MODULE_LICENSE("GPL v2"); |