[RFC PATCH 3/4] iio: adc: Add support for AD4170

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Ana-Maria Cusco <ana-maria.cusco@xxxxxxxxxx>

From: Ana-Maria Cusco <ana-maria.cusco@xxxxxxxxxx>

Add support for the AD4170 ADC with the following features:
- Single-shot read (read_raw), scale, sampling freq
- Multi channel buffer support
- Buffered capture in triggered mode
- Gain and offset calibration
- chop_iexc and chop_adc device configuration
- Powerdown switch configuration

Co-developed-by: Dragos Bogdan <dragos.bogdan@xxxxxxxxxx>
Signed-off-by: Dragos Bogdan <dragos.bogdan@xxxxxxxxxx>
Signed-off-by: Ana-Maria Cusco <ana-maria.cusco@xxxxxxxxxx>
Co-developed-by: Marcelo Schmitt <marcelo.schmitt@xxxxxxxxxx>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@xxxxxxxxxx>
---
 drivers/iio/adc/Kconfig  |   16 +
 drivers/iio/adc/Makefile |    1 +
 drivers/iio/adc/ad4170.c | 2049 ++++++++++++++++++++++++++++++++++++++
 drivers/iio/adc/ad4170.h |  316 ++++++
 4 files changed, 2382 insertions(+)
 create mode 100644 drivers/iio/adc/ad4170.c
 create mode 100644 drivers/iio/adc/ad4170.h

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 849c90203071..997473ac958f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -48,6 +48,22 @@ config AD4130
 	  To compile this driver as a module, choose M here: the module will be
 	  called ad4130.
 
+
+config AD4170
+	tristate "Analog Device AD4170 ADC Driver"
+	depends on SPI
+	depends on GPIOLIB
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_SPI
+	depends on COMMON_CLK
+	help
+	  Say yes here to build support for Analog Devices AD4170 SPI analog
+	  to digital converters (ADC).
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called ad4170.
+
 config AD4695
 	tristate "Analog Device AD4695 ADC Driver"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index ee19afba62b7..64c72b595580 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
 obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
 obj-$(CONFIG_AD4000) += ad4000.o
 obj-$(CONFIG_AD4130) += ad4130.o
+obj-$(CONFIG_AD4170) += ad4170.o
 obj-$(CONFIG_AD4695) += ad4695.o
 obj-$(CONFIG_AD7091R) += ad7091r-base.o
 obj-$(CONFIG_AD7091R5) += ad7091r5.o
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
new file mode 100644
index 000000000000..20b74575f2cb
--- /dev/null
+++ b/drivers/iio/adc/ad4170.c
@@ -0,0 +1,2049 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2024 Analog Devices, Inc.
+ * Author: Ana-Maria Cusco <ana-maria.cusco@xxxxxxxxxx>
+ * Author: Marcelo Schmitt <marcelo.schmitt@xxxxxxxxxx>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+#include <asm/div64.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include "ad4170.h"
+
+#define AD4170_SINC5_AVG_CONF		0x0
+#define AD4170_SINC5_CONF		0x4
+#define AD4170_SINC3_CONF		0x6
+
+enum ad4170_regulator {
+	AD4170_AVDD_SUP,
+	AD4170_AVSS_SUP,
+	AD4170_IOVDD_SUP,
+	AD4170_REFIN1P_SUP,
+	AD4170_REFIN1N_SUP,
+	AD4170_REFIN2P_SUP,
+	AD4170_REFIN2N_SUP,
+};
+
+static const char *const ad4170_clk_sel[] = {
+	"ext-clk", "xtal"
+};
+
+static const char *const ad4170_i_out_pin_dt_props[] = {
+	"adi,excitation-pin-0",
+	"adi,excitation-pin-1",
+	"adi,excitation-pin-2",
+	"adi,excitation-pin-3",
+};
+
+static const char *const ad4170_i_out_val_dt_props[] = {
+	"adi,excitation-current-0-microamp",
+	"adi,excitation-current-1-microamp",
+	"adi,excitation-current-2-microamp",
+	"adi,excitation-current-3-microamp",
+};
+
+#define AD4170_SINC5_FS_PADDING		8
+#define AD4170_SINC3_FS_OFFSET		2
+
+#define AD4170_SINC3_MIN_FS		4
+#define AD4170_SINC3_MAX_FS		65532
+#define AD4170_SINC5_MIN_FS		1
+#define AD4170_SINC5_MAX_FS		256
+
+/* Make separate fs tables? One for SINC5 other for SINC5+AVG and SINC3? */
+static const unsigned int ad4170_filt_fs_tbl[] = {
+	1, /* SINC5 minimum filter_fs */
+	2,
+	4, /* SINC5+AVG and SINC3 minimum filter_fs */
+	8,
+	12,
+	16,
+	20,
+	40,
+	48,
+	80,
+	100,
+	256, /* SINC5 maximum filter_fs */
+	500,
+	1000,
+	5000,
+	8332,
+	10000,
+	25000,
+	50000,
+	65532 /* SINC5+AVG and SINC3 maximum filter_fs */
+};
+
+/*
+ * There are 8 of each MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN
+ * configuration registers. That is, there are 8 miscellaneous registers, MISC0
+ * to MISC7. Each MISC register is associated with a setup; MISCN is associated
+ * with setup number N. The other 5 above mentioned types of registers have
+ * analogous structure. A setup is a set of those registers. For example,
+ * setup 1 comprises of MISC1, AFE1, FILTER1, FILTER_FS1, OFFSET1, and GAIN1
+ * registers. Also, there are 16 CHANNEL_SETUP registers (CHANNEL_SETUP0 to
+ * CHANNEL_SETUP15). Each channel setup is associated with one of the 8 possible
+ * setups. Thus, AD4170 can support up to 16 channels but, since there are only
+ * 8 available setups, channels must share settings if more than 8 channels are
+ * configured.
+ */
+struct ad4170_setup {
+	u16 misc;
+	u16 afe;
+	u16 filter;
+	u16 filter_fs;
+	u32 offset; /* For calibration purposes */
+	u32 gain; /* For calibration purposes */
+};
+
+struct ad4170_chan_info {
+	int setup_num;
+	int input_range_uv;
+	u32 scale_tbl[10][2];
+	int offset_tbl[10];
+	bool enabled;
+};
+
+static const char * const ad4170_filt_names[] = {
+	[AD4170_SINC5_AVG] = "sinc5+avg",
+	[AD4170_SINC5] = "sinc5",
+	[AD4170_SINC3] = "sinc3",
+};
+
+struct ad4170_state {
+	struct regmap *regmap;
+	struct spi_device *spi;
+	struct regulator_bulk_data supplies[7];
+	struct mutex lock; /* Protect filter, PGA, GPIO, chan read, chan config */
+	struct iio_chan_spec chans[AD4170_MAX_CHANNELS];
+	struct ad4170_chan_info chans_info[AD4170_MAX_CHANNELS];
+	struct ad4170_setup setups[AD4170_MAX_SETUPS];
+	u32 mclk_hz;
+	enum ad4170_pin_function pins_fn[AD4170_NUM_ANALOG_PINS];
+	enum ad4170_gpio_function gpio_fn[AD4170_NUM_GPIO_PINS];
+	unsigned int clock_ctrl;
+	struct clk *ext_clk;
+	struct clk_hw int_clk_hw;
+	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][ARRAY_SIZE(ad4170_filt_fs_tbl)][2];
+	struct completion completion;
+	struct iio_trigger *trig;
+	u32 data[AD4170_MAX_CHANNELS];
+
+	struct spi_transfer xfer;
+	struct spi_message msg;
+	/*
+	 * DMA (thus cache coherency maintenance) requires the transfer buffers
+	 * to live in their own cache lines.
+	 */
+	u8 reg_write_tx_buf[6];
+	__be32 reg_read_rx_buf __aligned(IIO_DMA_MINALIGN);
+	__be16 reg_read_tx_buf;
+};
+
+static void ad4170_fill_sps_tbl(struct ad4170_state *st)
+{
+	unsigned int tmp0, tmp1, i;
+
+	/*
+	 * The ODR can be calculated the same way for sinc5+avg, sinc5, and
+	 * sinc3 filter types with the exception that sinc5 filter has a
+	 * narrowed range of allowed FILTER_FS values.
+	 */
+	for (i = AD4170_SINC3_FS_OFFSET; i < ARRAY_SIZE(ad4170_filt_fs_tbl); i++) {
+		tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_filt_fs_tbl[i], &tmp1);
+		tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_filt_fs_tbl[i]);
+		// sinc5+avg and sinc3 havethe same SPS table. Drop one of them?
+		/* Fill sinc5+avg filter SPS table */
+		st->sps_tbl[AD4170_SINC5_AVG][i][0] = tmp0; /* Integer part */
+		st->sps_tbl[AD4170_SINC5_AVG][i][1] = tmp1; /* Fractional part */
+
+		/* Fill sinc3 filter SPS table */
+		st->sps_tbl[AD4170_SINC3][i][0] = tmp0; /* Integer part */
+		st->sps_tbl[AD4170_SINC3][i][1] = tmp1; /* Fractional part */
+	}
+	/* Sinc5 filter ODR doesn't use all FILTER_FS bits */
+	for (i = 0; i < ARRAY_SIZE(ad4170_filt_fs_tbl) - AD4170_SINC5_FS_PADDING; i++) {
+		tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_filt_fs_tbl[i], &tmp1);
+		tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_filt_fs_tbl[i]);
+		/* Fill sinc5 filter SPS table */
+		st->sps_tbl[AD4170_SINC5][i][0] = tmp0; /* Integer part */
+		st->sps_tbl[AD4170_SINC5][i][1] = tmp1; /* Fractional part */
+	}
+}
+
+static int ad4170_get_reg_size(struct ad4170_state *st, unsigned int reg,
+			       unsigned int *size)
+{
+	if (reg >= ARRAY_SIZE(ad4170_reg_size))
+		return -EINVAL;
+
+	if (!ad4170_reg_size[reg])
+		return -EINVAL;
+
+	*size = ad4170_reg_size[reg];
+
+	return 0;
+}
+
+static int ad4170_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+			     unsigned int writeval, unsigned int *readval)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static int ad4170_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct ad4170_state *st = context;
+	unsigned int size, addr;
+	int ret;
+
+	ret = ad4170_get_reg_size(st, reg, &size);
+	if (ret)
+		return ret;
+
+	addr = reg + size - 1;
+	put_unaligned_be16(addr, &st->reg_write_tx_buf[0]);
+
+	switch (size) {
+	case 4:
+		put_unaligned_be32(val, &st->reg_write_tx_buf[2]);
+		break;
+	case 3:
+		put_unaligned_be24(val, &st->reg_write_tx_buf[2]);
+		break;
+	case 2:
+		put_unaligned_be16(val, &st->reg_write_tx_buf[2]);
+		break;
+	case 1:
+		st->reg_write_tx_buf[2] = val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return spi_write(st->spi, st->reg_write_tx_buf, size + 2);
+}
+
+static int ad4170_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct ad4170_state *st = context;
+	struct spi_transfer t[] = {
+		{
+			.tx_buf = &st->reg_read_tx_buf,
+			.len = 2,
+		},
+		{
+			.rx_buf = &st->reg_read_rx_buf,
+		},
+	};
+	unsigned int size, addr;
+	int ret;
+
+	ret = ad4170_get_reg_size(st, reg, &size);
+	if (ret)
+		return ret;
+
+	addr = reg + size - 1;
+	put_unaligned_be16(AD4170_READ_MASK | addr, &st->reg_read_tx_buf);
+
+	t[1].len = size;
+
+	ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
+	if (ret)
+		return ret;
+
+	switch (size) {
+	case 4:
+		*val = get_unaligned_be32(&st->reg_read_rx_buf);
+		break;
+	case 3:
+		*val = get_unaligned_be24(&st->reg_read_rx_buf);
+		break;
+	case 2:
+		*val = get_unaligned_be16(&st->reg_read_rx_buf);
+		break;
+	case 1:
+		*val = st->reg_read_rx_buf;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct regmap_config ad4170_regmap_config = {
+	.reg_bits = 14,
+	.val_bits = 32,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.reg_read = ad4170_reg_read,
+	.reg_write = ad4170_reg_write,
+};
+
+static int ad4170_set_mode(struct ad4170_state *st, unsigned int mode)
+{
+	return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				  AD4170_REG_CTRL_MODE_MSK,
+				  FIELD_PREP(AD4170_REG_CTRL_MODE_MSK, mode));
+}
+
+/* 8 possible setups (0-7)*/
+static int ad4170_write_setup(struct ad4170_state *st, unsigned int setup_num,
+			      struct ad4170_setup *setup)
+{
+	int ret;
+
+	/*
+	 * It is recommended to place the ADC in standby mode or idle mode to
+	 * write to OFFSET and GAIN registers.
+	 */
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_MISC_REG(setup_num), setup->misc);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_AFE_REG(setup_num), setup->afe);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_FILTER_REG(setup_num),
+			   setup->filter);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_FILTER_FS_REG(setup_num),
+			   setup->filter_fs);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_OFFSET_REG(setup_num),
+			   setup->offset);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4170_GAIN_REG(setup_num), setup->gain);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ad4170_filter_to_filter_type(unsigned int filter)
+{
+	u16 f_conf = FIELD_GET(AD4170_SETUPS_FILTER_TYPE_MSK, filter);
+
+	switch (f_conf) {
+	case AD4170_SINC5_AVG_CONF:
+		return AD4170_SINC5_AVG;
+	case AD4170_SINC5_CONF:
+		return AD4170_SINC5;
+	case AD4170_SINC3_CONF:
+		return AD4170_SINC3;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_set_filter_type(struct iio_dev *indio_dev,
+				  const struct iio_chan_spec *chan,
+				  unsigned int val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[chan->address];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	unsigned int old_filter_fs, old_filter, filter_type_conf;
+	int ret = 0;
+
+	switch (val) {
+	case AD4170_SINC5_AVG:
+		filter_type_conf = AD4170_SINC5_AVG_CONF;
+		break;
+	case AD4170_SINC5:
+		filter_type_conf = AD4170_SINC5_CONF;
+		break;
+	case AD4170_SINC3:
+		filter_type_conf = AD4170_SINC3_CONF;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * The filters provide the same ODR for a given filter_fs value but
+	 * there are different minimum and maximum filter_fs limits for each
+	 * filter. The filter_fs value will be adjusted if the current filter_fs
+	 * is out of the limits of the just requested filter. Since the
+	 * filter_fs value affects the ODR (sampling_frequency), changing the
+	 * filter may lead to a change in the sampling frequency.
+	 */
+	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+		old_filter = setup->filter;
+		old_filter_fs = setup->filter_fs;
+		if (val == AD4170_SINC5_AVG || val == AD4170_SINC3) {
+			if (setup->filter_fs < AD4170_SINC3_MIN_FS)
+				setup->filter_fs = AD4170_SINC3_MIN_FS;
+			if (setup->filter_fs > AD4170_SINC3_MAX_FS)
+				setup->filter_fs = AD4170_SINC3_MAX_FS;
+
+		} else if (val == AD4170_SINC5) {
+			if (setup->filter_fs < AD4170_SINC5_MIN_FS)
+				setup->filter_fs = AD4170_SINC5_MIN_FS;
+			if (setup->filter_fs > AD4170_SINC5_MAX_FS)
+				setup->filter_fs = AD4170_SINC5_MAX_FS;
+		}
+
+		setup->filter &= ~AD4170_SETUPS_FILTER_TYPE_MSK;
+		setup->filter |= FIELD_PREP(AD4170_SETUPS_FILTER_TYPE_MSK,
+					    filter_type_conf);
+
+		guard(mutex)(&st->lock);
+		ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+		if (ret) {
+			setup->filter = old_filter;
+			setup->filter_fs = old_filter_fs;
+		}
+		return ret;
+	}
+	unreachable();
+}
+
+static int ad4170_get_filter_type(struct iio_dev *indio_dev,
+				  const struct iio_chan_spec *chan)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[chan->address];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+
+	return ad4170_filter_to_filter_type(setup->filter);
+}
+
+static const struct iio_enum ad4170_filter_type_enum = {
+	.items = ad4170_filt_names,
+	.num_items = ARRAY_SIZE(ad4170_filt_names),
+	.get = ad4170_get_filter_type,
+	.set = ad4170_set_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = {
+	IIO_ENUM("filter_type", IIO_SEPARATE, &ad4170_filter_type_enum),
+	IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+			   &ad4170_filter_type_enum),
+	{ }
+};
+
+static const struct iio_chan_spec ad4170_channel_template = {
+	.type = IIO_VOLTAGE,
+	.indexed = 1,
+	.differential = 1,
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			      BIT(IIO_CHAN_INFO_SCALE) |
+			      BIT(IIO_CHAN_INFO_OFFSET) |
+			      BIT(IIO_CHAN_INFO_CALIBSCALE) |
+			      BIT(IIO_CHAN_INFO_CALIBBIAS) |
+			      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+					BIT(IIO_CHAN_INFO_SAMP_FREQ),
+	.ext_info = ad4170_filter_type_ext_info,
+	.scan_type = {
+		.realbits = 24,
+		.storagebits = 32,
+		.endianness = IIO_BE,
+	},
+};
+
+static int _ad4170_find_table_index(const unsigned int *tbl, size_t len,
+				    unsigned int val)
+{
+	unsigned int i;
+
+	for (i = 0; i < len; i++)
+		if (tbl[i] == val)
+			return i;
+
+	return -EINVAL;
+}
+
+#define ad4170_find_table_index(table, val) \
+	_ad4170_find_table_index(table, ARRAY_SIZE(table), val)
+
+static int ad4170_set_channel_enable(struct ad4170_state *st,
+				     unsigned int channel, bool status)
+{
+	struct ad4170_chan_info *chan_info = &st->chans_info[channel];
+	int ret;
+
+	if (chan_info->enabled == status)
+		return 0;
+
+	ret = regmap_update_bits(st->regmap, AD4170_CHANNEL_EN_REG,
+				 AD4170_CHANNEL_EN(channel),
+				 status ? AD4170_CHANNEL_EN(channel) : 0);
+	if (ret)
+		return ret;
+
+	chan_info->enabled = status;
+	return 0;
+}
+
+static int ad4170_read_sample(struct iio_dev *indio_dev, unsigned int channel,
+			      int *val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[channel];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	int precision_bits = ad4170_channel_template.scan_type.realbits;
+	int ret;
+
+	guard(mutex)(&st->lock);
+	ret = ad4170_set_channel_enable(st, channel, true);
+	if (ret)
+		return ret;
+
+	reinit_completion(&st->completion);
+
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_SINGLE);
+	if (ret)
+		return ret;
+
+	ret = wait_for_completion_timeout(&st->completion, HZ);
+	if (!ret)
+		goto out;
+
+	ret = regmap_read(st->regmap, AD4170_DATA_24b_REG, val);
+	if (ret)
+		return ret;
+
+	if (FIELD_GET(AD4170_AFE_BIPOLAR_MSK, setup->afe))
+		*val = sign_extend32(*val, precision_bits - 1);
+out:
+	ret = ad4170_set_channel_enable(st, channel, false);
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+/*
+ * Receives the device state, the number of a multiplexed input (AINP_N
+ * or AIM_N), and stores the voltage (in µV) of the specified input into the
+ * third argument. If the input number is not one of the special multiplexed
+ * inputs ((AVDD-AVSS)/5, ..., REFOUT), stores zero to the voltage argument.
+ * If a voltage regulator required by the special input is unavailable, return
+ * error code. Return 0 on success.
+ *
+ * @st: pointer to device state struct
+ * @ain_n: number of a multiplexed AD4170 input
+ * @ain_voltage: pointer to a variable where to store ain_n voltage
+ */
+static int ad4170_get_AINM_voltage(struct ad4170_state *st, int ain_n,
+				   int *ain_voltage)
+{
+	int ret;
+
+	*ain_voltage = 0;
+	switch (ain_n) {
+	case AD4170_MAP_AVDD_AVSS_N:
+		ret = regulator_get_voltage(st->supplies[AD4170_AVDD_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		*ain_voltage = ret ? ret / 5 : 0;
+		return 0;
+	case AD4170_MAP_IOVDD_DGND_N:
+		ret = regulator_get_voltage(st->supplies[AD4170_IOVDD_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		*ain_voltage = ret ? ret / 5 : 0;
+		return 0;
+	case AD4170_MAP_AVSS:
+		ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer);
+		if (ret < 0)
+			ret = 0; /* Assume AVSS at 0V if not provided */
+
+		/* AVSS is never above 0V, i.e., it can only be negative. */
+		*ain_voltage = -ret; /* AVSS is a negative voltage */
+		return 0;
+	case AD4170_MAP_DGND:
+		*ain_voltage = 0;
+		return 0;
+	case AD4170_MAP_REFIN1_P:
+		ret = regulator_get_voltage(st->supplies[AD4170_REFIN1P_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		*ain_voltage = ret;
+		return 0;
+	case AD4170_MAP_REFIN1_N:
+		ret = regulator_get_voltage(st->supplies[AD4170_REFIN1N_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		/*
+		 * Making the assumption negative inputs of voltage references
+		 * are either at GND level or negative with respect to GND.
+		 */
+		*ain_voltage = -ret;
+		return 0;
+	case AD4170_MAP_REFIN2_P:
+		ret = regulator_get_voltage(st->supplies[AD4170_REFIN2P_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		*ain_voltage = ret;
+		return 0;
+	case AD4170_MAP_REFIN2_N:
+		ret = regulator_get_voltage(st->supplies[AD4170_REFIN2N_SUP].consumer);
+		if (ret < 0)
+			return ret;
+
+		/*
+		 * Making the assumption negative inputs of voltage references
+		 * are either at GND level or negative with respect to GND.
+		 */
+		*ain_voltage = -ret;
+		return 0;
+	case AD4170_MAP_REFOUT:
+		/* REFOUT is 2.5V relative to AVSS so take that into account */
+		ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer);
+		if (ret < 0)
+			ret = 0; /* Assume AVSS at GND (0V) if not provided */
+
+		*ain_voltage = AD4170_INT_REF_2_5V - ret;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int ad4170_validate_analog_input(struct ad4170_state *st, int pin)
+{
+	if (pin <= AD4170_MAX_ANALOG_PINS) {
+		if (st->pins_fn[pin] != AD4170_PIN_UNASIGNED)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Pin %d already used with fn %u.\n",
+					     pin, st->pins_fn[pin]);
+
+		st->pins_fn[pin] = AD4170_PIN_ANALOG_IN;
+	}
+	return 0;
+}
+
+static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool com)
+{
+	/* Check common-mode input pin is mapped to a special input. */
+	if (com && (pin < AD4170_MAP_AVDD_AVSS_P || pin > AD4170_MAP_REFOUT))
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "Invalid common-mode input pin number. %d\n",
+				     pin);
+
+	/* Check differential input pin is mapped to a analog input pin. */
+	if (!com && pin > AD4170_MAX_ANALOG_PINS)
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "Invalid analog input pin number. %d\n",
+				     pin);
+
+	return ad4170_validate_analog_input(st, pin);
+}
+
+/*
+ * Verifies whether the channel input configuration is valid by checking the
+ * provided input type and input numbers.
+ * Returns 0 on valid channel input configuration. -EINVAl otherwise.
+ */
+static int ad4170_validate_channel(struct ad4170_state *st,
+				   struct iio_chan_spec const *chan)
+{
+	int ret;
+
+	/* Check temperature channel mapping. */
+	if (chan->channel == AD4170_MAP_TEMP_SENSOR_P) {
+		if (chan->channel2 != AD4170_MAP_TEMP_SENSOR_N)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Invalid temperature channel pin. %d\n",
+					     chan->channel2);
+
+		return 0;
+	}
+
+	ret = ad4170_validate_channel_input(st, chan->channel, false);
+	if (ret < 0)
+		return ret;
+
+	ret = ad4170_validate_channel_input(st, chan->channel2, !chan->differential);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Receives the device state, the channel spec, a reference selection, and
+ * returns the magnitude of the allowed input range in µV.
+ * Verifies whether the channel configuration is valid by checking the provided
+ * input type, polarity, and voltage references result in a sane input range.
+ * Returns negative error code on failure.
+ */
+static int ad4170_get_input_range(struct ad4170_state *st,
+				  struct iio_chan_spec const *chan,
+				  unsigned int ref_sel)
+{
+	struct ad4170_chan_info *chan_info = &st->chans_info[chan->address];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	bool bipolar = FIELD_GET(AD4170_AFE_BIPOLAR_MSK, setup->afe);
+	int refp, refn, ain_voltage, ret;
+
+	switch (ref_sel) {
+	case AD4170_AFE_REFIN_REFIN1:
+		refp = regulator_get_voltage(st->supplies[AD4170_REFIN1P_SUP].consumer);
+		refn = regulator_get_voltage(st->supplies[AD4170_REFIN1N_SUP].consumer);
+		break;
+	case AD4170_AFE_REFIN_REFIN2:
+		refp = regulator_get_voltage(st->supplies[AD4170_REFIN2P_SUP].consumer);
+		refn = regulator_get_voltage(st->supplies[AD4170_REFIN2N_SUP].consumer);
+		break;
+	case AD4170_AFE_REFIN_AVDD:
+		refp = regulator_get_voltage(st->supplies[AD4170_AVDD_SUP].consumer);
+		ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer);
+		/*
+		 * TODO AVSS is actually optional.
+		 * Should we handle -EPROBE_DEFER here?
+		 */
+		if (ret < 0)
+			ret = 0; /* Assume AVSS at GND if not provided */
+
+		refn = ret;
+		break;
+	case AD4170_AFE_REFIN_REFOUT:
+		refn = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer);
+		if (refn < 0)
+			refn = 0;
+
+		/* REFOUT is 2.5 V relative to AVSS */
+		/* avss-supply is never above 0V. */
+		refp = AD4170_INT_REF_2_5V - refn;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (refp < 0)
+		return refp;
+
+	if (refn < 0)
+		return refn;
+
+	/*
+	 * Find out the analog input range from the channel type, polarity, and
+	 * voltage reference selection.
+	 * AD4170 channels are either differential or pseudo-differential.
+	 */
+	/* Differential Input Voltage Range: −VREF/gain to +VREF/gain (datasheet page 6) */
+	/* Single-Ended Input Voltage Range: 0 to VREF/gain (datasheet page 6) */
+	if (chan->differential) {
+		if (!bipolar)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Invalid channel %lu setup.\n",
+					     chan->address);
+
+		/* Differential bipolar channel */
+		/* avss-supply is never above 0V. */
+		/* Assuming refin1n-supply not above 0V. */
+		/* Assuming refin2n-supply not above 0V. */
+		return refp + refn;
+	}
+	/*
+	 * Some configurations can lead to invalid setups.
+	 * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS),
+	 * and single-ended channel configuration set, then the input range
+	 * should go from 0V to +VREF (single-ended - datasheet pg 10), but
+	 * REFOUT/AVSS range would be -2.5V to 0V.
+	 * Check the positive reference is higher than 0V for pseudo-diff
+	 * channels.
+	 */
+	if (bipolar) {
+		/* Pseudo-differential bipolar channel */
+		/* Input allowed to swing from GND to +VREF */
+		if (refp <= 0)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Invalid setup for channel %lu.\n",
+					     chan->address);
+
+		return refp;
+	}
+
+	/* Pseudo-differential unipolar channel */
+	/* Input allowed to swing from IN- to +VREF */
+	if (refp <= 0)
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "Invalid setup for channel %lu.\n",
+				     chan->address);
+
+	ret = ad4170_get_AINM_voltage(st, chan->channel2, &ain_voltage);
+	if (ret < 0)
+		return ret;
+
+	if (refp - ain_voltage <= 0)
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "Invalid setup for channel %lu.\n",
+				     chan->address);
+
+	return refp - ain_voltage;
+}
+
+static int ad4170_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long info)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[chan->address];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	enum ad4170_filter_type f_type;
+	unsigned int pga, fs_idx;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		iio_device_claim_direct_scoped(return -EBUSY, indio_dev)
+			return ad4170_read_sample(indio_dev, chan->address, val);
+		unreachable();
+	case IIO_CHAN_INFO_SCALE:
+		pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+		*val = chan_info->scale_tbl[pga][0];
+		*val2 = chan_info->scale_tbl[pga][1];
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_OFFSET:
+		pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+		*val = chan_info->offset_tbl[pga];
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		f_type = ad4170_filter_to_filter_type(setup->filter);
+		fs_idx = find_closest(setup->filter_fs, ad4170_filt_fs_tbl,
+				      ARRAY_SIZE(ad4170_filt_fs_tbl));
+		*val = st->sps_tbl[f_type][fs_idx][0];
+		*val2 = st->sps_tbl[f_type][fs_idx][1];
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*val = setup->offset;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = setup->gain;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev, int channel)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[channel];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	const struct iio_chan_spec *chan = &indio_dev->channels[channel];
+	bool bipolar = FIELD_GET(AD4170_AFE_BIPOLAR_MSK, setup->afe);
+	int ch_resolution = chan->scan_type.realbits - bipolar;
+	int pga, ainm_voltage, ret;
+	unsigned long long offset;
+
+	ainm_voltage = 0;
+	if (chan->channel2 > AD4170_MAP_TEMP_SENSOR_N) {
+		ret = ad4170_get_AINM_voltage(st, chan->channel2, &ainm_voltage);
+		if (ret < 0)
+			return dev_err_probe(&st->spi->dev, ret,
+					     "Failed to fill scale table\n");
+	}
+
+	for (pga = 0; pga < AD4170_PGA_OPTIONS; pga++) {
+		u64 nv;
+		unsigned int lshift, rshift;
+
+		/*
+		 * The scale factor to get ADC output codes to values in mV
+		 * units is given by:
+		 * _scale = (input_range / gain) / 2^precision
+		 * AD4170 gain is a power of 2 so the above can be written as
+		 * _scale = input_range / 2^(precision + gain)
+		 * Keep the input range in µV before right shift to preserve
+		 * scale precision.
+		 */
+		nv = (u64)chan_info->input_range_uv * NANO;
+		lshift = (pga >> 3 & 1);  /* handle cases 8 and 9 */
+		rshift = ch_resolution + (pga & 0x7) - lshift;
+		chan_info->scale_tbl[pga][0] = 0;
+		chan_info->scale_tbl[pga][1] = div_u64(nv >> rshift, MILLI);
+
+		/*
+		 * If the negative input is not at GND, the conversion result
+		 * (which is relative to IN-) will be offset by the level at IN-.
+		 * Use the scale factor the other way around to go from a known
+		 * voltage to the corresponding ADC output code.
+		 * With that, we are able to get to what would be the output
+		 * code for the voltage at the negative input.
+		 * If the negative input is not fixed, there is no offset.
+		 */
+		offset = ((unsigned long long)ainm_voltage) * MICRO;
+		offset = DIV_ROUND_CLOSEST_ULL(offset, chan_info->scale_tbl[pga][1]);
+
+		/*
+		 * After divided by the scale, offset will always fit into 31
+		 * bits. For _raw + _offset to be relative to GND, the value
+		 * provided as _offset is of opposite sign than the real offset.
+		 */
+		chan_info->offset_tbl[pga] = -(int)(offset);
+	}
+	return 0;
+}
+
+static int ad4170_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type, int *length,
+			     long info)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[chan->address];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	enum ad4170_filter_type f_type = ad4170_filter_to_filter_type(setup->filter);
+
+	switch (info) {
+	case IIO_CHAN_INFO_SCALE:
+		*vals = (int *)chan_info->scale_tbl;
+		*length = ARRAY_SIZE(chan_info->scale_tbl) * 2;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (f_type) {
+		case AD4170_SINC5_AVG:
+			fallthrough;
+		case AD4170_SINC3:
+			*vals = (int *)&st->sps_tbl[f_type][AD4170_SINC3_FS_OFFSET];
+			*length = (ARRAY_SIZE(ad4170_filt_fs_tbl)
+				   - AD4170_SINC3_FS_OFFSET) * 2;
+			break;
+		case AD4170_SINC5:
+			*vals = (int *)st->sps_tbl[f_type];
+			*length = (ARRAY_SIZE(ad4170_filt_fs_tbl)
+				   - AD4170_SINC5_FS_PADDING) * 2;
+			break;
+		default:
+			return -EINVAL;
+		}
+		*type = IIO_VAL_INT_PLUS_MICRO;
+
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
+				    struct iio_chan_spec const *chan,
+				    long info)
+{
+	switch (info) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_CALIBBIAS:
+	case IIO_CHAN_INFO_CALIBSCALE:
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_set_pga(struct iio_dev *indio_dev, struct ad4170_state *st,
+			  unsigned int channel_addr, int val, int val2)
+{
+	struct ad4170_chan_info *chan_info = &st->chans_info[channel_addr];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	unsigned int old_pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+	unsigned int pga;
+	int ret = 0;
+
+	for (pga = 0; pga < AD4170_PGA_OPTIONS; pga++) {
+		if (val == chan_info->scale_tbl[pga][0] &&
+		    val2 == chan_info->scale_tbl[pga][1])
+			break;
+	}
+
+	if (pga == AD4170_PGA_OPTIONS)
+		return -EINVAL;
+
+	if (pga == old_pga)
+		return 0;
+
+	setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+	setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, pga);
+
+	guard(mutex)(&st->lock);
+	ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+	if (ret) {
+		setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+		setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, old_pga);
+	}
+
+	return ret;
+}
+
+static int ad4170_set_channel_freq(struct ad4170_state *st,
+				   unsigned int channel_addr, int val, int val2)
+{
+	struct ad4170_chan_info *chan_info = &st->chans_info[channel_addr];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	enum ad4170_filter_type f_type = ad4170_filter_to_filter_type(setup->filter);
+	int offset, padding, i, ret = 0;
+	unsigned int old_filter_fs;
+
+	switch (f_type) {
+	case AD4170_SINC5_AVG:
+		fallthrough;
+	case AD4170_SINC3:
+		offset = AD4170_SINC3_FS_OFFSET;
+		padding = 0;
+		break;
+	case AD4170_SINC5:
+		offset = 0;
+		padding = AD4170_SINC5_FS_PADDING;
+		break;
+	}
+
+	for (i = offset; i < ARRAY_SIZE(ad4170_filt_fs_tbl) - padding; i++) {
+		if (st->sps_tbl[f_type][i][0] == val &&
+		    st->sps_tbl[f_type][i][1] == val2)
+			break;
+	}
+	if (i >= ARRAY_SIZE(ad4170_filt_fs_tbl) - padding)
+		return -EINVAL;
+
+	old_filter_fs = setup->filter_fs;
+	setup->filter_fs = ad4170_filt_fs_tbl[i];
+
+	guard(mutex)(&st->lock);
+	ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+	if (ret)
+		setup->filter_fs = old_filter_fs;
+
+	return ret;
+}
+
+static int ad4170_set_calib_gain(struct iio_dev *indio_dev, int addr, int val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[addr];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	u32 old_gain;
+	int ret;
+
+	old_gain = setup->gain;
+	setup->gain = val;
+
+	guard(mutex)(&st->lock);
+	ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+	if (ret)
+		setup->gain = old_gain;
+
+	return ret;
+}
+
+static int ad4170_set_calib_offset(struct iio_dev *indio_dev, int addr, int val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chans_info[addr];
+	struct ad4170_setup *setup = &st->setups[chan_info->setup_num];
+	u32 old_offset;
+	int ret;
+
+	old_offset = setup->offset;
+	setup->offset = val;
+
+	guard(mutex)(&st->lock);
+	ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+	if (ret)
+		setup->offset = old_offset;
+
+	return ret;
+}
+
+static int ad4170_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long info)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+
+	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+		switch (info) {
+		case IIO_CHAN_INFO_SCALE:
+			return ad4170_set_pga(indio_dev, st, chan->address, val,
+					      val2);
+		case IIO_CHAN_INFO_SAMP_FREQ:
+			return ad4170_set_channel_freq(st, chan->address, val,
+						       val2);
+		case IIO_CHAN_INFO_CALIBBIAS:
+			return ad4170_set_calib_offset(indio_dev, chan->address,
+						       val);
+		case IIO_CHAN_INFO_CALIBSCALE:
+			return ad4170_set_calib_gain(indio_dev, chan->address,
+						     val);
+		default:
+			return -EINVAL;
+		}
+	}
+	unreachable();
+}
+
+static int ad4170_update_scan_mode(struct iio_dev *indio_dev,
+				   const unsigned long *active_scan_mask)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	unsigned int channel;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	for_each_set_bit(channel, active_scan_mask, indio_dev->num_channels) {
+		ret = ad4170_set_channel_enable(st, channel, true);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static const struct iio_info ad4170_info = {
+	.read_raw = ad4170_read_raw,
+	.read_avail = ad4170_read_avail,
+	.write_raw = ad4170_write_raw,
+	.write_raw_get_fmt = ad4170_write_raw_get_fmt,
+	.update_scan_mode = ad4170_update_scan_mode,
+	.debugfs_reg_access = ad4170_reg_access,
+};
+
+static int ad4170_soft_reset(struct ad4170_state *st)
+{
+	int ret;
+
+	ret = regmap_write(st->regmap, AD4170_INTERFACE_CONFIG_A_REG,
+			   AD4170_SW_RESET_MSK);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * AD4170-4 requires a minimum of 1 ms between any reset event and a
+	 * register read/write transaction.
+	 */
+	fsleep(AD4170_RESET_SLEEP_US);
+
+	return 0;
+}
+
+static void ad4170_clk_disable_unprepare(void *clk)
+{
+	clk_disable_unprepare(clk);
+}
+
+static int ad4170_parse_pin_muxing(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	u32 pin_muxing;
+	u8 aux;
+	int ret;
+
+	/*
+	 * Optional adi,dig-aux1 defaults to 0, DIG_AUX1 pin disabled.
+	 */
+	aux = AD4170_DIG_AUX1_DISABLED;
+	ret = device_property_read_u8(dev, "adi,dig-aux1", &aux);
+	if (ret) {
+		if (aux < AD4170_DIG_AUX1_DISABLED || aux > AD4170_DIG_AUX1_SYNC)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid adi,dig-aux1: %u\n", aux);
+	}
+	pin_muxing |= FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, aux);
+
+	/*
+	 * Optional adi,dig-aux2 defaults to 0, DIG_AUX2 pin disabled.
+	 */
+	aux = AD4170_DIG_AUX2_DISABLED;
+	ret = device_property_read_u8(dev, "adi,dig-aux2", &aux);
+	if (ret) {
+		if (aux < AD4170_DIG_AUX2_DISABLED || aux > AD4170_DIG_AUX2_SYNC)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid adi,dig-aux2: %u\n", aux);
+	}
+	pin_muxing |= FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX2_CTRL_MSK, aux);
+
+	/*
+	 * Optional adi,sync-option defaults to 1, standard sync functionality.
+	 */
+	aux = AD4170_SYNC_STANDARD;
+	ret = device_property_read_u8(dev, "adi,sync-option", &aux);
+	if (ret) {
+		if (aux < AD4170_SYNC_DISABLED || aux > AD4170_SYNC_ALTERNATE)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid adi,sync-option: %u\n", aux);
+	}
+	pin_muxing |= FIELD_PREP(AD4170_PIN_MUXING_SYNC_CTRL_MSK, aux);
+
+	return regmap_write(st->regmap, AD4170_PIN_MUXING_REG, pin_muxing);
+}
+
+static int ad4170_parse_setup(struct ad4170_state *st,
+			      struct fwnode_handle *child,
+			      struct ad4170_setup *setup)
+{
+	struct device *dev = &st->spi->dev;
+	u32 tmp;
+	u16 aux16;
+	u8 aux;
+	int ret;
+
+	/* ADC input chopping setup */
+	aux16 = AD4170_MISC_CHOP_ADC_OFF;
+	ret = fwnode_property_read_u16(child, "adi,chop-adc", &aux16);
+	if (!ret) {
+		ret = ad4170_find_table_index(ad4170_chop_adc_tbl, aux16);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "Invalid ADC chop config: %u\n",
+					     aux16);
+	}
+	setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, aux16);
+
+	/* Burnout current setup */
+	tmp = AD4170_BURNOUT_OFF;
+	fwnode_property_read_u32(child, "adi,burnout-current-nanoamp", &tmp);
+	if (!ret) {
+		ret = ad4170_find_table_index(ad4170_burnout_current_na_tbl, tmp);
+		return dev_err_probe(dev, ret,
+				     "Invalid burnout current %unA\n", tmp);
+	}
+	setup->misc |= FIELD_PREP(AD4170_MISC_BURNOUT_MSK, tmp);
+
+	/* Positive reference buffer setup */
+	tmp = AD4170_REF_BUF_PRE; /* Default to have precharge buffer enabled. */
+	ret = fwnode_property_read_u32(child, "adi,buffered-positive", &tmp);
+	if (ret) {
+		if (tmp < AD4170_REF_BUF_PRE || tmp > AD4170_REF_BUF_BYPASS)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid adi,buffered-positive: %u\n",
+					     tmp);
+	}
+	setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, tmp);
+
+	/* Negative reference buffer setup */
+	tmp = AD4170_REF_BUF_PRE; /* Default to have precharge buffer enabled. */
+	ret = fwnode_property_read_u32(child, "adi,buffered-negative", &tmp);
+	if (ret) {
+		if (tmp < AD4170_REF_BUF_PRE || tmp > AD4170_REF_BUF_BYPASS)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid adi,buffered-negative: %u\n",
+					     tmp);
+	}
+	setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_M_MSK, tmp);
+
+	/* Voltage reference selection */
+	aux = AD4170_AFE_REFIN_REFOUT; /* Default reference selection. */
+	fwnode_property_read_u8(child, "adi,reference-select", &aux);
+	if (aux >= AD4170_AFE_REFIN_AVDD)
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid reference selected %u\n", aux);
+	setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, aux);
+
+	return 0;
+}
+
+static int ad4170_parse_channel_type(struct device *dev,
+				     struct fwnode_handle *child,
+				     struct iio_chan_spec *chan)
+{
+	u32 pins[2];
+	int ret;
+
+	ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
+					     ARRAY_SIZE(pins));
+	if (!ret) {
+		chan->differential = true;
+		chan->channel = pins[0];
+		chan->channel2 = pins[1];
+		return 0;
+	}
+	ret = fwnode_property_read_u32(child, "single-channel", &pins[0]);
+	if (!ret) {
+		chan->differential = false;
+		chan->channel = pins[0];
+
+		ret = fwnode_property_read_u32(child, "common-mode-channel",
+					       &pins[1]);
+		if (ret)
+			return dev_err_probe(dev, ret,
+				"single-ended channels must define common-mode-channel\n");
+
+		chan->channel2 = pins[1];
+		return 0;
+	}
+	return dev_err_probe(dev, ret,
+		"Channel must define one of diff-channels or single-channel.\n");
+}
+
+static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
+				     struct fwnode_handle *child, int chan_num)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	unsigned int index;
+	struct device *dev = &st->spi->dev;
+	struct ad4170_chan_info *chan_info;
+	struct ad4170_setup *setup;
+	struct iio_chan_spec *chan;
+	u8 ref_select;
+	bool bipolar;
+	int ret;
+
+	ret = fwnode_property_read_u32(child, "reg", &index);
+	if (ret)
+		return ret;
+
+	if (index >= AD4170_MAX_CHANNELS)
+		return dev_err_probe(dev, -EINVAL,
+				     "Channel idx greater than no of channels\n");
+
+	chan = &st->chans[chan_num];
+	chan_info = &st->chans_info[chan_num];
+
+	*chan = ad4170_channel_template;
+	chan->address = chan_num; /* Channel index in ad4170_state chans array */
+	chan->scan_index = index; /* Index to use with the sequencer */
+
+	chan_info->setup_num = AD4170_INVALID_SETUP_SLOT;
+	ret = fwnode_property_read_u32(child, "adi,config-setup-number",
+				       &chan_info->setup_num);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to read adi,config-setup-number\n");
+
+	if (chan_info->setup_num >= AD4170_MAX_SETUPS)
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid adi,config-setup-number: %d\n",
+				     chan_info->setup_num);
+
+	setup = &st->setups[chan_info->setup_num];
+	ret = ad4170_parse_setup(st, child, setup);
+	if (ret)
+		return ret;
+
+	ret = ad4170_parse_channel_type(dev, child, chan);
+	if (ret < 0)
+		return ret;
+
+	bipolar = fwnode_property_read_bool(child, "bipolar");
+	setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar);
+	if (bipolar)
+		chan->scan_type.sign = 's';
+	else
+		chan->scan_type.sign = 'u';
+
+	ref_select = FIELD_GET(AD4170_AFE_REF_SELECT_MSK, setup->afe);
+	ret = ad4170_validate_channel(st, chan);
+	if (ret < 0)
+		return ret;
+
+	ret = ad4170_get_input_range(st, chan, ref_select);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Cannot use reference %u\n",
+				     ref_select);
+
+	chan_info->input_range_uv = ret;
+	return 0;
+}
+
+static int ad4170_parse_channels(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	int chan_num, i, ret;
+
+	chan_num = 0;
+	device_for_each_child_node_scoped(dev, child) {
+		ret = ad4170_parse_channel_node(indio_dev, child, chan_num);
+		if (ret)
+			return ret;
+
+		chan_num++;
+	}
+	for (i = 0; i < AD4170_MAX_CHANNELS; i++)
+		if (st->chans[i].scan_index == 0)
+			break;
+
+	/*
+	 * When more than one channel is enabled, channel 0 must always be used.
+	 */
+	if (chan_num > 1 && i == AD4170_MAX_CHANNELS)
+		return dev_err_probe(dev, -EINVAL,
+				     "Channel 0 must be configured\n");
+
+	indio_dev->channels = st->chans;
+	indio_dev->num_channels = chan_num;
+	return 0;
+}
+
+/*
+ * Parses firmware data describing output current source setup. There are 4
+ * excitation currents (IOUT0 to IOUT3) that can be configured independently.
+ * Excitation currents are added if they are output on the same pin.
+ */
+static int ad4170_parse_exc_current(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	int gpio_number;
+	u16 current_src;
+	u32 pin, val;
+	int i, ret;
+
+	for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++) {
+		/* Parse excitation current output pin properties. */
+		pin = AD4170_CURRENT_IOUT_AIN0;
+		ret = fwnode_property_read_u32(dev->fwnode,
+					       ad4170_i_out_pin_dt_props[i],
+					       &pin);
+		if (!ret) {
+			ret = ad4170_find_table_index(ad4170_iout_pin_tbl, pin);
+			if (ret < 0)
+				return dev_err_probe(dev, ret,
+						     "Invalid %s: %u\n",
+						     ad4170_i_out_pin_dt_props[i],
+						     pin);
+		}
+		current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin);
+
+		/* Parse excitation current value properties. */
+		val = AD4170_I_OUT_0UA;
+		ret = fwnode_property_read_u32(dev->fwnode,
+					       ad4170_i_out_val_dt_props[i],
+					       &val);
+		if (!ret) {
+			ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl,
+						      val);
+			if (ret < 0)
+				return dev_err_probe(dev, ret,
+						     "Invalid %s: %uuA\n",
+						     ad4170_i_out_val_dt_props[i],
+						     val);
+		}
+		current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, val);
+
+		/*
+		 * The excitation currents can be added by outputting them on
+		 * the same pin so don't prevent a pin from being assigned for
+		 * current output multiple times.
+		 */
+		if (pin >= AD4170_CURRENT_IOUT_GPIO0) {
+			gpio_number = pin - AD4170_CURRENT_IOUT_GPIO0;
+			st->gpio_fn[gpio_number] = AD4170_GPIO_AC_EXCITATION;
+		} else if (val > AD4170_I_OUT_0UA) {
+			st->pins_fn[pin] = AD4170_PIN_CURRENT_OUT;
+		}
+
+		ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(i),
+				   current_src);
+		if (ret < 0)
+			return ret;
+
+		current_src = 0; /* Clear temporary variable. */
+	}
+	return 0;
+}
+
+static void ad4170_disable_supplies(void *data)
+{
+	struct ad4170_state *st = data;
+
+	regulator_bulk_disable(ARRAY_SIZE(st->supplies), st->supplies);
+}
+
+static struct ad4170_state *clk_hw_to_ad4170(struct clk_hw *hw)
+{
+	return container_of(hw, struct ad4170_state, int_clk_hw);
+}
+
+static unsigned long ad4170_sel_clk(struct ad4170_state *st,
+				    unsigned int clk_sel)
+{
+	st->clock_ctrl &= ~AD4170_CLOCK_CTRL_CLOCKSEL_MSK;
+	st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, clk_sel);
+	return regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
+}
+
+static unsigned long ad4170_clk_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	return AD4170_INT_FREQ_16MHZ;
+}
+
+static int ad4170_clk_output_is_enabled(struct clk_hw *hw)
+{
+	struct ad4170_state *st = clk_hw_to_ad4170(hw);
+	u32 clk_sel;
+
+	clk_sel = FIELD_GET(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, st->clock_ctrl);
+	return clk_sel == AD4170_INTERNAL_OSC_OUTPUT;
+}
+
+static int ad4170_clk_output_prepare(struct clk_hw *hw)
+{
+	struct ad4170_state *st = clk_hw_to_ad4170(hw);
+
+	return ad4170_sel_clk(st, AD4170_INTERNAL_OSC_OUTPUT);
+}
+
+static void ad4170_clk_output_unprepare(struct clk_hw *hw)
+{
+	struct ad4170_state *st = clk_hw_to_ad4170(hw);
+
+	ad4170_sel_clk(st, AD4170_INTERNAL_OSC);
+}
+
+static const struct clk_ops ad4170_int_clk_ops = {
+	.recalc_rate = ad4170_clk_recalc_rate,
+	.is_enabled = ad4170_clk_output_is_enabled,
+	.prepare = ad4170_clk_output_prepare,
+	.unprepare = ad4170_clk_output_unprepare,
+};
+
+static int ad4170_register_clk_provider(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = indio_dev->dev.parent;
+	struct fwnode_handle *fwnode = dev_fwnode(dev);
+	struct clk_init_data init = {};
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_COMMON_CLK))
+		return 0;
+
+	init.name = fwnode_get_name(fwnode);
+	init.ops = &ad4170_int_clk_ops;
+
+	st->int_clk_hw.init = &init;
+	ret = devm_clk_hw_register(dev, &st->int_clk_hw);
+	if (ret)
+		return ret;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+					   &st->int_clk_hw);
+}
+
+static int ad4170_clock_select(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	int ret;
+
+	st->mclk_hz = AD4170_INT_FREQ_16MHZ;
+	ret = device_property_match_property_string(dev, "clock-names",
+						    ad4170_clk_sel,
+						    ARRAY_SIZE(ad4170_clk_sel));
+	if (ret < 0) {
+		/* Use internal clock reference */
+		st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
+					     AD4170_INTERNAL_OSC_OUTPUT);
+		return ad4170_register_clk_provider(indio_dev);
+	}
+
+	/* Use external clock reference */
+	st->ext_clk = devm_clk_get(dev, ad4170_clk_sel[ret]);
+	if (IS_ERR(st->ext_clk)) {
+		if (PTR_ERR(st->ext_clk) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+
+		return dev_err_probe(dev, PTR_ERR(st->ext_clk),
+				     "Failed to get external clock\n");
+	}
+	st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
+				     AD4170_EXTERNAL_OSC + ret);
+
+	ret = clk_prepare_enable(st->ext_clk);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to enable external clock\n");
+
+	ret = devm_add_action_or_reset(dev, ad4170_clk_disable_unprepare,
+				       st->ext_clk);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to add clock unwind action\n");
+
+	st->mclk_hz = clk_get_rate(st->ext_clk);
+	if (st->mclk_hz < AD4170_EXT_FREQ_MHZ_MIN ||
+	    st->mclk_hz > AD4170_EXT_FREQ_MHZ_MAX) {
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid external clock frequency %u\n",
+				     st->mclk_hz);
+	}
+	return 0;
+}
+
+static int ad4170_parse_pw_switch(struct ad4170_state *st, struct device *dev)
+{
+	bool pdsw0, pdsw1;
+
+	pdsw0 = device_property_read_bool(dev, "adi,gpio0-power-down-switch");
+	pdsw1 = device_property_read_bool(dev, "adi,gpio1-power-down-switch");
+
+	/*
+	 * Error if a GPIO is assigned to be both excitation current output and
+	 * power switch.
+	 */
+	if (pdsw0) {
+		if (st->gpio_fn[0] != AD4170_GPIO_UNASIGNED)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "GPIO 0 already used with fn %u\n",
+					     st->gpio_fn[0]);
+
+		st->gpio_fn[0] = AD4170_GPIO_PW_DOW_SWITCH;
+	}
+	if (pdsw1) {
+		if (st->gpio_fn[1] != AD4170_GPIO_UNASIGNED)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "GPIO 1 already used with fn %u\n",
+					     st->gpio_fn[1]);
+
+		st->gpio_fn[1] = AD4170_GPIO_PW_DOW_SWITCH;
+	}
+	return regmap_update_bits(st->regmap, AD4170_POWER_DOWN_SW_REG,
+				 AD4170_POWER_DOWN_SW_PDSW0_MSK |
+				 AD4170_POWER_DOWN_SW_PDSW1_MSK,
+				 pdsw0 | pdsw1);
+}
+
+static int ad4170_parse_vbias(struct ad4170_state *st, struct device *dev)
+{
+	u32 vbias_pins[AD4170_MAX_ANALOG_PINS];
+	unsigned int i, val;
+	u32 num_vbias_pins;
+	int ret;
+
+	ret = device_property_count_u32(dev, "adi,vbias-pins");
+	if (ret > 0) {
+		if (ret > AD4170_MAX_ANALOG_PINS)
+			return dev_err_probe(dev, -EINVAL,
+					     "Too many vbias pins %u\n", ret);
+
+		num_vbias_pins = ret;
+		ret = device_property_read_u32_array(dev, "adi,vbias-pins",
+						     vbias_pins,
+						     num_vbias_pins);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to read vbias pins\n");
+	}
+	for (i = 0; i < num_vbias_pins; i++) {
+		if (st->pins_fn[vbias_pins[i]] != AD4170_PIN_UNASIGNED)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "AIN%d already used with fn %u\n",
+					     vbias_pins[i], st->pins_fn[i]);
+
+		val |= BIT(vbias_pins[i]);
+		st->pins_fn[vbias_pins[i]] = AD4170_PIN_VBIAS;
+	}
+	return regmap_write(st->regmap, AD4170_V_BIAS_REG, val);
+}
+
+static int ad4170_parse_firmware(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	int ret, i;
+	u8 aux;
+
+	ret = ad4170_clock_select(indio_dev);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to setup device clock\n");
+
+	ret = regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
+	if (ret < 0)
+		return ret;
+
+	ret = ad4170_parse_pin_muxing(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
+		st->pins_fn[i] = AD4170_PIN_UNASIGNED;
+
+	for (i = 0; i < AD4170_NUM_GPIO_PINS; i++)
+		st->gpio_fn[i] = AD4170_GPIO_UNASIGNED;
+
+	ret = ad4170_parse_exc_current(indio_dev);
+	if (ret)
+		return ret;
+// write to misc reg must be delaied because misc register contains settings
+// that are channel specific.
+
+	ret = ad4170_parse_pw_switch(st, dev);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to config power down switches\n");
+
+	ret = ad4170_parse_vbias(st, dev);
+	if (ret < 0)
+		return ret;
+
+	ret = ad4170_parse_channels(indio_dev);
+	if (ret)
+		return ret;
+
+	aux = AD4170_MISC_CHOP_IEXC_OFF;
+	ret = device_property_read_u8(dev, "adi,chop-iexc", &aux);
+	if (!ret) {
+		ret = ad4170_find_table_index(ad4170_iexc_chop_tbl, aux);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "Invalid adi,chop-iexc config: %u\n",
+					      aux);
+	}
+
+	/* Set excitation current chop config to first channel setup config */
+	st->setups[indio_dev->channels[0].address].misc |=
+		FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK, aux);
+	return 0;
+}
+
+static int ad4170_initial_config(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info;
+	struct iio_chan_spec const *chan;
+	struct ad4170_setup *setup;
+	unsigned int val;
+	unsigned int i;
+	int ret;
+
+	ad4170_fill_sps_tbl(st);
+
+	/* Put ADC in IDLE mode */
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+	if (ret)
+		return ret;
+
+	/* Setup channels. */
+	for (i = 0; i < indio_dev->num_channels; i++) {
+		chan = &indio_dev->channels[i];
+		chan_info = &st->chans_info[chan->address];
+		setup = &st->setups[chan_info->setup_num];
+
+		ret = regmap_update_bits(st->regmap,
+					 AD4170_CHAN_SETUP_REG(chan->address),
+					 AD4170_CHANNEL_SETUP_SETUP_MSK,
+					 FIELD_PREP(AD4170_CHANNEL_SETUP_SETUP_MSK,
+						    chan_info->setup_num));
+		if (ret)
+			return ret;
+
+		setup->gain = AD4170_GAIN_REG_DEFAULT;
+		ret = ad4170_write_setup(st, chan_info->setup_num, setup);
+		if (ret)
+			return ret;
+
+		val = FIELD_PREP(AD4170_CHANNEL_MAPN_AINP_MSK, chan->channel) |
+		      FIELD_PREP(AD4170_CHANNEL_MAPN_AINM_MSK, chan->channel2);
+
+		ret = regmap_write(st->regmap, AD4170_CHAN_MAP_REG(i), val);
+		if (ret < 0)
+			return ret;
+
+		ret = ad4170_set_channel_freq(st, chan->address,
+					      AD4170_MAX_SAMP_RATE, 0);
+		if (ret)
+			return ret;
+
+		ret = ad4170_fill_scale_tbl(indio_dev, i);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_write(st->regmap, AD4170_CHANNEL_EN_REG, 0);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Configure channels to share the same data output register, i.e. data
+	 * can be read from the same register address regardless of channel
+	 * number.
+	 */
+	ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				 AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK,
+				 AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct iio_trigger_ops ad4170_trigger_ops = {
+	.validate_device = iio_trigger_validate_own_device,
+};
+
+static irqreturn_t ad4170_interrupt(int irq, void *dev_id)
+{
+	struct iio_dev *indio_dev = dev_id;
+	struct ad4170_state *st = iio_priv(indio_dev);
+
+	if (iio_buffer_enabled(indio_dev))
+		iio_trigger_poll(st->trig);
+	else
+		complete(&st->completion);
+
+	return IRQ_HANDLED;
+};
+
+static void ad4170_prepare_message(struct ad4170_state *st)
+{
+	/*
+	 * Continuous data register read is enabled on buffer postenable so
+	 * no instruction phase is needed meaning we don't need to send the
+	 * register address to read data. Transfer only needs the read buffer.
+	 */
+	st->xfer.rx_buf = &st->reg_read_rx_buf;
+	st->xfer.bits_per_word = ad4170_channel_template.scan_type.storagebits;
+	st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.storagebits);
+
+	spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
+}
+
+static int ad4170_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_CONT);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				 AD4170_REG_CTRL_CONT_READ_MSK,
+				 FIELD_PREP(AD4170_REG_CTRL_CONT_READ_MSK,
+					    AD4170_ADC_CTRL_CONT_READ_ENABLE));
+
+	if (ret < 0)
+		return ret;
+
+	return spi_optimize_message(st->spi, &st->msg);
+}
+
+static int ad4170_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret, i;
+
+	spi_unoptimize_message(&st->msg);
+
+	for (i = 0; i < indio_dev->num_channels; i++) {
+		ret = ad4170_set_channel_enable(st, i, false);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				 AD4170_REG_CTRL_CONT_READ_MSK,
+				 FIELD_PREP(AD4170_REG_CTRL_CONT_READ_MSK,
+					    AD4170_ADC_CTRL_CONT_READ_DISABLE));
+	if (ret < 0)
+		return ret;
+
+	return ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+}
+
+static const struct iio_buffer_setup_ops ad4170_buffer_ops = {
+	.postenable = ad4170_buffer_postenable,
+	.predisable = ad4170_buffer_predisable,
+};
+
+static irqreturn_t ad4170_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret, i = 0;
+	int scan_index;
+
+	for_each_set_bit(scan_index, indio_dev->active_scan_mask,
+			 indio_dev->masklength) {
+		/* Read register data */
+		ret = regmap_read(st->regmap, AD4170_DATA_24b_REG, &st->data[i]);
+		if (ret)
+			goto out;
+		i++;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, &st->data,
+					   iio_get_time_ns(indio_dev));
+out:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int ad4170_triggered_buffer_setup(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;
+
+	indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+
+	st->trig = devm_iio_trigger_alloc(indio_dev->dev.parent, "%s-trig%d",
+					  indio_dev->name,
+					  iio_device_id(indio_dev));
+	if (!st->trig)
+		return -ENOMEM;
+
+	st->trig->ops = &ad4170_trigger_ops;
+	st->trig->dev.parent = indio_dev->dev.parent;
+
+	iio_trigger_set_drvdata(st->trig, indio_dev);
+	ret = devm_iio_trigger_register(indio_dev->dev.parent, st->trig);
+	if (ret)
+		return ret;
+
+	indio_dev->trig = iio_trigger_get(st->trig);
+
+	ret = request_irq(st->spi->irq,
+			  &ad4170_interrupt,
+			  IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			  indio_dev->name, indio_dev);
+	if (ret)
+		return ret;
+
+	return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev,
+					       &iio_pollfunc_store_time,
+					       &ad4170_trigger_handler,
+					       &ad4170_buffer_ops);
+}
+
+static int ad4170_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct ad4170_state *st;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	devm_mutex_init(dev, &st->lock);
+
+	indio_dev->name = AD4170_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &ad4170_info;
+
+	st->spi = spi;
+	st->regmap = devm_regmap_init(dev, NULL, st, &ad4170_regmap_config);
+
+	st->supplies[AD4170_AVDD_SUP].supply = "avdd";
+	st->supplies[AD4170_AVSS_SUP].supply = "avss";
+	st->supplies[AD4170_IOVDD_SUP].supply = "iovdd";
+	st->supplies[AD4170_REFIN1P_SUP].supply = "refin1p";
+	st->supplies[AD4170_REFIN1N_SUP].supply = "refin1n";
+	st->supplies[AD4170_REFIN2P_SUP].supply = "refin2p";
+	st->supplies[AD4170_REFIN2N_SUP].supply = "refin2n";
+
+	/*
+	 * If a regulator is not available, it will be set to a dummy regulator.
+	 * Each channel reference is checked with regulator_get_voltage() before
+	 * setting attributes so if any channel uses a dummy supply the driver
+	 * probe will fail.
+	 */
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->supplies),
+				      st->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get supplies\n");
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(st->supplies), st->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable supplies\n");
+
+	ret = devm_add_action_or_reset(dev, ad4170_disable_supplies, st);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to add supplies disable action\n");
+
+	ret = ad4170_soft_reset(st);
+	if (ret)
+		return ret;
+
+	ret = ad4170_parse_firmware(indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to parse firmware\n");
+
+	ret = ad4170_initial_config(indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to setup device\n");
+
+	init_completion(&st->completion);
+
+	ret = ad4170_triggered_buffer_setup(indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to setup read buffer\n");
+
+	ad4170_prepare_message(st);
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4170_of_match[] = {
+	{
+		.compatible = "adi,ad4170",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad4170_of_match);
+
+static struct spi_driver ad4170_driver = {
+	.driver = {
+		.name = AD4170_NAME,
+		.of_match_table = ad4170_of_match,
+	},
+	.probe = ad4170_probe,
+};
+module_spi_driver(ad4170_driver);
+
+MODULE_AUTHOR("Ana-Maria Cusco <ana-maria.cusco@xxxxxxxxxx>");
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Analog Devices AD4170 SPI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ad4170.h b/drivers/iio/adc/ad4170.h
new file mode 100644
index 000000000000..5b24788314b1
--- /dev/null
+++ b/drivers/iio/adc/ad4170.h
@@ -0,0 +1,316 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AD4170 ADC driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <dt-bindings/iio/adc/adi,ad4170.h>
+
+#define AD4170_NAME				"ad4170"
+
+#define AD4170_READ_MASK BIT(14)
+/* AD4170 registers */
+#define AD4170_INTERFACE_CONFIG_A_REG		0x00
+#define AD4170_STATUS_REG			0x14
+#define AD4170_DATA_24b_REG			0x1c
+#define AD4170_PIN_MUXING_REG			0x68
+#define AD4170_CLOCK_CTRL_REG			0x6A
+#define AD4170_POWER_DOWN_SW_REG		0x6e
+#define AD4170_ADC_CTRL_REG			0x70
+#define AD4170_CHANNEL_EN_REG			0x78
+#define AD4170_CHAN_SETUP_REG(x)		(0x80 + 4 * (x))
+#define AD4170_CHAN_MAP_REG(x)			(0x82 + 4 * (x))
+#define AD4170_MISC_REG(x)			(0xc0 + 14 * (x))
+#define AD4170_AFE_REG(x)			(0xc2 + 14 * (x))
+#define AD4170_FILTER_REG(x)			(0xc4 + 14 * (x))
+#define AD4170_FILTER_FS_REG(x)			(0xc6 + 14 * (x))
+#define AD4170_OFFSET_REG(x)			(0xc8 + 14 * (x))
+#define AD4170_GAIN_REG(x)			(0xcb + 14 * (x))
+#define AD4170_V_BIAS_REG			0x134
+#define AD4170_CURRENT_SRC_REG(x)		(0x138 + 2 * (x))
+
+/* AD4170_REG_INTERFACE_CONFIG_A */
+#define AD4170_SW_RESET_MSK		(BIT(7) | BIT(0))
+#define AD4170_IFACE_CONFIG_ADDR_ASCENSION_MSK	BIT(5)
+#define AD4170_RESET_SLEEP_US	        1000
+#define AD4170_INT_REF_2_5V		2500000
+
+/* AD4170_REG_PIN_MUXING */
+#define AD4170_PIN_MUXING_DIG_AUX2_CTRL_MSK		GENMASK(7, 6)
+#define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK		GENMASK(5, 4)
+#define AD4170_PIN_MUXING_SYNC_CTRL_MSK			GENMASK(3, 2)
+
+/* AD4170_REG_CLOCK_CTRL */
+#define AD4170_INT_FREQ_16MHZ				16000000
+#define AD4170_EXT_FREQ_MHZ_MIN				1000000
+#define AD4170_EXT_FREQ_MHZ_MAX				17000000
+#define AD4170_CLOCK_CTRL_CLOCKSEL_MSK			GENMASK(1, 0)
+
+#define AD4170_INTERNAL_OSC				0x0
+#define AD4170_INTERNAL_OSC_OUTPUT			0x1
+#define AD4170_EXTERNAL_OSC				0x2
+#define AD4170_EXTERNAL_XTAL				0x3
+
+/* AD4170_REG_POWER_DOWN_SW */
+#define AD4170_POWER_DOWN_SW_PDSW1_MSK				BIT(1)
+#define AD4170_POWER_DOWN_SW_PDSW0_MSK				BIT(0)
+
+/* AD4170_REG_ADC_CTRL */
+#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK			BIT(7)
+#define AD4170_REG_CTRL_CONT_READ_MSK				GENMASK(5, 4)
+#define AD4170_REG_CTRL_MODE_MSK				GENMASK(3, 0)
+
+/* AD4170_REG_CHANNEL_EN */
+#define AD4170_CHANNEL_EN(ch)					BIT(ch)
+
+/* AD4170_REG_ADC_CHANNEL_SETUP */
+#define AD4170_CHANNEL_SETUP_SETUP_MSK				GENMASK(2, 0)
+
+/* AD4170_REG_ADC_CHANNEL_MAP */
+#define AD4170_CHANNEL_MAPN_AINP_MSK				GENMASK(12, 8)
+#define AD4170_CHANNEL_MAPN_AINM_MSK				GENMASK(4, 0)
+
+/* AD4170_REG_ADC_SETUPS_MISC */
+#define AD4170_MISC_CHOP_IEXC_MSK				GENMASK(15, 14)
+#define AD4170_MISC_CHOP_ADC_MSK				GENMASK(9, 8)
+#define AD4170_MISC_BURNOUT_MSK					GENMASK(1, 0)
+
+/* AD4170_REG_ADC_SETUPS_AFE */
+#define AD4170_AFE_REF_BUF_M_MSK				GENMASK(11, 10)
+#define AD4170_AFE_REF_BUF_P_MSK				GENMASK(9, 8)
+#define AD4170_AFE_REF_SELECT_MSK				GENMASK(6, 5)
+#define AD4170_AFE_BIPOLAR_MSK					BIT(4)
+#define AD4170_AFE_PGA_GAIN_MSK					GENMASK(3, 0)
+
+/* AD4170_REG_ADC_SETUPS_FILTER */
+#define AD4170_SETUPS_POST_FILTER_SEL_MSK			GENMASK(7, 4)
+#define AD4170_SETUPS_FILTER_TYPE_MSK				GENMASK(3, 0)
+
+/* AD4170 REG_OFFSET*/
+#define AD4170_OFFSET_MSK					GENMASK(23, 0)
+
+/* AD4170 REG_GAIN*/
+#define AD4170_GAIN_MSK						GENMASK(23, 0)
+#define AD4170_GAIN_REG_DEFAULT					0x555555
+
+/* AD4170_REG_CURRENT_SOURCE */
+#define AD4170_CURRENT_SRC_I_OUT_PIN_MSK			GENMASK(12, 8)
+#define AD4170_CURRENT_SRC_I_OUT_VAL_MSK			GENMASK(2, 0)
+
+/* AD4170_REG_FIR_CONTROL */
+#define AD4170_FIR_CONTROL_IIR_MODE_MSK				BIT(15)
+
+#define AD4170_MAX_CHANNELS				16
+#define AD4170_MAX_ANALOG_PINS				8
+#define AD4170_MAX_SETUPS				8
+#define AD4170_INVALID_SETUP_SLOT			9
+#define AD4170_NUM_CURRENT_SRC				4
+#define AD4170_MAX_SAMP_RATE				125000
+
+#define AD4170_NUM_ANALOG_PINS				9
+#define AD4170_NUM_GPIO_PINS				4
+
+#define AD4170_DIG_AUX1_DISABLED	0
+#define AD4170_DIG_AUX1_RDY		1
+#define AD4170_DIG_AUX1_SYNC		2
+
+#define AD4170_DIG_AUX2_DISABLED	0
+#define AD4170_DIG_AUX2_LDAC		1
+#define AD4170_DIG_AUX2_SYNC		2
+
+#define AD4170_SYNC_DISABLED		0
+#define AD4170_SYNC_STANDARD		1
+#define AD4170_SYNC_ALTERNATE		2
+
+#define AD4170_I_OUT_0UA		0
+#define AD4170_I_OUT_10UA		10
+#define AD4170_I_OUT_50UA		50
+#define AD4170_I_OUT_100UA		100
+#define AD4170_I_OUT_250UA		250
+#define AD4170_I_OUT_500UA		500
+#define AD4170_I_OUT_1000UA		1000
+#define AD4170_I_OUT_1500UA		1500
+
+#define AD4170_BURNOUT_OFF		0
+#define AD4170_BURNOUT_100NA		100
+#define AD4170_BURNOUT_2000NA		2000
+#define AD4170_BURNOUT_10000NA		10000
+
+#define AD4170_PGA_OPTIONS		10
+
+enum ad4170_pin_function {
+	AD4170_PIN_UNASIGNED,
+	AD4170_PIN_ANALOG_IN,
+	AD4170_PIN_CURRENT_OUT,
+	AD4170_PIN_VBIAS
+};
+
+enum ad4170_gpio_function {
+	AD4170_GPIO_UNASIGNED,
+	AD4170_GPIO_PW_DOW_SWITCH,
+	AD4170_GPIO_AC_EXCITATION,
+	AD4170_GPIO_OUTPUT,
+	AD4170_GPIO_CHANNEL,
+};
+
+#define AD4170_ADC_CTRL_CONT_READ_DISABLE		0
+#define AD4170_ADC_CTRL_CONT_READ_ENABLE		1
+//#define AD4170_ADC_CTRL_CONT_READ_TRANSMIT		2
+
+#define AD4170_ADC_CTRL_MODE_CONT			0
+#define AD4170_ADC_CTRL_MODE_SINGLE			4
+#define AD4170_ADC_CTRL_MODE_IDLE			7
+
+/**
+ * @enum ad4170_ref_buf
+ * @brief REFIN Buffer Enable.
+ */
+enum ad4170_ref_buf {
+	/** Pre-charge Buffer. */
+	AD4170_REF_BUF_PRE,
+	/** Full Buffer.*/
+	AD4170_REF_BUF_FULL,
+	/** Bypass */
+	AD4170_REF_BUF_BYPASS
+};
+
+/**
+ * @enum ad4170_filter_type
+ * @brief Filter Mode for Sinc-Based Filters.
+ */
+enum ad4170_filter_type {
+	AD4170_SINC5_AVG,
+	AD4170_SINC5,
+	AD4170_SINC3,
+};
+
+/* ADC Register Lengths */
+static const unsigned int ad4170_reg_size[] = {
+	[AD4170_INTERFACE_CONFIG_A_REG] = 1,
+	[AD4170_STATUS_REG]	= 2,
+	[AD4170_DATA_24b_REG]	= 3,
+	[AD4170_PIN_MUXING_REG]	= 2,
+	[AD4170_CLOCK_CTRL_REG]	= 2,
+	[AD4170_POWER_DOWN_SW_REG]	= 2,
+	[AD4170_ADC_CTRL_REG]	= 2,
+	[AD4170_CHANNEL_EN_REG]	= 2,
+	/*
+	 * CHANNEL_SETUP and CHANNEL_MAP register are all 2 byte size each and
+	 * their addresses are interleaved such that we have CHANNEL_SETUP0
+	 * address followed by CHANNEL_MAP0 address, followed by CHANNEL_SETUP1,
+	 * and so on until CHANNEL_MAP15.
+	 * Thus, initialize the register size for them only once.
+	 */
+	[AD4170_CHAN_SETUP_REG(0) ... AD4170_CHAN_MAP_REG(AD4170_MAX_CHANNELS - 1)] = 2,
+	/*
+	 * MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN register addresses are
+	 * also interleaved but MISC, AFE, FILTER, FILTER_FS, OFFSET are 16-bit
+	 * while OFFSET, GAIN are 24-bit registers so we can't init them all to
+	 * the same size.
+	 */
+	/* Init MISC register size */
+	[AD4170_MISC_REG(0)] = 2,
+	[AD4170_MISC_REG(1)] = 2,
+	[AD4170_MISC_REG(2)] = 2,
+	[AD4170_MISC_REG(3)] = 2,
+	[AD4170_MISC_REG(4)] = 2,
+	[AD4170_MISC_REG(5)] = 2,
+	[AD4170_MISC_REG(6)] = 2,
+	[AD4170_MISC_REG(7)] = 2,
+	/* Init AFE register size */
+	[AD4170_AFE_REG(0)] = 2,
+	[AD4170_AFE_REG(1)] = 2,
+	[AD4170_AFE_REG(2)] = 2,
+	[AD4170_AFE_REG(3)] = 2,
+	[AD4170_AFE_REG(4)] = 2,
+	[AD4170_AFE_REG(5)] = 2,
+	[AD4170_AFE_REG(6)] = 2,
+	[AD4170_AFE_REG(7)] = 2,
+	/* Init FILTER register size */
+	[AD4170_FILTER_REG(0)]	= 2,
+	[AD4170_FILTER_REG(1)]	= 2,
+	[AD4170_FILTER_REG(2)]	= 2,
+	[AD4170_FILTER_REG(3)]	= 2,
+	[AD4170_FILTER_REG(4)]	= 2,
+	[AD4170_FILTER_REG(5)]	= 2,
+	[AD4170_FILTER_REG(6)]	= 2,
+	[AD4170_FILTER_REG(7)]	= 2,
+	/* Init FILTER_FS register size */
+	[AD4170_FILTER_FS_REG(0)]	= 2,
+	[AD4170_FILTER_FS_REG(1)]	= 2,
+	[AD4170_FILTER_FS_REG(2)]	= 2,
+	[AD4170_FILTER_FS_REG(3)]	= 2,
+	[AD4170_FILTER_FS_REG(4)]	= 2,
+	[AD4170_FILTER_FS_REG(5)]	= 2,
+	[AD4170_FILTER_FS_REG(6)]	= 2,
+	[AD4170_FILTER_FS_REG(7)]	= 2,
+	/* Init OFFSET register size */
+	[AD4170_OFFSET_REG(0)]	= 3,
+	[AD4170_OFFSET_REG(1)]	= 3,
+	[AD4170_OFFSET_REG(2)]	= 3,
+	[AD4170_OFFSET_REG(3)]	= 3,
+	[AD4170_OFFSET_REG(4)]	= 3,
+	[AD4170_OFFSET_REG(5)]	= 3,
+	[AD4170_OFFSET_REG(6)]	= 3,
+	[AD4170_OFFSET_REG(7)]	= 3,
+	/* Init GAIN register size */
+	[AD4170_GAIN_REG(0)]	= 3,
+	[AD4170_GAIN_REG(1)]	= 3,
+	[AD4170_GAIN_REG(2)]	= 3,
+	[AD4170_GAIN_REG(3)]	= 3,
+	[AD4170_GAIN_REG(4)]	= 3,
+	[AD4170_GAIN_REG(5)]	= 3,
+	[AD4170_GAIN_REG(6)]	= 3,
+	[AD4170_GAIN_REG(7)]	= 3,
+	[AD4170_V_BIAS_REG]	= 2,
+	[AD4170_CURRENT_SRC_REG(0) ... AD4170_CURRENT_SRC_REG(AD4170_NUM_CURRENT_SRC - 1)] = 2,
+};
+
+static const unsigned int ad4170_chop_adc_tbl[] = {
+	AD4170_MISC_CHOP_ADC_OFF,
+	AD4170_MISC_CHOP_ADC_MUX,
+	AD4170_MISC_CHOP_ADC_ACX_4PIN,
+	AD4170_MISC_CHOP_ADC_ACX_2PIN
+};
+
+static const unsigned int ad4170_iexc_chop_tbl[] = {
+	AD4170_MISC_CHOP_IEXC_OFF,
+	AD4170_MISC_CHOP_IEXC_AB,
+	AD4170_MISC_CHOP_IEXC_CD,
+	AD4170_MISC_CHOP_IEXC_ABCD,
+};
+
+static const unsigned int ad4170_iout_pin_tbl[] = {
+	AD4170_CURRENT_IOUT_AIN0,
+	AD4170_CURRENT_IOUT_AIN1,
+	AD4170_CURRENT_IOUT_AIN2,
+	AD4170_CURRENT_IOUT_AIN3,
+	AD4170_CURRENT_IOUT_AIN4,
+	AD4170_CURRENT_IOUT_AIN5,
+	AD4170_CURRENT_IOUT_AIN6,
+	AD4170_CURRENT_IOUT_AIN7,
+	AD4170_CURRENT_IOUT_AIN8,
+	AD4170_CURRENT_IOUT_GPIO0,
+	AD4170_CURRENT_IOUT_GPIO1,
+	AD4170_CURRENT_IOUT_GPIO2,
+	AD4170_CURRENT_IOUT_GPIO3,
+};
+
+static const unsigned int ad4170_iout_current_ua_tbl[] = {
+	AD4170_I_OUT_0UA,
+	AD4170_I_OUT_10UA,
+	AD4170_I_OUT_50UA,
+	AD4170_I_OUT_100UA,
+	AD4170_I_OUT_250UA,
+	AD4170_I_OUT_500UA,
+	AD4170_I_OUT_1000UA,
+	AD4170_I_OUT_1500UA,
+};
+
+static const unsigned int ad4170_burnout_current_na_tbl[] = {
+	AD4170_BURNOUT_OFF,
+	AD4170_BURNOUT_100NA,
+	AD4170_BURNOUT_2000NA,
+	AD4170_BURNOUT_10000NA,
+};
-- 
2.45.2





[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux