257 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * IIO DAC emulation driver using a digital potentiometer4 *5 * Copyright (C) 2016 Axentia Technologies AB6 *7 * Author: Peter Rosin <peda@axentia.se>8 */9 10/*11 * It is assumed that the dpot is used as a voltage divider between the12 * current dpot wiper setting and the maximum resistance of the dpot. The13 * divided voltage is provided by a vref regulator.14 *15 * .------.16 * .-----------. | |17 * | vref |--' .---.18 * | regulator |--. | |19 * '-----------' | | d |20 * | | p |21 * | | o | wiper22 * | | t |<---------+23 * | | |24 * | '---' dac output voltage25 * | |26 * '------+------------+27 */28 29#include <linux/err.h>30#include <linux/iio/consumer.h>31#include <linux/iio/iio.h>32#include <linux/module.h>33#include <linux/mod_devicetable.h>34#include <linux/platform_device.h>35#include <linux/regulator/consumer.h>36 37struct dpot_dac {38 struct regulator *vref;39 struct iio_channel *dpot;40 u32 max_ohms;41};42 43static const struct iio_chan_spec dpot_dac_iio_channel = {44 .type = IIO_VOLTAGE,45 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)46 | BIT(IIO_CHAN_INFO_SCALE),47 .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),48 .output = 1,49 .indexed = 1,50};51 52static int dpot_dac_read_raw(struct iio_dev *indio_dev,53 struct iio_chan_spec const *chan,54 int *val, int *val2, long mask)55{56 struct dpot_dac *dac = iio_priv(indio_dev);57 int ret;58 unsigned long long tmp;59 60 switch (mask) {61 case IIO_CHAN_INFO_RAW:62 return iio_read_channel_raw(dac->dpot, val);63 64 case IIO_CHAN_INFO_SCALE:65 ret = iio_read_channel_scale(dac->dpot, val, val2);66 switch (ret) {67 case IIO_VAL_FRACTIONAL_LOG2:68 tmp = *val * 1000000000LL;69 do_div(tmp, dac->max_ohms);70 tmp *= regulator_get_voltage(dac->vref) / 1000;71 do_div(tmp, 1000000000LL);72 *val = tmp;73 return ret;74 case IIO_VAL_INT:75 /*76 * Convert integer scale to fractional scale by77 * setting the denominator (val2) to one...78 */79 *val2 = 1;80 ret = IIO_VAL_FRACTIONAL;81 /* ...and fall through. Say it again for GCC. */82 fallthrough;83 case IIO_VAL_FRACTIONAL:84 *val *= regulator_get_voltage(dac->vref) / 1000;85 *val2 *= dac->max_ohms;86 break;87 }88 89 return ret;90 }91 92 return -EINVAL;93}94 95static int dpot_dac_read_avail(struct iio_dev *indio_dev,96 struct iio_chan_spec const *chan,97 const int **vals, int *type, int *length,98 long mask)99{100 struct dpot_dac *dac = iio_priv(indio_dev);101 102 switch (mask) {103 case IIO_CHAN_INFO_RAW:104 *type = IIO_VAL_INT;105 return iio_read_avail_channel_raw(dac->dpot, vals, length);106 }107 108 return -EINVAL;109}110 111static int dpot_dac_write_raw(struct iio_dev *indio_dev,112 struct iio_chan_spec const *chan,113 int val, int val2, long mask)114{115 struct dpot_dac *dac = iio_priv(indio_dev);116 117 switch (mask) {118 case IIO_CHAN_INFO_RAW:119 return iio_write_channel_raw(dac->dpot, val);120 }121 122 return -EINVAL;123}124 125static const struct iio_info dpot_dac_info = {126 .read_raw = dpot_dac_read_raw,127 .read_avail = dpot_dac_read_avail,128 .write_raw = dpot_dac_write_raw,129};130 131static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev)132{133 struct device *dev = &indio_dev->dev;134 struct dpot_dac *dac = iio_priv(indio_dev);135 unsigned long long tmp;136 int ret;137 int val;138 int val2;139 int max;140 141 ret = iio_read_max_channel_raw(dac->dpot, &max);142 if (ret < 0) {143 dev_err(dev, "dpot does not indicate its raw maximum value\n");144 return ret;145 }146 147 switch (iio_read_channel_scale(dac->dpot, &val, &val2)) {148 case IIO_VAL_INT:149 return max * val;150 case IIO_VAL_FRACTIONAL:151 tmp = (unsigned long long)max * val;152 do_div(tmp, val2);153 return tmp;154 case IIO_VAL_FRACTIONAL_LOG2:155 tmp = val * 1000000000LL * max >> val2;156 do_div(tmp, 1000000000LL);157 return tmp;158 default:159 dev_err(dev, "dpot has a scale that is too weird\n");160 }161 162 return -EINVAL;163}164 165static int dpot_dac_probe(struct platform_device *pdev)166{167 struct device *dev = &pdev->dev;168 struct iio_dev *indio_dev;169 struct dpot_dac *dac;170 enum iio_chan_type type;171 int ret;172 173 indio_dev = devm_iio_device_alloc(dev, sizeof(*dac));174 if (!indio_dev)175 return -ENOMEM;176 177 platform_set_drvdata(pdev, indio_dev);178 dac = iio_priv(indio_dev);179 180 indio_dev->name = dev_name(dev);181 indio_dev->info = &dpot_dac_info;182 indio_dev->modes = INDIO_DIRECT_MODE;183 indio_dev->channels = &dpot_dac_iio_channel;184 indio_dev->num_channels = 1;185 186 dac->vref = devm_regulator_get(dev, "vref");187 if (IS_ERR(dac->vref))188 return dev_err_probe(&pdev->dev, PTR_ERR(dac->vref),189 "failed to get vref regulator\n");190 191 dac->dpot = devm_iio_channel_get(dev, "dpot");192 if (IS_ERR(dac->dpot))193 return dev_err_probe(&pdev->dev, PTR_ERR(dac->dpot),194 "failed to get dpot input channel\n");195 196 ret = iio_get_channel_type(dac->dpot, &type);197 if (ret < 0)198 return ret;199 200 if (type != IIO_RESISTANCE) {201 dev_err(dev, "dpot is of the wrong type\n");202 return -EINVAL;203 }204 205 ret = dpot_dac_channel_max_ohms(indio_dev);206 if (ret < 0)207 return ret;208 dac->max_ohms = ret;209 210 ret = regulator_enable(dac->vref);211 if (ret) {212 dev_err(dev, "failed to enable the vref regulator\n");213 return ret;214 }215 216 ret = iio_device_register(indio_dev);217 if (ret) {218 dev_err(dev, "failed to register iio device\n");219 goto disable_reg;220 }221 222 return 0;223 224disable_reg:225 regulator_disable(dac->vref);226 return ret;227}228 229static void dpot_dac_remove(struct platform_device *pdev)230{231 struct iio_dev *indio_dev = platform_get_drvdata(pdev);232 struct dpot_dac *dac = iio_priv(indio_dev);233 234 iio_device_unregister(indio_dev);235 regulator_disable(dac->vref);236}237 238static const struct of_device_id dpot_dac_match[] = {239 { .compatible = "dpot-dac" },240 { /* sentinel */ }241};242MODULE_DEVICE_TABLE(of, dpot_dac_match);243 244static struct platform_driver dpot_dac_driver = {245 .probe = dpot_dac_probe,246 .remove_new = dpot_dac_remove,247 .driver = {248 .name = "iio-dpot-dac",249 .of_match_table = dpot_dac_match,250 },251};252module_platform_driver(dpot_dac_driver);253 254MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer");255MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");256MODULE_LICENSE("GPL v2");257