staging: iio: adc: ad7606: Move out of staging
Move ad7606 ADC driver out of staging and into the mainline. Signed-off-by: Stefan Popa <stefan.popa@analog.com> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:

committed by
Jonathan Cameron

parent
54160ae3b2
commit
2985a5d884
583
drivers/iio/adc/ad7606.c
Normal file
583
drivers/iio/adc/ad7606.c
Normal file
@@ -0,0 +1,583 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AD7606 SPI ADC driver
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
|
||||
#include "ad7606.h"
|
||||
|
||||
/*
|
||||
* Scales are computed as 5000/32768 and 10000/32768 respectively,
|
||||
* so that when applied to the raw values they provide mV values
|
||||
*/
|
||||
static const unsigned int scale_avail[2] = {
|
||||
152588, 305176
|
||||
};
|
||||
|
||||
static const unsigned int ad7606_oversampling_avail[7] = {
|
||||
1, 2, 4, 8, 16, 32, 64,
|
||||
};
|
||||
|
||||
static int ad7606_reset(struct ad7606_state *st)
|
||||
{
|
||||
if (st->gpio_reset) {
|
||||
gpiod_set_value(st->gpio_reset, 1);
|
||||
ndelay(100); /* t_reset >= 100ns */
|
||||
gpiod_set_value(st->gpio_reset, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int ad7606_read_samples(struct ad7606_state *st)
|
||||
{
|
||||
unsigned int num = st->chip_info->num_channels;
|
||||
u16 *data = st->data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The frstdata signal is set to high while and after reading the sample
|
||||
* of the first channel and low for all other channels. This can be used
|
||||
* to check that the incoming data is correctly aligned. During normal
|
||||
* operation the data should never become unaligned, but some glitch or
|
||||
* electrostatic discharge might cause an extra read or clock cycle.
|
||||
* Monitoring the frstdata signal allows to recover from such failure
|
||||
* situations.
|
||||
*/
|
||||
|
||||
if (st->gpio_frstdata) {
|
||||
ret = st->bops->read_block(st->dev, 1, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!gpiod_get_value(st->gpio_frstdata)) {
|
||||
ad7606_reset(st);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
data++;
|
||||
num--;
|
||||
}
|
||||
|
||||
return st->bops->read_block(st->dev, num, data);
|
||||
}
|
||||
|
||||
static irqreturn_t ad7606_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
|
||||
ret = ad7606_read_samples(st);
|
||||
if (ret == 0)
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, st->data,
|
||||
iio_get_time_ns(indio_dev));
|
||||
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
/* The rising edge of the CONVST signal starts a new conversion. */
|
||||
gpiod_set_value(st->gpio_convst, 1);
|
||||
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned int ch)
|
||||
{
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
gpiod_set_value(st->gpio_convst, 1);
|
||||
ret = wait_for_completion_timeout(&st->completion,
|
||||
msecs_to_jiffies(1000));
|
||||
if (!ret) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto error_ret;
|
||||
}
|
||||
|
||||
ret = ad7606_read_samples(st);
|
||||
if (ret == 0)
|
||||
ret = st->data[ch];
|
||||
|
||||
error_ret:
|
||||
gpiod_set_value(st->gpio_convst, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7606_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val,
|
||||
int *val2,
|
||||
long m)
|
||||
{
|
||||
int ret;
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
switch (m) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ad7606_scan_direct(indio_dev, chan->address);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = (short)ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = scale_avail[st->range];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
*val = st->oversampling;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t in_voltage_scale_available_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int i, len = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(scale_avail); i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
|
||||
scale_avail[i]);
|
||||
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static IIO_DEVICE_ATTR_RO(in_voltage_scale_available, 0);
|
||||
|
||||
static int ad7606_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val,
|
||||
int val2,
|
||||
long mask)
|
||||
{
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
DECLARE_BITMAP(values, 3);
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
mutex_lock(&st->lock);
|
||||
i = find_closest(val2, scale_avail, ARRAY_SIZE(scale_avail));
|
||||
gpiod_set_value(st->gpio_range, i);
|
||||
st->range = i;
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return 0;
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
if (val2)
|
||||
return -EINVAL;
|
||||
i = find_closest(val, ad7606_oversampling_avail,
|
||||
ARRAY_SIZE(ad7606_oversampling_avail));
|
||||
|
||||
values[0] = i;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
gpiod_set_array_value(ARRAY_SIZE(values), st->gpio_os->desc,
|
||||
st->gpio_os->info, values);
|
||||
st->oversampling = ad7606_oversampling_avail[i];
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(oversampling_ratio_available, "1 2 4 8 16 32 64");
|
||||
|
||||
static struct attribute *ad7606_attributes_os_and_range[] = {
|
||||
&iio_dev_attr_in_voltage_scale_available.dev_attr.attr,
|
||||
&iio_const_attr_oversampling_ratio_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7606_attribute_group_os_and_range = {
|
||||
.attrs = ad7606_attributes_os_and_range,
|
||||
};
|
||||
|
||||
static struct attribute *ad7606_attributes_os[] = {
|
||||
&iio_const_attr_oversampling_ratio_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7606_attribute_group_os = {
|
||||
.attrs = ad7606_attributes_os,
|
||||
};
|
||||
|
||||
static struct attribute *ad7606_attributes_range[] = {
|
||||
&iio_dev_attr_in_voltage_scale_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7606_attribute_group_range = {
|
||||
.attrs = ad7606_attributes_range,
|
||||
};
|
||||
|
||||
#define AD760X_CHANNEL(num, mask) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = num, \
|
||||
.address = num, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
|
||||
.info_mask_shared_by_all = mask, \
|
||||
.scan_index = num, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define AD7605_CHANNEL(num) \
|
||||
AD760X_CHANNEL(num, 0)
|
||||
|
||||
#define AD7606_CHANNEL(num) \
|
||||
AD760X_CHANNEL(num, BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO))
|
||||
|
||||
static const struct iio_chan_spec ad7605_channels[] = {
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
AD7605_CHANNEL(0),
|
||||
AD7605_CHANNEL(1),
|
||||
AD7605_CHANNEL(2),
|
||||
AD7605_CHANNEL(3),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec ad7606_channels[] = {
|
||||
IIO_CHAN_SOFT_TIMESTAMP(8),
|
||||
AD7606_CHANNEL(0),
|
||||
AD7606_CHANNEL(1),
|
||||
AD7606_CHANNEL(2),
|
||||
AD7606_CHANNEL(3),
|
||||
AD7606_CHANNEL(4),
|
||||
AD7606_CHANNEL(5),
|
||||
AD7606_CHANNEL(6),
|
||||
AD7606_CHANNEL(7),
|
||||
};
|
||||
|
||||
static const struct ad7606_chip_info ad7606_chip_info_tbl[] = {
|
||||
/* More devices added in future */
|
||||
[ID_AD7605_4] = {
|
||||
.channels = ad7605_channels,
|
||||
.num_channels = 5,
|
||||
},
|
||||
[ID_AD7606_8] = {
|
||||
.channels = ad7606_channels,
|
||||
.num_channels = 9,
|
||||
.has_oversampling = true,
|
||||
},
|
||||
[ID_AD7606_6] = {
|
||||
.channels = ad7606_channels,
|
||||
.num_channels = 7,
|
||||
.has_oversampling = true,
|
||||
},
|
||||
[ID_AD7606_4] = {
|
||||
.channels = ad7606_channels,
|
||||
.num_channels = 5,
|
||||
.has_oversampling = true,
|
||||
},
|
||||
};
|
||||
|
||||
static int ad7606_request_gpios(struct ad7606_state *st)
|
||||
{
|
||||
struct device *dev = st->dev;
|
||||
|
||||
st->gpio_convst = devm_gpiod_get(dev, "adi,conversion-start",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(st->gpio_convst))
|
||||
return PTR_ERR(st->gpio_convst);
|
||||
|
||||
st->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(st->gpio_reset))
|
||||
return PTR_ERR(st->gpio_reset);
|
||||
|
||||
st->gpio_range = devm_gpiod_get_optional(dev, "adi,range",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(st->gpio_range))
|
||||
return PTR_ERR(st->gpio_range);
|
||||
|
||||
st->gpio_standby = devm_gpiod_get_optional(dev, "standby",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(st->gpio_standby))
|
||||
return PTR_ERR(st->gpio_standby);
|
||||
|
||||
st->gpio_frstdata = devm_gpiod_get_optional(dev, "adi,first-data",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(st->gpio_frstdata))
|
||||
return PTR_ERR(st->gpio_frstdata);
|
||||
|
||||
if (!st->chip_info->has_oversampling)
|
||||
return 0;
|
||||
|
||||
st->gpio_os = devm_gpiod_get_array_optional(dev,
|
||||
"adi,oversampling-ratio",
|
||||
GPIOD_OUT_LOW);
|
||||
return PTR_ERR_OR_ZERO(st->gpio_os);
|
||||
}
|
||||
|
||||
/*
|
||||
* The BUSY signal indicates when conversions are in progress, so when a rising
|
||||
* edge of CONVST is applied, BUSY goes logic high and transitions low at the
|
||||
* end of the entire conversion process. The falling edge of the BUSY signal
|
||||
* triggers this interrupt.
|
||||
*/
|
||||
static irqreturn_t ad7606_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_id;
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (iio_buffer_enabled(indio_dev)) {
|
||||
gpiod_set_value(st->gpio_convst, 0);
|
||||
iio_trigger_poll_chained(st->trig);
|
||||
} else {
|
||||
complete(&st->completion);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
};
|
||||
|
||||
static int ad7606_validate_trigger(struct iio_dev *indio_dev,
|
||||
struct iio_trigger *trig)
|
||||
{
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (st->trig != trig)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7606_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
iio_triggered_buffer_postenable(indio_dev);
|
||||
gpiod_set_value(st->gpio_convst, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7606_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
gpiod_set_value(st->gpio_convst, 0);
|
||||
|
||||
return iio_triggered_buffer_predisable(indio_dev);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops ad7606_buffer_ops = {
|
||||
.postenable = &ad7606_buffer_postenable,
|
||||
.predisable = &ad7606_buffer_predisable,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7606_info_no_os_or_range = {
|
||||
.read_raw = &ad7606_read_raw,
|
||||
.validate_trigger = &ad7606_validate_trigger,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7606_info_os_and_range = {
|
||||
.read_raw = &ad7606_read_raw,
|
||||
.write_raw = &ad7606_write_raw,
|
||||
.attrs = &ad7606_attribute_group_os_and_range,
|
||||
.validate_trigger = &ad7606_validate_trigger,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7606_info_os = {
|
||||
.read_raw = &ad7606_read_raw,
|
||||
.write_raw = &ad7606_write_raw,
|
||||
.attrs = &ad7606_attribute_group_os,
|
||||
.validate_trigger = &ad7606_validate_trigger,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7606_info_range = {
|
||||
.read_raw = &ad7606_read_raw,
|
||||
.write_raw = &ad7606_write_raw,
|
||||
.attrs = &ad7606_attribute_group_range,
|
||||
.validate_trigger = &ad7606_validate_trigger,
|
||||
};
|
||||
|
||||
static const struct iio_trigger_ops ad7606_trigger_ops = {
|
||||
.validate_device = iio_trigger_validate_own_device,
|
||||
};
|
||||
|
||||
static void ad7606_regulator_disable(void *data)
|
||||
{
|
||||
struct ad7606_state *st = data;
|
||||
|
||||
regulator_disable(st->reg);
|
||||
}
|
||||
|
||||
int ad7606_probe(struct device *dev, int irq, void __iomem *base_address,
|
||||
const char *name, unsigned int id,
|
||||
const struct ad7606_bus_ops *bops)
|
||||
{
|
||||
struct ad7606_state *st;
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
st = iio_priv(indio_dev);
|
||||
dev_set_drvdata(dev, indio_dev);
|
||||
|
||||
st->dev = dev;
|
||||
mutex_init(&st->lock);
|
||||
st->bops = bops;
|
||||
st->base_address = base_address;
|
||||
/* tied to logic low, analog input range is +/- 5V */
|
||||
st->range = 0;
|
||||
st->oversampling = 1;
|
||||
|
||||
st->reg = devm_regulator_get(dev, "avcc");
|
||||
if (IS_ERR(st->reg))
|
||||
return PTR_ERR(st->reg);
|
||||
|
||||
ret = regulator_enable(st->reg);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable specified AVcc supply\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, ad7606_regulator_disable, st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->chip_info = &ad7606_chip_info_tbl[id];
|
||||
|
||||
ret = ad7606_request_gpios(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->dev.parent = dev;
|
||||
if (st->gpio_os) {
|
||||
if (st->gpio_range)
|
||||
indio_dev->info = &ad7606_info_os_and_range;
|
||||
else
|
||||
indio_dev->info = &ad7606_info_os;
|
||||
} else {
|
||||
if (st->gpio_range)
|
||||
indio_dev->info = &ad7606_info_range;
|
||||
else
|
||||
indio_dev->info = &ad7606_info_no_os_or_range;
|
||||
}
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->name = name;
|
||||
indio_dev->channels = st->chip_info->channels;
|
||||
indio_dev->num_channels = st->chip_info->num_channels;
|
||||
|
||||
init_completion(&st->completion);
|
||||
|
||||
ret = ad7606_reset(st);
|
||||
if (ret)
|
||||
dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n");
|
||||
|
||||
st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
|
||||
indio_dev->name, indio_dev->id);
|
||||
if (!st->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
st->trig->ops = &ad7606_trigger_ops;
|
||||
st->trig->dev.parent = dev;
|
||||
iio_trigger_set_drvdata(st->trig, indio_dev);
|
||||
ret = devm_iio_trigger_register(dev, st->trig);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->trig = iio_trigger_get(st->trig);
|
||||
|
||||
ret = devm_request_threaded_irq(dev, irq,
|
||||
NULL,
|
||||
&ad7606_interrupt,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
name, indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
&ad7606_trigger_handler,
|
||||
&ad7606_buffer_ops);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ad7606_probe);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int ad7606_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (st->gpio_standby) {
|
||||
gpiod_set_value(st->gpio_range, 1);
|
||||
gpiod_set_value(st->gpio_standby, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7606_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad7606_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (st->gpio_standby) {
|
||||
gpiod_set_value(st->gpio_range, st->range);
|
||||
gpiod_set_value(st->gpio_standby, 1);
|
||||
ad7606_reset(st);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SIMPLE_DEV_PM_OPS(ad7606_pm_ops, ad7606_suspend, ad7606_resume);
|
||||
EXPORT_SYMBOL_GPL(ad7606_pm_ops);
|
||||
|
||||
#endif
|
||||
|
||||
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD7606 ADC");
|
||||
MODULE_LICENSE("GPL v2");
|
Reference in New Issue
Block a user