| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * IIO DAC emulation driver using a digital potentiometer |
| * |
| * Copyright (C) 2016 Axentia Technologies AB |
| * |
| * Author: Peter Rosin <peda@axentia.se> |
| */ |
| |
| /* |
| * 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/mod_devicetable.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. Say it again for GCC. */ |
| fallthrough; |
| 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, |
| }; |
| |
| 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->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)) |
| return dev_err_probe(&pdev->dev, PTR_ERR(dac->vref), |
| "failed to get vref regulator\n"); |
| |
| dac->dpot = devm_iio_channel_get(dev, "dpot"); |
| if (IS_ERR(dac->dpot)) |
| return dev_err_probe(&pdev->dev, PTR_ERR(dac->dpot), |
| "failed to get dpot input channel\n"); |
| |
| 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 void 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); |
| } |
| |
| 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_new = 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"); |