mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
iio: dpot-dac: DAC driver based on a digital potentiometer
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@axentia.se> Signed-off-by: Jonathan Cameron <jic23@kernel.org>
This commit is contained in:
parent
ed13134ba8
commit
7fde1484af
8
Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac
Normal file
8
Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac
Normal file
@ -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@axentia.se>
|
||||
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]
|
@ -6123,7 +6123,9 @@ IIO DIGITAL POTENTIOMETER DAC
|
||||
M: Peter Rosin <peda@axentia.se>
|
||||
L: linux-iio@vger.kernel.org
|
||||
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@kernel.org>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
266
drivers/iio/dac/dpot-dac.c
Normal file
266
drivers/iio/dac/dpot-dac.c
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* 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");
|
Loading…
Reference in New Issue
Block a user