Some devices may require a current adc, but only have a voltage ADC onboard. In order to read the current, they have a resistor connected to the ADC. Suggested-by: Jonathan Cameron <jic23@xxxxxxxxxx> Signed-off-by: Jonathan Bakker <xc-racer2@xxxxxxx> --- MAINTAINERS | 8 ++ drivers/iio/adc/Kconfig | 9 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/current-from-voltage.c | 123 +++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 drivers/iio/adc/current-from-voltage.c diff --git a/MAINTAINERS b/MAINTAINERS index 2926327e4976..094cf512b403 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4503,6 +4503,14 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/allwinner,sun6i-a31-csi.yaml F: drivers/media/platform/sunxi/sun6i-csi/ +CURRENT ADC FROM VOLTAGE ADC DRIVER +M: Jonathan Bakker <xc-racer2@xxxxxxx> +L: linux-iio@xxxxxxxxxxxxxxx +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git +F: Documentation/devicetree/bindings/iio/adc/linux,current-from-voltage.yaml +F: drivers/iio/adc/current-from-voltage.c + CW1200 WLAN driver M: Solomon Peachy <pizza@xxxxxxxxxxxx> S: Maintained diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 12bb8b7ca1ff..84e6ccb36024 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -344,6 +344,15 @@ config CPCAP_ADC This driver can also be built as a module. If so, the module will be called cpcap-adc. +config CURRENT_FROM_VOLTAGE + tristate "Current from voltage shim driver" + help + Say yes here to build support for a shim driver converting a voltage + ADC coupled with a resistor to a current ADC. + + To compile this driver as a module, choose M here: the module will be + called current-from-voltage. + config DA9150_GPADC tristate "Dialog DA9150 GPADC driver support" depends on MFD_DA9150 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 637807861112..d293184fc32a 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_CPCAP_ADC) += cpcap-adc.o +obj-$(CONFIG_CURRENT_FROM_VOLTAGE) += current-from-voltage.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_DLN2_ADC) += dln2-adc.o obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o diff --git a/drivers/iio/adc/current-from-voltage.c b/drivers/iio/adc/current-from-voltage.c new file mode 100644 index 000000000000..69cb18e0995b --- /dev/null +++ b/drivers/iio/adc/current-from-voltage.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for converting a resistor and a voltage ADC to a current ADC + * + * Copyright (C) 2020 Jonathan Bakker <xc-racer2@xxxxxxx> + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/iio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +struct shim { + struct iio_channel *adc; + u32 resistor_value; +}; + +static int shim_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct shim *shim = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_read_channel_processed(shim->adc, val); + if (ret < 0) { + dev_err(&indio_dev->dev, "fail reading voltage ADC\n"); + return ret; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = shim->resistor_value; + + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static const struct iio_chan_spec shim_iio_channel = { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), +}; + +static const struct iio_info shim_info = { + .read_raw = &shim_read_raw, +}; + +static int shim_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct shim *shim; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*shim)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + shim = iio_priv(indio_dev); + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = dev->of_node; + indio_dev->info = &shim_info; + indio_dev->channels = &shim_iio_channel; + indio_dev->num_channels = 1; + + shim->adc = devm_iio_channel_get(dev, "adc"); + if (IS_ERR(shim->adc)) { + if (PTR_ERR(shim->adc) != -EPROBE_DEFER) + dev_err(dev, "failed to get adc input channel\n"); + return PTR_ERR(shim->adc); + } + + ret = iio_get_channel_type(shim->adc, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "ADC is of the wrong type\n"); + return -EINVAL; + } + + ret = device_property_read_u32(dev, "linux,resistor-ohms", + &shim->resistor_value); + if (ret < 0) { + dev_err(dev, "no resistor value found\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id shim_match[] = { + { .compatible = "linux,current-from-voltage", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, shim_match); + +static struct platform_driver shim_driver = { + .probe = shim_probe, + .driver = { + .name = "current-from-voltage", + .of_match_table = shim_match, + }, +}; +module_platform_driver(shim_driver); + +MODULE_DESCRIPTION("Current ADC from voltage ADC and resistor"); +MODULE_AUTHOR("Jonathan Bakker <xc-racer2@xxxxxxx>"); +MODULE_LICENSE("GPL v2"); -- 2.20.1