summaryrefslogtreecommitdiffstats
path: root/drivers/iio/addac
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2026-09-02 09:55:01 -0400
committerSteven Rostedt <rostedt@goodmis.org>2026-09-02 11:08:21 -0400
commit4617721c502b2ddaa4e324e86da4997edf738fa5 (patch)
tree350aa83b47da0b574a814795392b20b0bed83cbc /drivers/iio/addac
downloadlinux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz
linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/iio/addac')
-rw-r--r--drivers/iio/addac/Kconfig55
-rw-r--r--drivers/iio/addac/Makefile9
-rw-r--r--drivers/iio/addac/ad74115.c1933
-rw-r--r--drivers/iio/addac/ad74413r.c1531
-rw-r--r--drivers/iio/addac/stx104.c523
5 files changed, 4051 insertions, 0 deletions
diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig
new file mode 100644
index 000000000..16876b8b5
--- /dev/null
+++ b/drivers/iio/addac/Kconfig
@@ -0,0 +1,55 @@
+#
+# ADC DAC drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Analog to digital and digital to analog converters"
+
+config AD74115
+ tristate "Analog Devices AD74115H driver"
+ depends on GPIOLIB && SPI
+ select CRC8
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices AD74115H
+ single-channel software configurable input/output solution.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad74115.
+
+config AD74413R
+ tristate "Analog Devices AD74412R/AD74413R driver"
+ depends on GPIOLIB && SPI
+ select REGMAP_SPI
+ select CRC8
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for Analog Devices AD74412R/AD74413R
+ quad-channel software configurable input/output solution.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad74413r.
+
+config STX104
+ tristate "Apex Embedded Systems STX104 driver"
+ depends on PC104 && X86
+ select ISA_BUS_API
+ select REGMAP_MMIO
+ select GPIOLIB
+ select GPIO_REGMAP
+ select I8254
+ help
+ Say yes here to build support for the Apex Embedded Systems STX104
+ integrated analog PC/104 card.
+
+ This driver supports the 16 channels of single-ended (8 channels of
+ differential) analog inputs, 2 channels of analog output, 4 digital
+ inputs, and 4 digital outputs provided by the STX104.
+
+ The base port addresses for the devices may be configured via the base
+ array module parameter.
+
+endmenu
diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile
new file mode 100644
index 000000000..577777276
--- /dev/null
+++ b/drivers/iio/addac/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for industrial I/O ADDAC drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AD74115) += ad74115.o
+obj-$(CONFIG_AD74413R) += ad74413r.o
+obj-$(CONFIG_STX104) += stx104.o
diff --git a/drivers/iio/addac/ad74115.c b/drivers/iio/addac/ad74115.c
new file mode 100644
index 000000000..9b9d7a12c
--- /dev/null
+++ b/drivers/iio/addac/ad74115.c
@@ -0,0 +1,1933 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/crc8.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+
+#include <linux/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define AD74115_NAME "ad74115"
+
+#define AD74115_CH_FUNC_SETUP_REG 0x01
+
+#define AD74115_ADC_CONFIG_REG 0x02
+#define AD74115_ADC_CONFIG_CONV2_RATE_MASK GENMASK(15, 13)
+#define AD74115_ADC_CONFIG_CONV1_RATE_MASK GENMASK(12, 10)
+#define AD74115_ADC_CONFIG_CONV2_RANGE_MASK GENMASK(9, 7)
+#define AD74115_ADC_CONFIG_CONV1_RANGE_MASK GENMASK(6, 4)
+
+#define AD74115_PWR_OPTIM_CONFIG_REG 0x03
+
+#define AD74115_DIN_CONFIG1_REG 0x04
+#define AD74115_DIN_COMPARATOR_EN_MASK BIT(13)
+#define AD74115_DIN_SINK_MASK GENMASK(11, 7)
+#define AD74115_DIN_DEBOUNCE_MASK GENMASK(4, 0)
+
+#define AD74115_DIN_CONFIG2_REG 0x05
+#define AD74115_COMP_THRESH_MASK GENMASK(6, 0)
+
+#define AD74115_OUTPUT_CONFIG_REG 0x06
+#define AD74115_OUTPUT_SLEW_EN_MASK GENMASK(6, 5)
+#define AD74115_OUTPUT_SLEW_LIN_STEP_MASK GENMASK(4, 3)
+#define AD74115_OUTPUT_SLEW_LIN_RATE_MASK GENMASK(2, 1)
+
+#define AD74115_RTD3W4W_CONFIG_REG 0x07
+
+#define AD74115_BURNOUT_CONFIG_REG 0x0a
+#define AD74115_BURNOUT_EXT2_EN_MASK BIT(10)
+#define AD74115_BURNOUT_EXT1_EN_MASK BIT(5)
+#define AD74115_BURNOUT_VIOUT_EN_MASK BIT(0)
+
+#define AD74115_DAC_CODE_REG 0x0b
+
+#define AD74115_DAC_ACTIVE_REG 0x0d
+
+#define AD74115_GPIO_CONFIG_X_REG(x) (0x35 + (x))
+#define AD74115_GPIO_CONFIG_GPI_DATA BIT(5)
+#define AD74115_GPIO_CONFIG_GPO_DATA BIT(4)
+#define AD74115_GPIO_CONFIG_SELECT_MASK GENMASK(2, 0)
+
+#define AD74115_CHARGE_PUMP_REG 0x3a
+
+#define AD74115_ADC_CONV_CTRL_REG 0x3b
+#define AD74115_ADC_CONV_SEQ_MASK GENMASK(13, 12)
+
+#define AD74115_DIN_COMP_OUT_REG 0x40
+
+#define AD74115_LIVE_STATUS_REG 0x42
+#define AD74115_ADC_DATA_RDY_MASK BIT(3)
+
+#define AD74115_READ_SELECT_REG 0x64
+
+#define AD74115_CMD_KEY_REG 0x78
+#define AD74115_CMD_KEY_RESET1 0x15fa
+#define AD74115_CMD_KEY_RESET2 0xaf51
+
+#define AD74115_CRC_POLYNOMIAL 0x7
+DECLARE_CRC8_TABLE(ad74115_crc8_table);
+
+#define AD74115_ADC_CODE_MAX ((int)GENMASK(15, 0))
+#define AD74115_ADC_CODE_HALF (AD74115_ADC_CODE_MAX / 2)
+
+#define AD74115_DAC_VOLTAGE_MAX 12000
+#define AD74115_DAC_CURRENT_MAX 25
+#define AD74115_DAC_CODE_MAX ((int)GENMASK(13, 0))
+#define AD74115_DAC_CODE_HALF (AD74115_DAC_CODE_MAX / 2)
+
+#define AD74115_COMP_THRESH_MAX 98
+
+#define AD74115_SENSE_RESISTOR_OHMS 100
+#define AD74115_REF_RESISTOR_OHMS 2100
+
+#define AD74115_DIN_SINK_LOW_STEP 120
+#define AD74115_DIN_SINK_HIGH_STEP 240
+#define AD74115_DIN_SINK_MAX 31
+
+#define AD74115_FRAME_SIZE 4
+#define AD74115_GPIO_NUM 4
+
+#define AD74115_CONV_TIME_US 1000000
+
+enum ad74115_dac_ch {
+ AD74115_DAC_CH_MAIN,
+ AD74115_DAC_CH_COMPARATOR,
+};
+
+enum ad74115_adc_ch {
+ AD74115_ADC_CH_CONV1,
+ AD74115_ADC_CH_CONV2,
+ AD74115_ADC_CH_NUM
+};
+
+enum ad74115_ch_func {
+ AD74115_CH_FUNC_HIGH_IMPEDANCE,
+ AD74115_CH_FUNC_VOLTAGE_OUTPUT,
+ AD74115_CH_FUNC_CURRENT_OUTPUT,
+ AD74115_CH_FUNC_VOLTAGE_INPUT,
+ AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER,
+ AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER,
+ AD74115_CH_FUNC_2_WIRE_RESISTANCE_INPUT,
+ AD74115_CH_FUNC_3_4_WIRE_RESISTANCE_INPUT,
+ AD74115_CH_FUNC_DIGITAL_INPUT_LOGIC,
+ AD74115_CH_FUNC_DIGITAL_INPUT_LOOP_POWER,
+ AD74115_CH_FUNC_CURRENT_OUTPUT_HART,
+ AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER_HART,
+ AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART,
+ AD74115_CH_FUNC_MAX = AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART,
+ AD74115_CH_FUNC_NUM
+};
+
+enum ad74115_adc_range {
+ AD74115_ADC_RANGE_12V,
+ AD74115_ADC_RANGE_12V_BIPOLAR,
+ AD74115_ADC_RANGE_2_5V_BIPOLAR,
+ AD74115_ADC_RANGE_2_5V_NEG,
+ AD74115_ADC_RANGE_2_5V,
+ AD74115_ADC_RANGE_0_625V,
+ AD74115_ADC_RANGE_104MV_BIPOLAR,
+ AD74115_ADC_RANGE_12V_OTHER,
+ AD74115_ADC_RANGE_MAX = AD74115_ADC_RANGE_12V_OTHER,
+ AD74115_ADC_RANGE_NUM
+};
+
+enum ad74115_adc_conv_seq {
+ AD74115_ADC_CONV_SEQ_STANDBY = 0b00,
+ AD74115_ADC_CONV_SEQ_SINGLE = 0b01,
+ AD74115_ADC_CONV_SEQ_CONTINUOUS = 0b10,
+};
+
+enum ad74115_din_threshold_mode {
+ AD74115_DIN_THRESHOLD_MODE_AVDD,
+ AD74115_DIN_THRESHOLD_MODE_FIXED,
+ AD74115_DIN_THRESHOLD_MODE_MAX = AD74115_DIN_THRESHOLD_MODE_FIXED,
+};
+
+enum ad74115_slew_mode {
+ AD74115_SLEW_MODE_DISABLED,
+ AD74115_SLEW_MODE_LINEAR,
+ AD74115_SLEW_MODE_HART,
+};
+
+enum ad74115_slew_step {
+ AD74115_SLEW_STEP_0_8_PERCENT,
+ AD74115_SLEW_STEP_1_5_PERCENT,
+ AD74115_SLEW_STEP_6_1_PERCENT,
+ AD74115_SLEW_STEP_22_2_PERCENT,
+};
+
+enum ad74115_slew_rate {
+ AD74115_SLEW_RATE_4KHZ,
+ AD74115_SLEW_RATE_64KHZ,
+ AD74115_SLEW_RATE_150KHZ,
+ AD74115_SLEW_RATE_240KHZ,
+};
+
+enum ad74115_gpio_config {
+ AD74115_GPIO_CONFIG_OUTPUT_BUFFERED = 0b010,
+ AD74115_GPIO_CONFIG_INPUT = 0b011,
+};
+
+enum ad74115_gpio_mode {
+ AD74115_GPIO_MODE_LOGIC = 1,
+ AD74115_GPIO_MODE_SPECIAL = 2,
+};
+
+struct ad74115_channels {
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+};
+
+struct ad74115_state {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+
+ /*
+ * Synchronize consecutive operations when doing a one-shot
+ * conversion and when updating the ADC samples SPI message.
+ */
+ struct mutex lock;
+ struct gpio_chip gc;
+ struct gpio_chip comp_gc;
+ int irq;
+
+ unsigned int avdd_mv;
+ unsigned long gpio_valid_mask;
+ bool dac_bipolar;
+ bool dac_hart_slew;
+ bool rtd_mode_4_wire;
+ enum ad74115_ch_func ch_func;
+ enum ad74115_din_threshold_mode din_threshold_mode;
+
+ struct completion adc_data_completion;
+ struct spi_message adc_samples_msg;
+ struct spi_transfer adc_samples_xfer[AD74115_ADC_CH_NUM + 1];
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ u8 reg_tx_buf[AD74115_FRAME_SIZE] __aligned(IIO_DMA_MINALIGN);
+ u8 reg_rx_buf[AD74115_FRAME_SIZE];
+ u8 adc_samples_tx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM];
+ u8 adc_samples_rx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM];
+};
+
+struct ad74115_fw_prop {
+ const char *name;
+ bool is_boolean;
+ bool negate;
+ unsigned int max;
+ unsigned int reg;
+ unsigned int mask;
+ const unsigned int *lookup_tbl;
+ unsigned int lookup_tbl_len;
+};
+
+#define AD74115_FW_PROP(_name, _max, _reg, _mask) \
+{ \
+ .name = (_name), \
+ .max = (_max), \
+ .reg = (_reg), \
+ .mask = (_mask), \
+}
+
+#define AD74115_FW_PROP_TBL(_name, _tbl, _reg, _mask) \
+{ \
+ .name = (_name), \
+ .reg = (_reg), \
+ .mask = (_mask), \
+ .lookup_tbl = (_tbl), \
+ .lookup_tbl_len = ARRAY_SIZE(_tbl), \
+}
+
+#define AD74115_FW_PROP_BOOL(_name, _reg, _mask) \
+{ \
+ .name = (_name), \
+ .is_boolean = true, \
+ .reg = (_reg), \
+ .mask = (_mask), \
+}
+
+#define AD74115_FW_PROP_BOOL_NEG(_name, _reg, _mask) \
+{ \
+ .name = (_name), \
+ .is_boolean = true, \
+ .negate = true, \
+ .reg = (_reg), \
+ .mask = (_mask), \
+}
+
+static const int ad74115_dac_rate_tbl[] = {
+ 0,
+ 4 * 8,
+ 4 * 15,
+ 4 * 61,
+ 4 * 222,
+ 64 * 8,
+ 64 * 15,
+ 64 * 61,
+ 64 * 222,
+ 150 * 8,
+ 150 * 15,
+ 150 * 61,
+ 150 * 222,
+ 240 * 8,
+ 240 * 15,
+ 240 * 61,
+ 240 * 222,
+};
+
+static const unsigned int ad74115_dac_rate_step_tbl[][3] = {
+ { AD74115_SLEW_MODE_DISABLED },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_4KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_4KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_4KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_4KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_64KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_64KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_64KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_64KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_150KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_150KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_150KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_150KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_240KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_240KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_240KHZ },
+ { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_240KHZ },
+};
+
+static const unsigned int ad74115_rtd_excitation_current_ua_tbl[] = {
+ 250, 500, 750, 1000
+};
+
+static const unsigned int ad74115_burnout_current_na_tbl[] = {
+ 0, 50, 0, 500, 1000, 0, 10000, 0
+};
+
+static const unsigned int ad74115_viout_burnout_current_na_tbl[] = {
+ 0, 0, 0, 0, 1000, 0, 10000, 0
+};
+
+static const unsigned int ad74115_gpio_mode_tbl[] = {
+ 0, 0, 0, 1, 2, 3, 4, 5
+};
+
+static const unsigned int ad74115_adc_conv_rate_tbl[] = {
+ 10, 20, 1200, 4800, 9600
+};
+
+static const unsigned int ad74115_debounce_tbl[] = {
+ 0, 13, 18, 24, 32, 42, 56, 75,
+ 100, 130, 180, 240, 320, 420, 560, 750,
+ 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500,
+ 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000,
+};
+
+static const unsigned int ad74115_adc_ch_data_regs_tbl[] = {
+ [AD74115_ADC_CH_CONV1] = 0x44,
+ [AD74115_ADC_CH_CONV2] = 0x46,
+};
+
+static const unsigned int ad74115_adc_ch_en_bit_tbl[] = {
+ [AD74115_ADC_CH_CONV1] = BIT(0),
+ [AD74115_ADC_CH_CONV2] = BIT(1),
+};
+
+static const bool ad74115_adc_bipolar_tbl[AD74115_ADC_RANGE_NUM] = {
+ [AD74115_ADC_RANGE_12V_BIPOLAR] = true,
+ [AD74115_ADC_RANGE_2_5V_BIPOLAR] = true,
+ [AD74115_ADC_RANGE_104MV_BIPOLAR] = true,
+};
+
+static const unsigned int ad74115_adc_conv_mul_tbl[AD74115_ADC_RANGE_NUM] = {
+ [AD74115_ADC_RANGE_12V] = 12000,
+ [AD74115_ADC_RANGE_12V_BIPOLAR] = 24000,
+ [AD74115_ADC_RANGE_2_5V_BIPOLAR] = 5000,
+ [AD74115_ADC_RANGE_2_5V_NEG] = 2500,
+ [AD74115_ADC_RANGE_2_5V] = 2500,
+ [AD74115_ADC_RANGE_0_625V] = 625,
+ [AD74115_ADC_RANGE_104MV_BIPOLAR] = 208,
+ [AD74115_ADC_RANGE_12V_OTHER] = 12000,
+};
+
+static const unsigned int ad74115_adc_gain_tbl[AD74115_ADC_RANGE_NUM][2] = {
+ [AD74115_ADC_RANGE_12V] = { 5, 24 },
+ [AD74115_ADC_RANGE_12V_BIPOLAR] = { 5, 24 },
+ [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { 1, 1 },
+ [AD74115_ADC_RANGE_2_5V_NEG] = { 1, 1 },
+ [AD74115_ADC_RANGE_2_5V] = { 1, 1 },
+ [AD74115_ADC_RANGE_0_625V] = { 4, 1 },
+ [AD74115_ADC_RANGE_104MV_BIPOLAR] = { 24, 1 },
+ [AD74115_ADC_RANGE_12V_OTHER] = { 5, 24 },
+};
+
+static const int ad74115_adc_range_tbl[AD74115_ADC_RANGE_NUM][2] = {
+ [AD74115_ADC_RANGE_12V] = { 0, 12000000 },
+ [AD74115_ADC_RANGE_12V_BIPOLAR] = { -12000000, 12000000 },
+ [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { -2500000, 2500000 },
+ [AD74115_ADC_RANGE_2_5V_NEG] = { -2500000, 0 },
+ [AD74115_ADC_RANGE_2_5V] = { 0, 2500000 },
+ [AD74115_ADC_RANGE_0_625V] = { 0, 625000 },
+ [AD74115_ADC_RANGE_104MV_BIPOLAR] = { -104000, 104000 },
+ [AD74115_ADC_RANGE_12V_OTHER] = { 0, 12000000 },
+};
+
+static int _ad74115_find_tbl_index(const unsigned int *tbl, unsigned int tbl_len,
+ unsigned int val, unsigned int *index)
+{
+ unsigned int i;
+
+ for (i = 0; i < tbl_len; i++)
+ if (val == tbl[i]) {
+ *index = i;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+#define ad74115_find_tbl_index(tbl, val, index) \
+ _ad74115_find_tbl_index(tbl, ARRAY_SIZE(tbl), val, index)
+
+static int ad74115_crc(u8 *buf)
+{
+ return crc8(ad74115_crc8_table, buf, 3, 0);
+}
+
+static void ad74115_format_reg_write(u8 reg, u16 val, u8 *buf)
+{
+ buf[0] = reg;
+ put_unaligned_be16(val, &buf[1]);
+ buf[3] = ad74115_crc(buf);
+}
+
+static int ad74115_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct ad74115_state *st = context;
+
+ ad74115_format_reg_write(reg, val, st->reg_tx_buf);
+
+ return spi_write(st->spi, st->reg_tx_buf, AD74115_FRAME_SIZE);
+}
+
+static int ad74115_crc_check(struct ad74115_state *st, u8 *buf)
+{
+ struct device *dev = &st->spi->dev;
+ u8 expected_crc = ad74115_crc(buf);
+
+ if (buf[3] != expected_crc) {
+ dev_err(dev, "Bad CRC %02x for %02x%02x%02x, expected %02x\n",
+ buf[3], buf[0], buf[1], buf[2], expected_crc);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ad74115_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct ad74115_state *st = context;
+ struct spi_transfer reg_read_xfer[] = {
+ {
+ .tx_buf = st->reg_tx_buf,
+ .len = sizeof(st->reg_tx_buf),
+ .cs_change = 1,
+ },
+ {
+ .rx_buf = st->reg_rx_buf,
+ .len = sizeof(st->reg_rx_buf),
+ },
+ };
+ int ret;
+
+ ad74115_format_reg_write(AD74115_READ_SELECT_REG, reg, st->reg_tx_buf);
+
+ ret = spi_sync_transfer(st->spi, reg_read_xfer, ARRAY_SIZE(reg_read_xfer));
+ if (ret)
+ return ret;
+
+ ret = ad74115_crc_check(st, st->reg_rx_buf);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&st->reg_rx_buf[1]);
+
+ return 0;
+}
+
+static const struct regmap_config ad74115_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .reg_read = ad74115_reg_read,
+ .reg_write = ad74115_reg_write,
+};
+
+static int ad74115_gpio_config_set(struct ad74115_state *st, unsigned int offset,
+ enum ad74115_gpio_config cfg)
+{
+ return regmap_update_bits(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset),
+ AD74115_GPIO_CONFIG_SELECT_MASK,
+ FIELD_PREP(AD74115_GPIO_CONFIG_SELECT_MASK, cfg));
+}
+
+static int ad74115_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+
+ *valid_mask = st->gpio_valid_mask;
+
+ return 0;
+}
+
+static int ad74115_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(AD74115_GPIO_CONFIG_SELECT_MASK, val) == AD74115_GPIO_CONFIG_INPUT;
+}
+
+static int ad74115_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+
+ return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_INPUT);
+}
+
+static int ad74115_gpio_direction_output(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+
+ return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_OUTPUT_BUFFERED);
+}
+
+static int ad74115_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(AD74115_GPIO_CONFIG_GPI_DATA, val);
+}
+
+static int ad74115_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+
+ return regmap_update_bits(st->regmap,
+ AD74115_GPIO_CONFIG_X_REG(offset),
+ AD74115_GPIO_CONFIG_GPO_DATA,
+ FIELD_PREP(AD74115_GPIO_CONFIG_GPO_DATA,
+ value));
+}
+
+static int ad74115_set_comp_debounce(struct ad74115_state *st, unsigned int val)
+{
+ unsigned int len = ARRAY_SIZE(ad74115_debounce_tbl);
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ if (val <= ad74115_debounce_tbl[i])
+ break;
+
+ if (i == len)
+ i = len - 1;
+
+ return regmap_update_bits(st->regmap, AD74115_DIN_CONFIG1_REG,
+ AD74115_DIN_DEBOUNCE_MASK,
+ FIELD_PREP(AD74115_DIN_DEBOUNCE_MASK, val));
+}
+
+static int ad74115_comp_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int ad74115_comp_gpio_set_config(struct gpio_chip *chip,
+ unsigned int offset,
+ unsigned long config)
+{
+ struct ad74115_state *st = gpiochip_get_data(chip);
+ u32 param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ switch (param) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return ad74115_set_comp_debounce(st, arg);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ad74115_comp_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ad74115_state *st = gpiochip_get_data(chip);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_DIN_COMP_OUT_REG, &val);
+ if (ret)
+ return ret;
+
+ return !!val;
+}
+
+static irqreturn_t ad74115_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad74115_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = spi_sync(st->spi, &st->adc_samples_msg);
+ if (ret)
+ goto out;
+
+ iio_push_to_buffers(indio_dev, st->adc_samples_rx_buf);
+
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ad74115_adc_data_interrupt(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct ad74115_state *st = iio_priv(indio_dev);
+
+ if (iio_buffer_enabled(indio_dev))
+ iio_trigger_poll(st->trig);
+ else
+ complete(&st->adc_data_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int ad74115_set_adc_ch_en(struct ad74115_state *st,
+ enum ad74115_adc_ch channel, bool status)
+{
+ unsigned int mask = ad74115_adc_ch_en_bit_tbl[channel];
+
+ return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG, mask,
+ status ? mask : 0);
+}
+
+static int ad74115_set_adc_conv_seq(struct ad74115_state *st,
+ enum ad74115_adc_conv_seq conv_seq)
+{
+ return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG,
+ AD74115_ADC_CONV_SEQ_MASK,
+ FIELD_PREP(AD74115_ADC_CONV_SEQ_MASK, conv_seq));
+}
+
+static int ad74115_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *active_scan_mask)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ struct spi_transfer *xfer = st->adc_samples_xfer;
+ u8 *rx_buf = st->adc_samples_rx_buf;
+ u8 *tx_buf = st->adc_samples_tx_buf;
+ unsigned int i;
+ int ret = 0;
+
+ mutex_lock(&st->lock);
+
+ spi_message_init(&st->adc_samples_msg);
+
+ for_each_clear_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) {
+ ret = ad74115_set_adc_ch_en(st, i, false);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * The read select register is used to select which register's value
+ * will be sent by the slave on the next SPI frame.
+ *
+ * Create an SPI message that, on each step, writes to the read select
+ * register to select the ADC result of the next enabled channel, and
+ * reads the ADC result of the previous enabled channel.
+ *
+ * Example:
+ * W: [WCH1] [WCH2] [WCH2] [WCH3] [ ]
+ * R: [ ] [RCH1] [RCH2] [RCH3] [RCH4]
+ */
+ for_each_set_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) {
+ ret = ad74115_set_adc_ch_en(st, i, true);
+ if (ret)
+ goto out;
+
+ if (xfer == st->adc_samples_xfer)
+ xfer->rx_buf = NULL;
+ else
+ xfer->rx_buf = rx_buf;
+
+ xfer->tx_buf = tx_buf;
+ xfer->len = AD74115_FRAME_SIZE;
+ xfer->cs_change = 1;
+
+ ad74115_format_reg_write(AD74115_READ_SELECT_REG,
+ ad74115_adc_ch_data_regs_tbl[i], tx_buf);
+
+ spi_message_add_tail(xfer, &st->adc_samples_msg);
+
+ tx_buf += AD74115_FRAME_SIZE;
+ if (xfer != st->adc_samples_xfer)
+ rx_buf += AD74115_FRAME_SIZE;
+ xfer++;
+ }
+
+ xfer->rx_buf = rx_buf;
+ xfer->tx_buf = NULL;
+ xfer->len = AD74115_FRAME_SIZE;
+ xfer->cs_change = 0;
+
+ spi_message_add_tail(xfer, &st->adc_samples_msg);
+
+out:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int ad74115_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+
+ return ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_CONTINUOUS);
+}
+
+static int ad74115_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ unsigned int i;
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY);
+ if (ret)
+ goto out;
+
+ /*
+ * update_scan_mode() is not called in the disable path, disable all
+ * channels here.
+ */
+ for (i = 0; i < AD74115_ADC_CH_NUM; i++) {
+ ret = ad74115_set_adc_ch_en(st, i, false);
+ if (ret)
+ goto out;
+ }
+
+out:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops ad74115_buffer_ops = {
+ .postenable = &ad74115_buffer_postenable,
+ .predisable = &ad74115_buffer_predisable,
+};
+
+static const struct iio_trigger_ops ad74115_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static int ad74115_get_adc_rate(struct ad74115_state *st,
+ enum ad74115_adc_ch channel, int *val)
+{
+ unsigned int i;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_ADC_CONFIG_REG, &i);
+ if (ret)
+ return ret;
+
+ if (channel == AD74115_ADC_CH_CONV1)
+ i = FIELD_GET(AD74115_ADC_CONFIG_CONV1_RATE_MASK, i);
+ else
+ i = FIELD_GET(AD74115_ADC_CONFIG_CONV2_RATE_MASK, i);
+
+ *val = ad74115_adc_conv_rate_tbl[i];
+
+ return IIO_VAL_INT;
+}
+
+static int _ad74115_get_adc_code(struct ad74115_state *st,
+ enum ad74115_adc_ch channel, int *val)
+{
+ unsigned int uval;
+ int ret;
+
+ reinit_completion(&st->adc_data_completion);
+
+ ret = ad74115_set_adc_ch_en(st, channel, true);
+ if (ret)
+ return ret;
+
+ ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_SINGLE);
+ if (ret)
+ return ret;
+
+ if (st->irq) {
+ ret = wait_for_completion_timeout(&st->adc_data_completion,
+ msecs_to_jiffies(1000));
+ if (!ret)
+ return -ETIMEDOUT;
+ } else {
+ unsigned int regval, wait_time;
+ int rate;
+
+ ret = ad74115_get_adc_rate(st, channel, &rate);
+ if (ret < 0)
+ return ret;
+
+ wait_time = DIV_ROUND_CLOSEST(AD74115_CONV_TIME_US, rate);
+
+ ret = regmap_read_poll_timeout(st->regmap, AD74115_LIVE_STATUS_REG,
+ regval, regval & AD74115_ADC_DATA_RDY_MASK,
+ wait_time, 5 * wait_time);
+ if (ret)
+ return ret;
+
+ /*
+ * The ADC_DATA_RDY bit is W1C.
+ * See datasheet page 98, Table 62. Bit Descriptions for
+ * LIVE_STATUS.
+ * Although the datasheet mentions that the bit will auto-clear
+ * when writing to the ADC_CONV_CTRL register, this does not
+ * seem to happen.
+ */
+ ret = regmap_write_bits(st->regmap, AD74115_LIVE_STATUS_REG,
+ AD74115_ADC_DATA_RDY_MASK,
+ FIELD_PREP(AD74115_ADC_DATA_RDY_MASK, 1));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(st->regmap, ad74115_adc_ch_data_regs_tbl[channel], &uval);
+ if (ret)
+ return ret;
+
+ ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY);
+ if (ret)
+ return ret;
+
+ ret = ad74115_set_adc_ch_en(st, channel, false);
+ if (ret)
+ return ret;
+
+ *val = uval;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74115_get_adc_code(struct iio_dev *indio_dev,
+ enum ad74115_adc_ch channel, int *val)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ mutex_lock(&st->lock);
+ ret = _ad74115_get_adc_code(st, channel, val);
+ mutex_unlock(&st->lock);
+
+ iio_device_release_direct(indio_dev);
+
+ return ret;
+}
+
+static int ad74115_adc_code_to_resistance(int code, int *val, int *val2)
+{
+ if (code == AD74115_ADC_CODE_MAX)
+ code--;
+
+ *val = code * AD74115_REF_RESISTOR_OHMS;
+ *val2 = AD74115_ADC_CODE_MAX - code;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74115_set_dac_code(struct ad74115_state *st,
+ enum ad74115_dac_ch channel, int val)
+{
+ if (val < 0)
+ return -EINVAL;
+
+ if (channel == AD74115_DAC_CH_COMPARATOR) {
+ if (val > AD74115_COMP_THRESH_MAX)
+ return -EINVAL;
+
+ return regmap_update_bits(st->regmap, AD74115_DIN_CONFIG2_REG,
+ AD74115_COMP_THRESH_MASK,
+ FIELD_PREP(AD74115_COMP_THRESH_MASK, val));
+ }
+
+ if (val > AD74115_DAC_CODE_MAX)
+ return -EINVAL;
+
+ return regmap_write(st->regmap, AD74115_DAC_CODE_REG, val);
+}
+
+static int ad74115_get_dac_code(struct ad74115_state *st,
+ enum ad74115_dac_ch channel, int *val)
+{
+ unsigned int uval;
+ int ret;
+
+ if (channel == AD74115_DAC_CH_COMPARATOR)
+ return -EINVAL;
+
+ ret = regmap_read(st->regmap, AD74115_DAC_ACTIVE_REG, &uval);
+ if (ret)
+ return ret;
+
+ *val = uval;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74115_set_adc_rate(struct ad74115_state *st,
+ enum ad74115_adc_ch channel, int val)
+{
+ unsigned int i;
+ int ret;
+
+ ret = ad74115_find_tbl_index(ad74115_adc_conv_rate_tbl, val, &i);
+ if (ret)
+ return ret;
+
+ if (channel == AD74115_ADC_CH_CONV1)
+ return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG,
+ AD74115_ADC_CONFIG_CONV1_RATE_MASK,
+ FIELD_PREP(AD74115_ADC_CONFIG_CONV1_RATE_MASK, i));
+
+ return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG,
+ AD74115_ADC_CONFIG_CONV2_RATE_MASK,
+ FIELD_PREP(AD74115_ADC_CONFIG_CONV2_RATE_MASK, i));
+}
+
+static int ad74115_get_dac_rate(struct ad74115_state *st, int *val)
+{
+ unsigned int i, en_val, step_val, rate_val, tmp;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_OUTPUT_CONFIG_REG, &tmp);
+ if (ret)
+ return ret;
+
+ en_val = FIELD_GET(AD74115_OUTPUT_SLEW_EN_MASK, tmp);
+ step_val = FIELD_GET(AD74115_OUTPUT_SLEW_LIN_STEP_MASK, tmp);
+ rate_val = FIELD_GET(AD74115_OUTPUT_SLEW_LIN_RATE_MASK, tmp);
+
+ for (i = 0; i < ARRAY_SIZE(ad74115_dac_rate_step_tbl); i++)
+ if (en_val == ad74115_dac_rate_step_tbl[i][0] &&
+ step_val == ad74115_dac_rate_step_tbl[i][1] &&
+ rate_val == ad74115_dac_rate_step_tbl[i][2])
+ break;
+
+ if (i == ARRAY_SIZE(ad74115_dac_rate_step_tbl))
+ return -EINVAL;
+
+ *val = ad74115_dac_rate_tbl[i];
+
+ return IIO_VAL_INT;
+}
+
+static int ad74115_set_dac_rate(struct ad74115_state *st, int val)
+{
+ unsigned int i, en_val, step_val, rate_val, mask, tmp;
+ int ret;
+
+ ret = ad74115_find_tbl_index(ad74115_dac_rate_tbl, val, &i);
+ if (ret)
+ return ret;
+
+ en_val = ad74115_dac_rate_step_tbl[i][0];
+ step_val = ad74115_dac_rate_step_tbl[i][1];
+ rate_val = ad74115_dac_rate_step_tbl[i][2];
+
+ mask = AD74115_OUTPUT_SLEW_EN_MASK;
+ mask |= AD74115_OUTPUT_SLEW_LIN_STEP_MASK;
+ mask |= AD74115_OUTPUT_SLEW_LIN_RATE_MASK;
+
+ tmp = FIELD_PREP(AD74115_OUTPUT_SLEW_EN_MASK, en_val);
+ tmp |= FIELD_PREP(AD74115_OUTPUT_SLEW_LIN_STEP_MASK, step_val);
+ tmp |= FIELD_PREP(AD74115_OUTPUT_SLEW_LIN_RATE_MASK, rate_val);
+
+ return regmap_update_bits(st->regmap, AD74115_OUTPUT_CONFIG_REG, mask, tmp);
+}
+
+static int ad74115_get_dac_scale(struct ad74115_state *st,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ if (chan->channel == AD74115_DAC_CH_MAIN) {
+ if (chan->type == IIO_VOLTAGE) {
+ *val = AD74115_DAC_VOLTAGE_MAX;
+
+ if (st->dac_bipolar)
+ *val *= 2;
+
+ } else {
+ *val = AD74115_DAC_CURRENT_MAX;
+ }
+
+ *val2 = AD74115_DAC_CODE_MAX;
+ } else {
+ if (st->din_threshold_mode == AD74115_DIN_THRESHOLD_MODE_AVDD) {
+ *val = 196 * st->avdd_mv;
+ *val2 = 10 * AD74115_COMP_THRESH_MAX;
+ } else {
+ *val = 49000;
+ *val2 = AD74115_COMP_THRESH_MAX;
+ }
+ }
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74115_get_dac_offset(struct ad74115_state *st,
+ struct iio_chan_spec const *chan, int *val)
+{
+ if (chan->channel == AD74115_DAC_CH_MAIN) {
+ if (chan->type == IIO_VOLTAGE && st->dac_bipolar)
+ *val = -AD74115_DAC_CODE_HALF;
+ else
+ *val = 0;
+ } else {
+ if (st->din_threshold_mode == AD74115_DIN_THRESHOLD_MODE_AVDD)
+ *val = -48;
+ else
+ *val = -38;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int ad74115_get_adc_range(struct ad74115_state *st,
+ enum ad74115_adc_ch channel, unsigned int *val)
+{
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_ADC_CONFIG_REG, val);
+ if (ret)
+ return ret;
+
+ if (channel == AD74115_ADC_CH_CONV1)
+ *val = FIELD_GET(AD74115_ADC_CONFIG_CONV1_RANGE_MASK, *val);
+ else
+ *val = FIELD_GET(AD74115_ADC_CONFIG_CONV2_RANGE_MASK, *val);
+
+ return 0;
+}
+
+static int ad74115_get_adc_resistance_scale(struct ad74115_state *st,
+ unsigned int range,
+ int *val, int *val2)
+{
+ *val = ad74115_adc_gain_tbl[range][1] * AD74115_REF_RESISTOR_OHMS;
+ *val2 = ad74115_adc_gain_tbl[range][0];
+
+ if (ad74115_adc_bipolar_tbl[range])
+ *val2 *= AD74115_ADC_CODE_HALF;
+ else
+ *val2 *= AD74115_ADC_CODE_MAX;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74115_get_adc_scale(struct ad74115_state *st,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ unsigned int range;
+ int ret;
+
+ ret = ad74115_get_adc_range(st, chan->channel, &range);
+ if (ret)
+ return ret;
+
+ if (chan->type == IIO_RESISTANCE)
+ return ad74115_get_adc_resistance_scale(st, range, val, val2);
+
+ *val = ad74115_adc_conv_mul_tbl[range];
+ *val2 = AD74115_ADC_CODE_MAX;
+
+ if (chan->type == IIO_CURRENT)
+ *val2 *= AD74115_SENSE_RESISTOR_OHMS;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74115_get_adc_resistance_offset(struct ad74115_state *st,
+ unsigned int range,
+ int *val, int *val2)
+{
+ unsigned int d = 10 * AD74115_REF_RESISTOR_OHMS
+ * ad74115_adc_gain_tbl[range][1];
+
+ *val = 5;
+
+ if (ad74115_adc_bipolar_tbl[range])
+ *val -= AD74115_ADC_CODE_HALF;
+
+ *val *= d;
+
+ if (!st->rtd_mode_4_wire) {
+ /* Add 0.2 Ohm to the final result for 3-wire RTD. */
+ unsigned int v = 2 * ad74115_adc_gain_tbl[range][0];
+
+ if (ad74115_adc_bipolar_tbl[range])
+ v *= AD74115_ADC_CODE_HALF;
+ else
+ v *= AD74115_ADC_CODE_MAX;
+
+ *val += v;
+ }
+
+ *val2 = d;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74115_get_adc_offset(struct ad74115_state *st,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ unsigned int range;
+ int ret;
+
+ ret = ad74115_get_adc_range(st, chan->channel, &range);
+ if (ret)
+ return ret;
+
+ if (chan->type == IIO_RESISTANCE)
+ return ad74115_get_adc_resistance_offset(st, range, val, val2);
+
+ if (ad74115_adc_bipolar_tbl[range])
+ *val = -AD74115_ADC_CODE_HALF;
+ else if (range == AD74115_ADC_RANGE_2_5V_NEG)
+ *val = -AD74115_ADC_CODE_MAX;
+ else
+ *val = 0;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74115_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->output)
+ return ad74115_get_dac_code(st, chan->channel, val);
+
+ return ad74115_get_adc_code(indio_dev, chan->channel, val);
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = ad74115_get_adc_code(indio_dev, chan->channel, val);
+ if (ret)
+ return ret;
+
+ return ad74115_adc_code_to_resistance(*val, val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->output)
+ return ad74115_get_dac_scale(st, chan, val, val2);
+
+ return ad74115_get_adc_scale(st, chan, val, val2);
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->output)
+ return ad74115_get_dac_offset(st, chan, val);
+
+ return ad74115_get_adc_offset(st, chan, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->output)
+ return ad74115_get_dac_rate(st, val);
+
+ return ad74115_get_adc_rate(st, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad74115_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2,
+ long info)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!chan->output)
+ return -EINVAL;
+
+ return ad74115_set_dac_code(st, chan->channel, val);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->output)
+ return ad74115_set_dac_rate(st, val);
+
+ return ad74115_set_adc_rate(st, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad74115_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length, long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->output) {
+ *vals = ad74115_dac_rate_tbl;
+ *length = ARRAY_SIZE(ad74115_dac_rate_tbl);
+ } else {
+ *vals = ad74115_adc_conv_rate_tbl;
+ *length = ARRAY_SIZE(ad74115_adc_conv_rate_tbl);
+ }
+
+ *type = IIO_VAL_INT;
+
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad74115_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad74115_info = {
+ .read_raw = ad74115_read_raw,
+ .write_raw = ad74115_write_raw,
+ .read_avail = ad74115_read_avail,
+ .update_scan_mode = ad74115_update_scan_mode,
+ .debugfs_reg_access = ad74115_reg_access,
+};
+
+#define AD74115_DAC_CHANNEL(_type, index) \
+ { \
+ .type = (_type), \
+ .channel = (index), \
+ .indexed = 1, \
+ .output = 1, \
+ .scan_index = -1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SCALE) \
+ | BIT(IIO_CHAN_INFO_OFFSET), \
+ }
+
+#define _AD74115_ADC_CHANNEL(_type, index, extra_mask_separate) \
+ { \
+ .type = (_type), \
+ .channel = (index), \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | (extra_mask_separate), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 32, \
+ .shift = 8, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+#define AD74115_ADC_CHANNEL(_type, index) \
+ _AD74115_ADC_CHANNEL(_type, index, BIT(IIO_CHAN_INFO_SCALE) \
+ | BIT(IIO_CHAN_INFO_OFFSET))
+
+static const struct iio_chan_spec ad74115_voltage_input_channels[] = {
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_voltage_output_channels[] = {
+ AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_MAIN),
+ AD74115_ADC_CHANNEL(IIO_CURRENT, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_current_input_channels[] = {
+ AD74115_ADC_CHANNEL(IIO_CURRENT, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_current_output_channels[] = {
+ AD74115_DAC_CHANNEL(IIO_CURRENT, AD74115_DAC_CH_MAIN),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_2_wire_resistance_input_channels[] = {
+ _AD74115_ADC_CHANNEL(IIO_RESISTANCE, AD74115_ADC_CH_CONV1,
+ BIT(IIO_CHAN_INFO_PROCESSED)),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_3_4_wire_resistance_input_channels[] = {
+ AD74115_ADC_CHANNEL(IIO_RESISTANCE, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_digital_input_logic_channels[] = {
+ AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_COMPARATOR),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+static const struct iio_chan_spec ad74115_digital_input_loop_channels[] = {
+ AD74115_DAC_CHANNEL(IIO_CURRENT, AD74115_DAC_CH_MAIN),
+ AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_COMPARATOR),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1),
+ AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2),
+};
+
+#define _AD74115_CHANNELS(_channels) \
+ { \
+ .channels = _channels, \
+ .num_channels = ARRAY_SIZE(_channels), \
+ }
+
+#define AD74115_CHANNELS(name) \
+ _AD74115_CHANNELS(ad74115_ ## name ## _channels)
+
+static const struct ad74115_channels ad74115_channels_map[AD74115_CH_FUNC_NUM] = {
+ [AD74115_CH_FUNC_HIGH_IMPEDANCE] = AD74115_CHANNELS(voltage_input),
+ [AD74115_CH_FUNC_VOLTAGE_INPUT] = AD74115_CHANNELS(voltage_input),
+
+ [AD74115_CH_FUNC_VOLTAGE_OUTPUT] = AD74115_CHANNELS(voltage_output),
+
+ [AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER] = AD74115_CHANNELS(current_input),
+ [AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER] = AD74115_CHANNELS(current_input),
+ [AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER_HART] = AD74115_CHANNELS(current_input),
+ [AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART] = AD74115_CHANNELS(current_input),
+
+ [AD74115_CH_FUNC_CURRENT_OUTPUT] = AD74115_CHANNELS(current_output),
+ [AD74115_CH_FUNC_CURRENT_OUTPUT_HART] = AD74115_CHANNELS(current_output),
+
+ [AD74115_CH_FUNC_2_WIRE_RESISTANCE_INPUT] = AD74115_CHANNELS(2_wire_resistance_input),
+ [AD74115_CH_FUNC_3_4_WIRE_RESISTANCE_INPUT] = AD74115_CHANNELS(3_4_wire_resistance_input),
+
+ [AD74115_CH_FUNC_DIGITAL_INPUT_LOGIC] = AD74115_CHANNELS(digital_input_logic),
+
+ [AD74115_CH_FUNC_DIGITAL_INPUT_LOOP_POWER] = AD74115_CHANNELS(digital_input_loop),
+};
+
+#define AD74115_GPIO_MODE_FW_PROP(i) \
+{ \
+ .name = "adi,gpio" __stringify(i) "-mode", \
+ .reg = AD74115_GPIO_CONFIG_X_REG(i), \
+ .mask = AD74115_GPIO_CONFIG_SELECT_MASK, \
+ .lookup_tbl = ad74115_gpio_mode_tbl, \
+ .lookup_tbl_len = ARRAY_SIZE(ad74115_gpio_mode_tbl), \
+}
+
+static const struct ad74115_fw_prop ad74115_gpio_mode_fw_props[] = {
+ AD74115_GPIO_MODE_FW_PROP(0),
+ AD74115_GPIO_MODE_FW_PROP(1),
+ AD74115_GPIO_MODE_FW_PROP(2),
+ AD74115_GPIO_MODE_FW_PROP(3),
+};
+
+static const struct ad74115_fw_prop ad74115_din_threshold_mode_fw_prop =
+ AD74115_FW_PROP_BOOL("adi,digital-input-threshold-mode-fixed",
+ AD74115_DIN_CONFIG2_REG, BIT(7));
+
+static const struct ad74115_fw_prop ad74115_dac_bipolar_fw_prop =
+ AD74115_FW_PROP_BOOL("adi,dac-bipolar", AD74115_OUTPUT_CONFIG_REG, BIT(7));
+
+static const struct ad74115_fw_prop ad74115_ch_func_fw_prop =
+ AD74115_FW_PROP("adi,ch-func", AD74115_CH_FUNC_MAX,
+ AD74115_CH_FUNC_SETUP_REG, GENMASK(3, 0));
+
+static const struct ad74115_fw_prop ad74115_rtd_mode_fw_prop =
+ AD74115_FW_PROP_BOOL("adi,4-wire-rtd", AD74115_RTD3W4W_CONFIG_REG, BIT(3));
+
+static const struct ad74115_fw_prop ad74115_din_range_fw_prop =
+ AD74115_FW_PROP_BOOL("adi,digital-input-sink-range-high",
+ AD74115_DIN_CONFIG1_REG, BIT(12));
+
+static const struct ad74115_fw_prop ad74115_ext2_burnout_current_fw_prop =
+ AD74115_FW_PROP_TBL("adi,ext2-burnout-current-nanoamp",
+ ad74115_burnout_current_na_tbl,
+ AD74115_BURNOUT_CONFIG_REG, GENMASK(14, 12));
+
+static const struct ad74115_fw_prop ad74115_ext1_burnout_current_fw_prop =
+ AD74115_FW_PROP_TBL("adi,ext1-burnout-current-nanoamp",
+ ad74115_burnout_current_na_tbl,
+ AD74115_BURNOUT_CONFIG_REG, GENMASK(9, 7));
+
+static const struct ad74115_fw_prop ad74115_viout_burnout_current_fw_prop =
+ AD74115_FW_PROP_TBL("adi,viout-burnout-current-nanoamp",
+ ad74115_viout_burnout_current_na_tbl,
+ AD74115_BURNOUT_CONFIG_REG, GENMASK(4, 2));
+
+static const struct ad74115_fw_prop ad74115_fw_props[] = {
+ AD74115_FW_PROP("adi,conv2-mux", 3,
+ AD74115_ADC_CONFIG_REG, GENMASK(3, 2)),
+
+ AD74115_FW_PROP_BOOL_NEG("adi,sense-agnd-buffer-low-power",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(4)),
+ AD74115_FW_PROP_BOOL_NEG("adi,lf-buffer-low-power",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(3)),
+ AD74115_FW_PROP_BOOL_NEG("adi,hf-buffer-low-power",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(2)),
+ AD74115_FW_PROP_BOOL_NEG("adi,ext2-buffer-low-power",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(1)),
+ AD74115_FW_PROP_BOOL_NEG("adi,ext1-buffer-low-power",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(0)),
+
+ AD74115_FW_PROP_BOOL("adi,comparator-invert",
+ AD74115_DIN_CONFIG1_REG, BIT(14)),
+ AD74115_FW_PROP_BOOL("adi,digital-input-debounce-mode-counter-reset",
+ AD74115_DIN_CONFIG1_REG, BIT(6)),
+
+ AD74115_FW_PROP_BOOL("adi,digital-input-unbuffered",
+ AD74115_DIN_CONFIG2_REG, BIT(10)),
+ AD74115_FW_PROP_BOOL("adi,digital-input-short-circuit-detection",
+ AD74115_DIN_CONFIG2_REG, BIT(9)),
+ AD74115_FW_PROP_BOOL("adi,digital-input-open-circuit-detection",
+ AD74115_DIN_CONFIG2_REG, BIT(8)),
+
+ AD74115_FW_PROP_BOOL("adi,dac-current-limit-low",
+ AD74115_OUTPUT_CONFIG_REG, BIT(0)),
+
+ AD74115_FW_PROP_BOOL("adi,3-wire-rtd-excitation-swap",
+ AD74115_RTD3W4W_CONFIG_REG, BIT(2)),
+ AD74115_FW_PROP_TBL("adi,rtd-excitation-current-microamp",
+ ad74115_rtd_excitation_current_ua_tbl,
+ AD74115_RTD3W4W_CONFIG_REG, GENMASK(1, 0)),
+
+ AD74115_FW_PROP_BOOL("adi,ext2-burnout-current-polarity-sourcing",
+ AD74115_BURNOUT_CONFIG_REG, BIT(11)),
+ AD74115_FW_PROP_BOOL("adi,ext1-burnout-current-polarity-sourcing",
+ AD74115_BURNOUT_CONFIG_REG, BIT(6)),
+ AD74115_FW_PROP_BOOL("adi,viout-burnout-current-polarity-sourcing",
+ AD74115_BURNOUT_CONFIG_REG, BIT(1)),
+
+ AD74115_FW_PROP_BOOL("adi,charge-pump",
+ AD74115_CHARGE_PUMP_REG, BIT(0)),
+};
+
+static int ad74115_apply_fw_prop(struct ad74115_state *st,
+ const struct ad74115_fw_prop *prop, u32 *retval)
+{
+ struct device *dev = &st->spi->dev;
+ u32 val = 0;
+ int ret;
+
+ if (prop->is_boolean) {
+ val = device_property_read_bool(dev, prop->name);
+ } else {
+ ret = device_property_read_u32(dev, prop->name, &val);
+ if (ret && prop->lookup_tbl)
+ val = prop->lookup_tbl[0];
+ }
+
+ *retval = val;
+
+ if (prop->negate)
+ val = !val;
+
+ if (prop->lookup_tbl)
+ ret = _ad74115_find_tbl_index(prop->lookup_tbl,
+ prop->lookup_tbl_len, val, &val);
+ else if (prop->max && val > prop->max)
+ ret = -EINVAL;
+ else
+ ret = 0;
+
+ if (ret)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid value %u for prop %s\n",
+ val, prop->name);
+
+ WARN(!prop->mask, "Prop %s mask is empty\n", prop->name);
+
+ val = (val << __ffs(prop->mask)) & prop->mask;
+
+ return regmap_update_bits(st->regmap, prop->reg, prop->mask, val);
+}
+
+static int ad74115_setup_adc_conv2_range(struct ad74115_state *st)
+{
+ unsigned int tbl_len = ARRAY_SIZE(ad74115_adc_range_tbl);
+ const char *prop_name = "adi,conv2-range-microvolt";
+ s32 vals[2] = {
+ ad74115_adc_range_tbl[0][0],
+ ad74115_adc_range_tbl[0][1],
+ };
+ struct device *dev = &st->spi->dev;
+ unsigned int i;
+
+ device_property_read_u32_array(dev, prop_name, vals, 2);
+
+ for (i = 0; i < tbl_len; i++)
+ if (vals[0] == ad74115_adc_range_tbl[i][0] &&
+ vals[1] == ad74115_adc_range_tbl[i][1])
+ break;
+
+ if (i == tbl_len)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid value %d, %d for prop %s\n",
+ vals[0], vals[1], prop_name);
+
+ return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG,
+ AD74115_ADC_CONFIG_CONV2_RANGE_MASK,
+ FIELD_PREP(AD74115_ADC_CONFIG_CONV2_RANGE_MASK, i));
+}
+
+static int ad74115_setup_iio_channels(struct iio_dev *indio_dev)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct iio_chan_spec *channels;
+
+ channels = devm_kcalloc(dev, sizeof(*channels),
+ indio_dev->num_channels, GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ indio_dev->channels = channels;
+
+ memcpy(channels, ad74115_channels_map[st->ch_func].channels,
+ sizeof(*channels) * ad74115_channels_map[st->ch_func].num_channels);
+
+ if (channels[0].output && channels[0].channel == AD74115_DAC_CH_MAIN &&
+ channels[0].type == IIO_VOLTAGE && !st->dac_hart_slew) {
+ channels[0].info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ channels[0].info_mask_separate_available |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ }
+
+ return 0;
+}
+
+static int ad74115_setup_gpio_chip(struct ad74115_state *st)
+{
+ struct device *dev = &st->spi->dev;
+
+ if (!st->gpio_valid_mask)
+ return 0;
+
+ st->gc = (struct gpio_chip) {
+ .owner = THIS_MODULE,
+ .label = AD74115_NAME,
+ .base = -1,
+ .ngpio = AD74115_GPIO_NUM,
+ .parent = dev,
+ .can_sleep = true,
+ .init_valid_mask = ad74115_gpio_init_valid_mask,
+ .get_direction = ad74115_gpio_get_direction,
+ .direction_input = ad74115_gpio_direction_input,
+ .direction_output = ad74115_gpio_direction_output,
+ .get = ad74115_gpio_get,
+ .set = ad74115_gpio_set,
+ };
+
+ return devm_gpiochip_add_data(dev, &st->gc, st);
+}
+
+static int ad74115_setup_comp_gpio_chip(struct ad74115_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74115_DIN_CONFIG1_REG, &val);
+ if (ret)
+ return ret;
+
+ if (!(val & AD74115_DIN_COMPARATOR_EN_MASK))
+ return 0;
+
+ st->comp_gc = (struct gpio_chip) {
+ .owner = THIS_MODULE,
+ .label = AD74115_NAME,
+ .base = -1,
+ .ngpio = 1,
+ .parent = dev,
+ .can_sleep = true,
+ .get_direction = ad74115_comp_gpio_get_direction,
+ .get = ad74115_comp_gpio_get,
+ .set_config = ad74115_comp_gpio_set_config,
+ };
+
+ return devm_gpiochip_add_data(dev, &st->comp_gc, st);
+}
+
+static int ad74115_setup(struct iio_dev *indio_dev)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ u32 val, din_range_high;
+ unsigned int i;
+ int ret;
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_ch_func_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ indio_dev->num_channels += ad74115_channels_map[val].num_channels;
+ st->ch_func = val;
+
+ ret = ad74115_setup_adc_conv2_range(st);
+ if (ret)
+ return ret;
+
+ val = device_property_read_bool(dev, "adi,dac-hart-slew");
+ if (val) {
+ st->dac_hart_slew = val;
+
+ ret = regmap_update_bits(st->regmap, AD74115_OUTPUT_CONFIG_REG,
+ AD74115_OUTPUT_SLEW_EN_MASK,
+ FIELD_PREP(AD74115_OUTPUT_SLEW_EN_MASK,
+ AD74115_SLEW_MODE_HART));
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_din_range_fw_prop,
+ &din_range_high);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_u32(dev, "adi,digital-input-sink-microamp", &val);
+ if (!ret) {
+ if (din_range_high)
+ val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_LOW_STEP);
+ else
+ val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_HIGH_STEP);
+
+ if (val > AD74115_DIN_SINK_MAX)
+ val = AD74115_DIN_SINK_MAX;
+
+ ret = regmap_update_bits(st->regmap, AD74115_DIN_CONFIG1_REG,
+ AD74115_DIN_SINK_MASK,
+ FIELD_PREP(AD74115_DIN_SINK_MASK, val));
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_din_threshold_mode_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ if (val == AD74115_DIN_THRESHOLD_MODE_AVDD && !st->avdd_mv)
+ return dev_err_probe(dev, -EINVAL,
+ "AVDD voltage is required for digital input threshold mode AVDD\n");
+
+ st->din_threshold_mode = val;
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_dac_bipolar_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ st->dac_bipolar = val;
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_rtd_mode_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ st->rtd_mode_4_wire = val;
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_ext2_burnout_current_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG,
+ AD74115_BURNOUT_EXT2_EN_MASK,
+ FIELD_PREP(AD74115_BURNOUT_EXT2_EN_MASK, 1));
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_ext1_burnout_current_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG,
+ AD74115_BURNOUT_EXT1_EN_MASK,
+ FIELD_PREP(AD74115_BURNOUT_EXT1_EN_MASK, 1));
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74115_apply_fw_prop(st, &ad74115_viout_burnout_current_fw_prop, &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG,
+ AD74115_BURNOUT_VIOUT_EN_MASK,
+ FIELD_PREP(AD74115_BURNOUT_VIOUT_EN_MASK, 1));
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < AD74115_GPIO_NUM; i++) {
+ ret = ad74115_apply_fw_prop(st, &ad74115_gpio_mode_fw_props[i], &val);
+ if (ret)
+ return ret;
+
+ if (val == AD74115_GPIO_MODE_LOGIC)
+ st->gpio_valid_mask |= BIT(i);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ad74115_fw_props); i++) {
+ ret = ad74115_apply_fw_prop(st, &ad74115_fw_props[i], &val);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74115_setup_gpio_chip(st);
+ if (ret)
+ return ret;
+
+ ret = ad74115_setup_comp_gpio_chip(st);
+ if (ret)
+ return ret;
+
+ return ad74115_setup_iio_channels(indio_dev);
+}
+
+static int ad74115_reset(struct ad74115_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct gpio_desc *reset_gpio;
+ int ret;
+
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(reset_gpio),
+ "Failed to find reset GPIO\n");
+
+ if (reset_gpio) {
+ fsleep(100);
+
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ } else {
+ ret = regmap_write(st->regmap, AD74115_CMD_KEY_REG,
+ AD74115_CMD_KEY_RESET1);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD74115_CMD_KEY_REG,
+ AD74115_CMD_KEY_RESET2);
+ if (ret)
+ return ret;
+ }
+
+ fsleep(1000);
+
+ return 0;
+}
+
+static int ad74115_setup_trigger(struct iio_dev *indio_dev)
+{
+ struct ad74115_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "adc_rdy");
+
+ if (st->irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (st->irq < 0) {
+ st->irq = 0;
+ return 0;
+ }
+
+ ret = devm_request_irq(dev, st->irq, ad74115_adc_data_interrupt,
+ 0, AD74115_NAME, indio_dev);
+ if (ret)
+ return ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", AD74115_NAME,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad74115_trigger_ops;
+ iio_trigger_set_drvdata(st->trig, st);
+
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ return 0;
+}
+
+static int ad74115_probe(struct spi_device *spi)
+{
+ static const char * const regulator_names[] = {
+ "avcc", "dvcc", "dovdd", "refin",
+ };
+ struct device *dev = &spi->dev;
+ struct ad74115_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->spi = spi;
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ init_completion(&st->adc_data_completion);
+
+ indio_dev->name = AD74115_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad74115_info;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (ret < 0) {
+ /*
+ * Since this is both a power supply and only optionally a
+ * reference voltage, make sure to enable it even when the
+ * voltage is not available.
+ */
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable avdd\n");
+ } else {
+ st->avdd_mv = ret / 1000;
+ }
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return ret;
+
+ st->regmap = devm_regmap_init(dev, NULL, st, &ad74115_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = ad74115_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ad74115_setup(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad74115_setup_trigger(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ ad74115_trigger_handler,
+ &ad74115_buffer_ops);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ad74115_unregister_driver(struct spi_driver *spi)
+{
+ spi_unregister_driver(spi);
+
+ return 0;
+}
+
+static int __init ad74115_register_driver(struct spi_driver *spi)
+{
+ crc8_populate_msb(ad74115_crc8_table, AD74115_CRC_POLYNOMIAL);
+
+ return spi_register_driver(spi);
+}
+
+static const struct spi_device_id ad74115_spi_id[] = {
+ { .name = "ad74115h" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(spi, ad74115_spi_id);
+
+static const struct of_device_id ad74115_dt_id[] = {
+ { .compatible = "adi,ad74115h" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad74115_dt_id);
+
+static struct spi_driver ad74115_driver = {
+ .driver = {
+ .name = "ad74115",
+ .of_match_table = ad74115_dt_id,
+ },
+ .probe = ad74115_probe,
+ .id_table = ad74115_spi_id,
+};
+
+module_driver(ad74115_driver,
+ ad74115_register_driver, ad74115_unregister_driver);
+
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD74115 ADDAC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/addac/ad74413r.c b/drivers/iio/addac/ad74413r.c
new file mode 100644
index 000000000..2e5a58e48
--- /dev/null
+++ b/drivers/iio/addac/ad74413r.c
@@ -0,0 +1,1531 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/interrupt.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include <dt-bindings/iio/addac/adi,ad74413r.h>
+
+#define AD74413R_CRC_POLYNOMIAL 0x7
+DECLARE_CRC8_TABLE(ad74413r_crc8_table);
+
+#define AD74413R_CHANNEL_MAX 4
+
+#define AD74413R_FRAME_SIZE 4
+
+struct ad74413r_chip_info {
+ const char *name;
+ bool hart_support;
+};
+
+struct ad74413r_channel_config {
+ u32 func;
+ u32 drive_strength;
+ bool gpo_comparator;
+ bool initialized;
+};
+
+struct ad74413r_channels {
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+};
+
+struct ad74413r_state {
+ struct ad74413r_channel_config channel_configs[AD74413R_CHANNEL_MAX];
+ unsigned int gpo_gpio_offsets[AD74413R_CHANNEL_MAX];
+ unsigned int comp_gpio_offsets[AD74413R_CHANNEL_MAX];
+ struct gpio_chip gpo_gpiochip;
+ struct gpio_chip comp_gpiochip;
+ struct completion adc_data_completion;
+ unsigned int num_gpo_gpios;
+ unsigned int num_comparator_gpios;
+ u32 sense_resistor_ohms;
+ int refin_reg_uv;
+ /*
+ * Synchronize consecutive operations when doing a one-shot
+ * conversion and when updating the ADC samples SPI message.
+ */
+ struct mutex lock;
+
+ const struct ad74413r_chip_info *chip_info;
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct device *dev;
+ struct iio_trigger *trig;
+
+ size_t adc_active_channels;
+ struct spi_message adc_samples_msg;
+ struct spi_transfer adc_samples_xfer[AD74413R_CHANNEL_MAX + 1];
+
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ */
+ struct {
+ u8 rx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX];
+ aligned_s64 timestamp;
+ } adc_samples_buf __aligned(IIO_DMA_MINALIGN);
+
+ u8 adc_samples_tx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX];
+ u8 reg_tx_buf[AD74413R_FRAME_SIZE];
+ u8 reg_rx_buf[AD74413R_FRAME_SIZE];
+};
+
+#define AD74413R_REG_NOP 0x00
+
+#define AD74413R_REG_CH_FUNC_SETUP_X(x) (0x01 + (x))
+#define AD74413R_CH_FUNC_SETUP_MASK GENMASK(3, 0)
+
+#define AD74413R_REG_ADC_CONFIG_X(x) (0x05 + (x))
+#define AD74413R_ADC_CONFIG_RANGE_MASK GENMASK(7, 5)
+#define AD74413R_ADC_CONFIG_REJECTION_MASK GENMASK(4, 3)
+#define AD74413R_ADC_CONFIG_CH_200K_TO_GND BIT(2)
+#define AD74413R_ADC_RANGE_10V 0b000
+#define AD74413R_ADC_RANGE_2P5V_EXT_POW 0b001
+#define AD74413R_ADC_RANGE_2P5V_INT_POW 0b010
+#define AD74413R_ADC_RANGE_5V_BI_DIR 0b011
+#define AD74413R_ADC_REJECTION_50_60 0b00
+#define AD74413R_ADC_REJECTION_NONE 0b01
+#define AD74413R_ADC_REJECTION_50_60_HART 0b10
+#define AD74413R_ADC_REJECTION_HART 0b11
+
+#define AD74413R_REG_DIN_CONFIG_X(x) (0x09 + (x))
+#define AD74413R_DIN_DEBOUNCE_MASK GENMASK(4, 0)
+#define AD74413R_DIN_DEBOUNCE_LEN BIT(5)
+#define AD74413R_DIN_SINK_MASK GENMASK(9, 6)
+
+#define AD74413R_REG_DAC_CODE_X(x) (0x16 + (x))
+#define AD74413R_DAC_CODE_MAX GENMASK(12, 0)
+#define AD74413R_DAC_VOLTAGE_MAX 11000
+
+#define AD74413R_REG_GPO_PAR_DATA 0x0d
+#define AD74413R_REG_GPO_CONFIG_X(x) (0x0e + (x))
+#define AD74413R_GPO_CONFIG_DATA_MASK BIT(3)
+#define AD74413R_GPO_CONFIG_SELECT_MASK GENMASK(2, 0)
+#define AD74413R_GPO_CONFIG_100K_PULL_DOWN 0b000
+#define AD74413R_GPO_CONFIG_LOGIC 0b001
+#define AD74413R_GPO_CONFIG_LOGIC_PARALLEL 0b010
+#define AD74413R_GPO_CONFIG_COMPARATOR 0b011
+#define AD74413R_GPO_CONFIG_HIGH_IMPEDANCE 0b100
+
+#define AD74413R_REG_ADC_CONV_CTRL 0x23
+#define AD74413R_CONV_SEQ_MASK GENMASK(9, 8)
+#define AD74413R_CONV_SEQ_ON 0b00
+#define AD74413R_CONV_SEQ_SINGLE 0b01
+#define AD74413R_CONV_SEQ_CONTINUOUS 0b10
+#define AD74413R_CONV_SEQ_OFF 0b11
+#define AD74413R_CH_EN_MASK(x) BIT(x)
+
+#define AD74413R_REG_DIN_COMP_OUT 0x25
+
+#define AD74413R_REG_ADC_RESULT_X(x) (0x26 + (x))
+#define AD74413R_ADC_RESULT_MAX GENMASK(15, 0)
+
+#define AD74413R_REG_READ_SELECT 0x41
+
+#define AD74413R_REG_CMD_KEY 0x44
+#define AD74413R_CMD_KEY_LDAC 0x953a
+#define AD74413R_CMD_KEY_RESET1 0x15fa
+#define AD74413R_CMD_KEY_RESET2 0xaf51
+
+static const int ad74413r_adc_sampling_rates[] = {
+ 20, 4800,
+};
+
+static const int ad74413r_adc_sampling_rates_hart[] = {
+ 10, 20, 1200, 4800,
+};
+
+static int ad74413r_crc(u8 *buf)
+{
+ return crc8(ad74413r_crc8_table, buf, 3, 0);
+}
+
+static void ad74413r_format_reg_write(u8 reg, u16 val, u8 *buf)
+{
+ buf[0] = reg;
+ put_unaligned_be16(val, &buf[1]);
+ buf[3] = ad74413r_crc(buf);
+}
+
+static int ad74413r_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct ad74413r_state *st = context;
+
+ ad74413r_format_reg_write(reg, val, st->reg_tx_buf);
+
+ return spi_write(st->spi, st->reg_tx_buf, AD74413R_FRAME_SIZE);
+}
+
+static int ad74413r_crc_check(struct ad74413r_state *st, u8 *buf)
+{
+ u8 expected_crc = ad74413r_crc(buf);
+
+ if (buf[3] != expected_crc) {
+ dev_err(st->dev, "Bad CRC %02x for %02x%02x%02x\n",
+ buf[3], buf[0], buf[1], buf[2]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ad74413r_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct ad74413r_state *st = context;
+ struct spi_transfer reg_read_xfer[] = {
+ {
+ .tx_buf = st->reg_tx_buf,
+ .len = AD74413R_FRAME_SIZE,
+ .cs_change = 1,
+ },
+ {
+ .rx_buf = st->reg_rx_buf,
+ .len = AD74413R_FRAME_SIZE,
+ },
+ };
+ int ret;
+
+ ad74413r_format_reg_write(AD74413R_REG_READ_SELECT, reg,
+ st->reg_tx_buf);
+
+ ret = spi_sync_transfer(st->spi, reg_read_xfer,
+ ARRAY_SIZE(reg_read_xfer));
+ if (ret)
+ return ret;
+
+ ret = ad74413r_crc_check(st, st->reg_rx_buf);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&st->reg_rx_buf[1]);
+
+ return 0;
+}
+
+static const struct regmap_config ad74413r_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .reg_read = ad74413r_reg_read,
+ .reg_write = ad74413r_reg_write,
+};
+
+static int ad74413r_set_gpo_config(struct ad74413r_state *st,
+ unsigned int offset, u8 mode)
+{
+ return regmap_update_bits(st->regmap, AD74413R_REG_GPO_CONFIG_X(offset),
+ AD74413R_GPO_CONFIG_SELECT_MASK, mode);
+}
+
+static const unsigned int ad74413r_debounce_map[AD74413R_DIN_DEBOUNCE_LEN] = {
+ 0, 13, 18, 24, 32, 42, 56, 75,
+ 100, 130, 180, 240, 320, 420, 560, 750,
+ 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500,
+ 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000,
+};
+
+static int ad74413r_set_comp_debounce(struct ad74413r_state *st,
+ unsigned int offset,
+ unsigned int debounce)
+{
+ unsigned int val = AD74413R_DIN_DEBOUNCE_LEN - 1;
+ unsigned int i;
+
+ for (i = 0; i < AD74413R_DIN_DEBOUNCE_LEN; i++)
+ if (debounce <= ad74413r_debounce_map[i]) {
+ val = i;
+ break;
+ }
+
+ return regmap_update_bits(st->regmap,
+ AD74413R_REG_DIN_CONFIG_X(offset),
+ AD74413R_DIN_DEBOUNCE_MASK,
+ val);
+}
+
+static int ad74413r_set_comp_drive_strength(struct ad74413r_state *st,
+ unsigned int offset,
+ unsigned int strength)
+{
+ strength = min(strength, 1800U);
+
+ return regmap_update_bits(st->regmap, AD74413R_REG_DIN_CONFIG_X(offset),
+ AD74413R_DIN_SINK_MASK,
+ FIELD_PREP(AD74413R_DIN_SINK_MASK, strength / 120));
+}
+
+
+static int ad74413r_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int val)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned int real_offset = st->gpo_gpio_offsets[offset];
+ int ret;
+
+ ret = ad74413r_set_gpo_config(st, real_offset,
+ AD74413R_GPO_CONFIG_LOGIC);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap,
+ AD74413R_REG_GPO_CONFIG_X(real_offset),
+ AD74413R_GPO_CONFIG_DATA_MASK,
+ val ? AD74413R_GPO_CONFIG_DATA_MASK : 0);
+}
+
+static int ad74413r_gpio_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned long real_mask = 0;
+ unsigned long real_bits = 0;
+ unsigned int offset;
+ int ret;
+
+ for_each_set_bit(offset, mask, chip->ngpio) {
+ unsigned int real_offset = st->gpo_gpio_offsets[offset];
+
+ ret = ad74413r_set_gpo_config(st, real_offset,
+ AD74413R_GPO_CONFIG_LOGIC_PARALLEL);
+ if (ret)
+ return ret;
+
+ real_mask |= BIT(real_offset);
+ if (*bits & offset)
+ real_bits |= BIT(real_offset);
+ }
+
+ return regmap_update_bits(st->regmap, AD74413R_REG_GPO_PAR_DATA,
+ real_mask, real_bits);
+}
+
+static int ad74413r_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned int real_offset = st->comp_gpio_offsets[offset];
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &status);
+ if (ret)
+ return ret;
+
+ status &= BIT(real_offset);
+
+ return status ? 1 : 0;
+}
+
+static int ad74413r_gpio_get_multiple(struct gpio_chip *chip,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned int offset;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &val);
+ if (ret)
+ return ret;
+
+ for_each_set_bit(offset, mask, chip->ngpio) {
+ unsigned int real_offset = st->comp_gpio_offsets[offset];
+
+ __assign_bit(offset, bits, val & BIT(real_offset));
+ }
+
+ return ret;
+}
+
+static int ad74413r_gpio_get_gpo_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ad74413r_gpio_get_comp_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int ad74413r_gpio_set_gpo_config(struct gpio_chip *chip,
+ unsigned int offset,
+ unsigned long config)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned int real_offset = st->gpo_gpio_offsets[offset];
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return ad74413r_set_gpo_config(st, real_offset,
+ AD74413R_GPO_CONFIG_100K_PULL_DOWN);
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ return ad74413r_set_gpo_config(st, real_offset,
+ AD74413R_GPO_CONFIG_HIGH_IMPEDANCE);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ad74413r_gpio_set_comp_config(struct gpio_chip *chip,
+ unsigned int offset,
+ unsigned long config)
+{
+ struct ad74413r_state *st = gpiochip_get_data(chip);
+ unsigned int real_offset = st->comp_gpio_offsets[offset];
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return ad74413r_set_comp_debounce(st, real_offset,
+ pinconf_to_config_argument(config));
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ad74413r_reset(struct ad74413r_state *st)
+{
+ struct gpio_desc *reset_gpio;
+ int ret;
+
+ reset_gpio = devm_gpiod_get_optional(st->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+
+ if (reset_gpio) {
+ fsleep(50);
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ return 0;
+ }
+
+ ret = regmap_write(st->regmap, AD74413R_REG_CMD_KEY,
+ AD74413R_CMD_KEY_RESET1);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD74413R_REG_CMD_KEY,
+ AD74413R_CMD_KEY_RESET2);
+}
+
+static int ad74413r_set_channel_dac_code(struct ad74413r_state *st,
+ unsigned int channel, int dac_code)
+{
+ struct reg_sequence reg_seq[2] = {
+ { AD74413R_REG_DAC_CODE_X(channel), dac_code },
+ { AD74413R_REG_CMD_KEY, AD74413R_CMD_KEY_LDAC },
+ };
+
+ return regmap_multi_reg_write(st->regmap, reg_seq, 2);
+}
+
+static int ad74413r_set_channel_function(struct ad74413r_state *st,
+ unsigned int channel, u8 func)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap,
+ AD74413R_REG_CH_FUNC_SETUP_X(channel),
+ AD74413R_CH_FUNC_SETUP_MASK,
+ CH_FUNC_HIGH_IMPEDANCE);
+ if (ret)
+ return ret;
+
+ /* Set DAC code to 0 prior to changing channel function */
+ ret = ad74413r_set_channel_dac_code(st, channel, 0);
+ if (ret)
+ return ret;
+
+ /* Delay required before transition to new desired mode */
+ usleep_range(130, 150);
+
+ ret = regmap_update_bits(st->regmap,
+ AD74413R_REG_CH_FUNC_SETUP_X(channel),
+ AD74413R_CH_FUNC_SETUP_MASK, func);
+ if (ret)
+ return ret;
+
+ /* Delay required before updating the new DAC code */
+ usleep_range(150, 170);
+
+ if (func == CH_FUNC_CURRENT_INPUT_LOOP_POWER)
+ ret = regmap_set_bits(st->regmap,
+ AD74413R_REG_ADC_CONFIG_X(channel),
+ AD74413R_ADC_CONFIG_CH_200K_TO_GND);
+
+ return ret;
+}
+
+static int ad74413r_set_adc_conv_seq(struct ad74413r_state *st,
+ unsigned int status)
+{
+ int ret;
+
+ /*
+ * These bits do not clear when a conversion completes.
+ * To enable a subsequent conversion, repeat the write.
+ */
+ ret = regmap_write_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL,
+ AD74413R_CONV_SEQ_MASK,
+ FIELD_PREP(AD74413R_CONV_SEQ_MASK, status));
+ if (ret)
+ return ret;
+
+ /*
+ * Wait 100us before starting conversions.
+ */
+ usleep_range(100, 120);
+
+ return 0;
+}
+
+static int ad74413r_set_adc_channel_enable(struct ad74413r_state *st,
+ unsigned int channel,
+ bool status)
+{
+ return regmap_update_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL,
+ AD74413R_CH_EN_MASK(channel),
+ status ? AD74413R_CH_EN_MASK(channel) : 0);
+}
+
+static int ad74413r_get_adc_range(struct ad74413r_state *st,
+ unsigned int channel,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(AD74413R_ADC_CONFIG_RANGE_MASK, *val);
+
+ return 0;
+}
+
+static int ad74413r_get_adc_rejection(struct ad74413r_state *st,
+ unsigned int channel,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(AD74413R_ADC_CONFIG_REJECTION_MASK, *val);
+
+ return 0;
+}
+
+static int ad74413r_set_adc_rejection(struct ad74413r_state *st,
+ unsigned int channel,
+ unsigned int val)
+{
+ return regmap_update_bits(st->regmap,
+ AD74413R_REG_ADC_CONFIG_X(channel),
+ AD74413R_ADC_CONFIG_REJECTION_MASK,
+ FIELD_PREP(AD74413R_ADC_CONFIG_REJECTION_MASK,
+ val));
+}
+
+static int ad74413r_rejection_to_rate(struct ad74413r_state *st,
+ unsigned int rej, int *val)
+{
+ switch (rej) {
+ case AD74413R_ADC_REJECTION_50_60:
+ *val = 20;
+ return 0;
+ case AD74413R_ADC_REJECTION_NONE:
+ *val = 4800;
+ return 0;
+ case AD74413R_ADC_REJECTION_50_60_HART:
+ *val = 10;
+ return 0;
+ case AD74413R_ADC_REJECTION_HART:
+ *val = 1200;
+ return 0;
+ default:
+ dev_err(st->dev, "ADC rejection invalid\n");
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_rate_to_rejection(struct ad74413r_state *st,
+ int rate, unsigned int *val)
+{
+ switch (rate) {
+ case 20:
+ *val = AD74413R_ADC_REJECTION_50_60;
+ return 0;
+ case 4800:
+ *val = AD74413R_ADC_REJECTION_NONE;
+ return 0;
+ case 10:
+ *val = AD74413R_ADC_REJECTION_50_60_HART;
+ return 0;
+ case 1200:
+ *val = AD74413R_ADC_REJECTION_HART;
+ return 0;
+ default:
+ dev_err(st->dev, "ADC rate invalid\n");
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_range_to_voltage_range(struct ad74413r_state *st,
+ unsigned int range, int *val)
+{
+ switch (range) {
+ case AD74413R_ADC_RANGE_10V:
+ *val = 10000;
+ return 0;
+ case AD74413R_ADC_RANGE_2P5V_EXT_POW:
+ case AD74413R_ADC_RANGE_2P5V_INT_POW:
+ *val = 2500;
+ return 0;
+ case AD74413R_ADC_RANGE_5V_BI_DIR:
+ *val = 5000;
+ return 0;
+ default:
+ dev_err(st->dev, "ADC range invalid\n");
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_range_to_voltage_offset(struct ad74413r_state *st,
+ unsigned int range, int *val)
+{
+ switch (range) {
+ case AD74413R_ADC_RANGE_10V:
+ case AD74413R_ADC_RANGE_2P5V_EXT_POW:
+ *val = 0;
+ return 0;
+ case AD74413R_ADC_RANGE_2P5V_INT_POW:
+ case AD74413R_ADC_RANGE_5V_BI_DIR:
+ *val = -2500;
+ return 0;
+ default:
+ dev_err(st->dev, "ADC range invalid\n");
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_range_to_voltage_offset_raw(struct ad74413r_state *st,
+ unsigned int range, int *val)
+{
+ switch (range) {
+ case AD74413R_ADC_RANGE_10V:
+ case AD74413R_ADC_RANGE_2P5V_EXT_POW:
+ *val = 0;
+ return 0;
+ case AD74413R_ADC_RANGE_2P5V_INT_POW:
+ *val = -((int)AD74413R_ADC_RESULT_MAX);
+ return 0;
+ case AD74413R_ADC_RANGE_5V_BI_DIR:
+ *val = -((int)AD74413R_ADC_RESULT_MAX / 2);
+ return 0;
+ default:
+ dev_err(st->dev, "ADC range invalid\n");
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_get_output_voltage_scale(struct ad74413r_state *st,
+ int *val, int *val2)
+{
+ *val = AD74413R_DAC_VOLTAGE_MAX;
+ *val2 = AD74413R_DAC_CODE_MAX;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74413r_get_output_current_scale(struct ad74413r_state *st,
+ int *val, int *val2)
+{
+ *val = st->refin_reg_uv;
+ *val2 = st->sense_resistor_ohms * AD74413R_DAC_CODE_MAX * 1000;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74413r_get_input_voltage_scale(struct ad74413r_state *st,
+ unsigned int channel,
+ int *val, int *val2)
+{
+ unsigned int range;
+ int ret;
+
+ ret = ad74413r_get_adc_range(st, channel, &range);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_range_to_voltage_range(st, range, val);
+ if (ret)
+ return ret;
+
+ *val2 = AD74413R_ADC_RESULT_MAX;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74413r_get_input_voltage_offset(struct ad74413r_state *st,
+ unsigned int channel, int *val)
+{
+ unsigned int range;
+ int ret;
+
+ ret = ad74413r_get_adc_range(st, channel, &range);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_range_to_voltage_offset_raw(st, range, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74413r_get_input_current_scale(struct ad74413r_state *st,
+ unsigned int channel, int *val,
+ int *val2)
+{
+ unsigned int range;
+ int ret;
+
+ ret = ad74413r_get_adc_range(st, channel, &range);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_range_to_voltage_range(st, range, val);
+ if (ret)
+ return ret;
+
+ *val2 = AD74413R_ADC_RESULT_MAX * st->sense_resistor_ohms;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int ad74413r_get_input_current_offset(struct ad74413r_state *st,
+ unsigned int channel, int *val)
+{
+ unsigned int range;
+ int voltage_range;
+ int voltage_offset;
+ int ret;
+
+ ret = ad74413r_get_adc_range(st, channel, &range);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_range_to_voltage_range(st, range, &voltage_range);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_range_to_voltage_offset(st, range, &voltage_offset);
+ if (ret)
+ return ret;
+
+ *val = voltage_offset * (int)AD74413R_ADC_RESULT_MAX / voltage_range;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74413r_get_adc_rate(struct ad74413r_state *st,
+ unsigned int channel, int *val)
+{
+ unsigned int rejection;
+ int ret;
+
+ ret = ad74413r_get_adc_rejection(st, channel, &rejection);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_rejection_to_rate(st, rejection, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74413r_set_adc_rate(struct ad74413r_state *st,
+ unsigned int channel, int val)
+{
+ unsigned int rejection;
+ int ret;
+
+ ret = ad74413r_rate_to_rejection(st, val, &rejection);
+ if (ret)
+ return ret;
+
+ return ad74413r_set_adc_rejection(st, channel, rejection);
+}
+
+static irqreturn_t ad74413r_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ u8 *rx_buf = st->adc_samples_buf.rx_buf;
+ unsigned int i;
+ int ret;
+
+ ret = spi_sync(st->spi, &st->adc_samples_msg);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < st->adc_active_channels; i++)
+ ad74413r_crc_check(st, &rx_buf[i * AD74413R_FRAME_SIZE]);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &st->adc_samples_buf,
+ iio_get_time_ns(indio_dev));
+
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ad74413r_adc_data_interrupt(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ if (iio_buffer_enabled(indio_dev))
+ iio_trigger_poll(st->trig);
+ else
+ complete(&st->adc_data_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int _ad74413r_get_single_adc_result(struct ad74413r_state *st,
+ unsigned int channel, int *val)
+{
+ unsigned int uval;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ reinit_completion(&st->adc_data_completion);
+
+ ret = ad74413r_set_adc_channel_enable(st, channel, true);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_SINGLE);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&st->adc_data_completion,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ ret = regmap_read(st->regmap, AD74413R_REG_ADC_RESULT_X(channel),
+ &uval);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_set_adc_channel_enable(st, channel, false);
+ if (ret)
+ return ret;
+
+ *val = uval;
+
+ return IIO_VAL_INT;
+}
+
+static int ad74413r_get_single_adc_result(struct iio_dev *indio_dev,
+ unsigned int channel, int *val)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = _ad74413r_get_single_adc_result(st, channel, val);
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static void ad74413r_adc_to_resistance_result(int adc_result, int *val)
+{
+ if (adc_result == AD74413R_ADC_RESULT_MAX)
+ adc_result = AD74413R_ADC_RESULT_MAX - 1;
+
+ *val = DIV_ROUND_CLOSEST(adc_result * 2100,
+ AD74413R_ADC_RESULT_MAX - adc_result);
+}
+
+static int ad74413r_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *active_scan_mask)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ struct spi_transfer *xfer = st->adc_samples_xfer;
+ u8 *rx_buf = st->adc_samples_buf.rx_buf;
+ u8 *tx_buf = st->adc_samples_tx_buf;
+ unsigned int channel;
+ int ret = -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ spi_message_init(&st->adc_samples_msg);
+ st->adc_active_channels = 0;
+
+ for_each_clear_bit(channel, active_scan_mask, AD74413R_CHANNEL_MAX) {
+ ret = ad74413r_set_adc_channel_enable(st, channel, false);
+ if (ret)
+ return ret;
+ }
+
+ if (*active_scan_mask == 0)
+ return ret;
+
+ /*
+ * The read select register is used to select which register's value
+ * will be sent by the slave on the next SPI frame.
+ *
+ * Create an SPI message that, on each step, writes to the read select
+ * register to select the ADC result of the next enabled channel, and
+ * reads the ADC result of the previous enabled channel.
+ *
+ * Example:
+ * W: [WCH1] [WCH2] [WCH2] [WCH3] [ ]
+ * R: [ ] [RCH1] [RCH2] [RCH3] [RCH4]
+ */
+
+ for_each_set_bit(channel, active_scan_mask, AD74413R_CHANNEL_MAX) {
+ ret = ad74413r_set_adc_channel_enable(st, channel, true);
+ if (ret)
+ return ret;
+
+ st->adc_active_channels++;
+
+ if (xfer == st->adc_samples_xfer)
+ xfer->rx_buf = NULL;
+ else
+ xfer->rx_buf = rx_buf;
+
+ xfer->tx_buf = tx_buf;
+ xfer->len = AD74413R_FRAME_SIZE;
+ xfer->cs_change = 1;
+
+ ad74413r_format_reg_write(AD74413R_REG_READ_SELECT,
+ AD74413R_REG_ADC_RESULT_X(channel),
+ tx_buf);
+
+ spi_message_add_tail(xfer, &st->adc_samples_msg);
+
+ tx_buf += AD74413R_FRAME_SIZE;
+ if (xfer != st->adc_samples_xfer)
+ rx_buf += AD74413R_FRAME_SIZE;
+ xfer++;
+ }
+
+ xfer->rx_buf = rx_buf;
+ xfer->tx_buf = NULL;
+ xfer->len = AD74413R_FRAME_SIZE;
+ xfer->cs_change = 0;
+
+ spi_message_add_tail(xfer, &st->adc_samples_msg);
+ return 0;
+}
+
+static int ad74413r_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_CONTINUOUS);
+}
+
+static int ad74413r_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF);
+}
+
+static int ad74413r_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->output)
+ return ad74413r_get_output_voltage_scale(st,
+ val, val2);
+ else
+ return ad74413r_get_input_voltage_scale(st,
+ chan->channel, val, val2);
+ case IIO_CURRENT:
+ if (chan->output)
+ return ad74413r_get_output_current_scale(st,
+ val, val2);
+ else
+ return ad74413r_get_input_current_scale(st,
+ chan->channel, val, val2);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ return ad74413r_get_input_voltage_offset(st,
+ chan->channel, val);
+ case IIO_CURRENT:
+ return ad74413r_get_input_current_offset(st,
+ chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ if (chan->output)
+ return -EINVAL;
+
+ return ad74413r_get_single_adc_result(indio_dev, chan->channel,
+ val);
+ case IIO_CHAN_INFO_PROCESSED: {
+ int ret;
+
+ ret = ad74413r_get_single_adc_result(indio_dev, chan->channel,
+ val);
+ if (ret < 0)
+ return ret;
+
+ ad74413r_adc_to_resistance_result(*val, val);
+
+ return ret;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad74413r_get_adc_rate(st, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!chan->output)
+ return -EINVAL;
+
+ if (val < 0 || val > AD74413R_DAC_CODE_MAX) {
+ dev_err(st->dev, "Invalid DAC code\n");
+ return -EINVAL;
+ }
+
+ return ad74413r_set_channel_dac_code(st, chan->channel, val);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad74413r_set_adc_rate(st, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad74413r_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long info)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (st->chip_info->hart_support) {
+ *vals = ad74413r_adc_sampling_rates_hart;
+ *length = ARRAY_SIZE(ad74413r_adc_sampling_rates_hart);
+ } else {
+ *vals = ad74413r_adc_sampling_rates;
+ *length = ARRAY_SIZE(ad74413r_adc_sampling_rates);
+ }
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_buffer_setup_ops ad74413r_buffer_ops = {
+ .postenable = &ad74413r_buffer_postenable,
+ .predisable = &ad74413r_buffer_predisable,
+};
+
+static const struct iio_trigger_ops ad74413r_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static const struct iio_info ad74413r_info = {
+ .read_raw = &ad74413r_read_raw,
+ .write_raw = &ad74413r_write_raw,
+ .read_avail = &ad74413r_read_avail,
+ .update_scan_mode = &ad74413r_update_scan_mode,
+};
+
+#define AD74413R_DAC_CHANNEL(_type, extra_mask_separate) \
+ { \
+ .type = (_type), \
+ .indexed = 1, \
+ .output = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | (extra_mask_separate), \
+ }
+
+#define AD74413R_ADC_CHANNEL(_type, extra_mask_separate) \
+ { \
+ .type = (_type), \
+ .indexed = 1, \
+ .output = 0, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | (extra_mask_separate), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 32, \
+ .shift = 8, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+#define AD74413R_ADC_VOLTAGE_CHANNEL \
+ AD74413R_ADC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE) \
+ | BIT(IIO_CHAN_INFO_OFFSET))
+
+#define AD74413R_ADC_CURRENT_CHANNEL \
+ AD74413R_ADC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE) \
+ | BIT(IIO_CHAN_INFO_OFFSET))
+
+static const struct iio_chan_spec ad74413r_voltage_output_channels[] = {
+ AD74413R_DAC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE)),
+ AD74413R_ADC_CURRENT_CHANNEL,
+};
+
+static const struct iio_chan_spec ad74413r_current_output_channels[] = {
+ AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)),
+ AD74413R_ADC_VOLTAGE_CHANNEL,
+};
+
+static const struct iio_chan_spec ad74413r_voltage_input_channels[] = {
+ AD74413R_ADC_VOLTAGE_CHANNEL,
+};
+
+static const struct iio_chan_spec ad74413r_current_input_channels[] = {
+ AD74413R_ADC_CURRENT_CHANNEL,
+};
+
+static const struct iio_chan_spec ad74413r_current_input_loop_channels[] = {
+ AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)),
+ AD74413R_ADC_CURRENT_CHANNEL,
+};
+
+static const struct iio_chan_spec ad74413r_resistance_input_channels[] = {
+ AD74413R_ADC_CHANNEL(IIO_RESISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)),
+};
+
+static const struct iio_chan_spec ad74413r_digital_input_channels[] = {
+ AD74413R_ADC_VOLTAGE_CHANNEL,
+};
+
+#define _AD74413R_CHANNELS(_channels) \
+ { \
+ .channels = _channels, \
+ .num_channels = ARRAY_SIZE(_channels), \
+ }
+
+#define AD74413R_CHANNELS(name) \
+ _AD74413R_CHANNELS(ad74413r_ ## name ## _channels)
+
+static const struct ad74413r_channels ad74413r_channels_map[] = {
+ [CH_FUNC_HIGH_IMPEDANCE] = AD74413R_CHANNELS(voltage_input),
+ [CH_FUNC_VOLTAGE_OUTPUT] = AD74413R_CHANNELS(voltage_output),
+ [CH_FUNC_CURRENT_OUTPUT] = AD74413R_CHANNELS(current_output),
+ [CH_FUNC_VOLTAGE_INPUT] = AD74413R_CHANNELS(voltage_input),
+ [CH_FUNC_CURRENT_INPUT_EXT_POWER] = AD74413R_CHANNELS(current_input),
+ [CH_FUNC_CURRENT_INPUT_LOOP_POWER] = AD74413R_CHANNELS(current_input_loop),
+ [CH_FUNC_RESISTANCE_INPUT] = AD74413R_CHANNELS(resistance_input),
+ [CH_FUNC_DIGITAL_INPUT_LOGIC] = AD74413R_CHANNELS(digital_input),
+ [CH_FUNC_DIGITAL_INPUT_LOOP_POWER] = AD74413R_CHANNELS(digital_input),
+ [CH_FUNC_CURRENT_INPUT_EXT_POWER_HART] = AD74413R_CHANNELS(current_input),
+ [CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART] = AD74413R_CHANNELS(current_input),
+};
+
+static int ad74413r_parse_channel_config(struct iio_dev *indio_dev,
+ struct fwnode_handle *channel_node)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ struct ad74413r_channel_config *config;
+ u32 index;
+ int ret;
+
+ ret = fwnode_property_read_u32(channel_node, "reg", &index);
+ if (ret) {
+ dev_err(st->dev, "Failed to read channel reg: %d\n", ret);
+ return ret;
+ }
+
+ if (index >= AD74413R_CHANNEL_MAX) {
+ dev_err(st->dev, "Channel index %u is too large\n", index);
+ return -EINVAL;
+ }
+
+ config = &st->channel_configs[index];
+ if (config->initialized) {
+ dev_err(st->dev, "Channel %u already initialized\n", index);
+ return -EINVAL;
+ }
+
+ config->func = CH_FUNC_HIGH_IMPEDANCE;
+ fwnode_property_read_u32(channel_node, "adi,ch-func", &config->func);
+
+ if (config->func < CH_FUNC_MIN || config->func > CH_FUNC_MAX) {
+ dev_err(st->dev, "Invalid channel function %u\n", config->func);
+ return -EINVAL;
+ }
+
+ if (!st->chip_info->hart_support &&
+ (config->func == CH_FUNC_CURRENT_INPUT_EXT_POWER_HART ||
+ config->func == CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART)) {
+ dev_err(st->dev, "Unsupported HART function %u\n", config->func);
+ return -EINVAL;
+ }
+
+ if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC ||
+ config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER)
+ st->num_comparator_gpios++;
+
+ config->gpo_comparator = fwnode_property_read_bool(channel_node,
+ "adi,gpo-comparator");
+
+ fwnode_property_read_u32(channel_node, "drive-strength-microamp",
+ &config->drive_strength);
+
+ if (!config->gpo_comparator)
+ st->num_gpo_gpios++;
+
+ indio_dev->num_channels += ad74413r_channels_map[config->func].num_channels;
+
+ config->initialized = true;
+
+ return 0;
+}
+
+static int ad74413r_parse_channel_configs(struct iio_dev *indio_dev)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ int ret;
+
+ device_for_each_child_node_scoped(st->dev, channel_node) {
+ ret = ad74413r_parse_channel_config(indio_dev, channel_node);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad74413r_setup_channels(struct iio_dev *indio_dev)
+{
+ struct ad74413r_state *st = iio_priv(indio_dev);
+ struct ad74413r_channel_config *config;
+ const struct iio_chan_spec *chans;
+ struct iio_chan_spec *channels;
+ unsigned int i, num_chans, chan_i;
+ int ret;
+
+ channels = devm_kcalloc(st->dev, sizeof(*channels),
+ indio_dev->num_channels, GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ indio_dev->channels = channels;
+
+ for (i = 0; i < AD74413R_CHANNEL_MAX; i++) {
+ config = &st->channel_configs[i];
+ chans = ad74413r_channels_map[config->func].channels;
+ num_chans = ad74413r_channels_map[config->func].num_channels;
+
+ memcpy(channels, chans, num_chans * sizeof(*chans));
+
+ for (chan_i = 0; chan_i < num_chans; chan_i++) {
+ struct iio_chan_spec *chan = &channels[chan_i];
+
+ chan->channel = i;
+ if (chan->output)
+ chan->scan_index = -1;
+ else
+ chan->scan_index = i;
+ }
+
+ ret = ad74413r_set_channel_function(st, i, config->func);
+ if (ret)
+ return ret;
+
+ channels += num_chans;
+ }
+
+ return 0;
+}
+
+static int ad74413r_setup_gpios(struct ad74413r_state *st)
+{
+ struct ad74413r_channel_config *config;
+ unsigned int comp_gpio_i = 0;
+ unsigned int gpo_gpio_i = 0;
+ unsigned int i;
+ u8 gpo_config;
+ u32 strength;
+ int ret;
+
+ for (i = 0; i < AD74413R_CHANNEL_MAX; i++) {
+ config = &st->channel_configs[i];
+
+ if (config->gpo_comparator) {
+ gpo_config = AD74413R_GPO_CONFIG_COMPARATOR;
+ } else {
+ gpo_config = AD74413R_GPO_CONFIG_LOGIC;
+ st->gpo_gpio_offsets[gpo_gpio_i++] = i;
+ }
+
+ if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC ||
+ config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER) {
+ st->comp_gpio_offsets[comp_gpio_i++] = i;
+
+ strength = config->drive_strength;
+ ret = ad74413r_set_comp_drive_strength(st, i, strength);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74413r_set_gpo_config(st, i, gpo_config);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad74413r_probe(struct spi_device *spi)
+{
+ struct ad74413r_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->spi = spi;
+ st->dev = &spi->dev;
+ st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -EINVAL;
+
+ ret = devm_mutex_init(st->dev, &st->lock);
+ if (ret)
+ return ret;
+
+ init_completion(&st->adc_data_completion);
+
+ st->regmap = devm_regmap_init(st->dev, NULL, st,
+ &ad74413r_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = devm_regulator_get_enable_read_voltage(st->dev, "refin");
+ if (ret < 0)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get refin regulator voltage\n");
+ st->refin_reg_uv = ret;
+
+ st->sense_resistor_ohms = 100000000;
+ device_property_read_u32(st->dev, "shunt-resistor-micro-ohms",
+ &st->sense_resistor_ohms);
+ st->sense_resistor_ohms /= 1000000;
+
+ st->trig = devm_iio_trigger_alloc(st->dev, "%s-dev%d",
+ st->chip_info->name, iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad74413r_trigger_ops;
+ iio_trigger_set_drvdata(st->trig, st);
+
+ ret = devm_iio_trigger_register(st->dev, st->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip_info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad74413r_info;
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ ret = ad74413r_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_parse_channel_configs(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_setup_channels(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad74413r_setup_gpios(st);
+ if (ret)
+ return ret;
+
+ if (st->num_gpo_gpios) {
+ st->gpo_gpiochip.owner = THIS_MODULE;
+ st->gpo_gpiochip.label = st->chip_info->name;
+ st->gpo_gpiochip.base = -1;
+ st->gpo_gpiochip.ngpio = st->num_gpo_gpios;
+ st->gpo_gpiochip.parent = st->dev;
+ st->gpo_gpiochip.can_sleep = true;
+ st->gpo_gpiochip.set = ad74413r_gpio_set;
+ st->gpo_gpiochip.set_multiple = ad74413r_gpio_set_multiple;
+ st->gpo_gpiochip.set_config = ad74413r_gpio_set_gpo_config;
+ st->gpo_gpiochip.get_direction =
+ ad74413r_gpio_get_gpo_direction;
+
+ ret = devm_gpiochip_add_data(st->dev, &st->gpo_gpiochip, st);
+ if (ret)
+ return ret;
+ }
+
+ if (st->num_comparator_gpios) {
+ st->comp_gpiochip.owner = THIS_MODULE;
+ st->comp_gpiochip.label = st->chip_info->name;
+ st->comp_gpiochip.base = -1;
+ st->comp_gpiochip.ngpio = st->num_comparator_gpios;
+ st->comp_gpiochip.parent = st->dev;
+ st->comp_gpiochip.can_sleep = true;
+ st->comp_gpiochip.get = ad74413r_gpio_get;
+ st->comp_gpiochip.get_multiple = ad74413r_gpio_get_multiple;
+ st->comp_gpiochip.set_config = ad74413r_gpio_set_comp_config;
+ st->comp_gpiochip.get_direction =
+ ad74413r_gpio_get_comp_direction;
+
+ ret = devm_gpiochip_add_data(st->dev, &st->comp_gpiochip, st);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(st->dev, spi->irq, ad74413r_adc_data_interrupt,
+ 0, st->chip_info->name, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(st->dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad74413r_trigger_handler,
+ &ad74413r_buffer_ops);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(st->dev, indio_dev);
+}
+
+static int ad74413r_unregister_driver(struct spi_driver *spi)
+{
+ spi_unregister_driver(spi);
+
+ return 0;
+}
+
+static int __init ad74413r_register_driver(struct spi_driver *spi)
+{
+ crc8_populate_msb(ad74413r_crc8_table, AD74413R_CRC_POLYNOMIAL);
+
+ return spi_register_driver(spi);
+}
+
+static const struct ad74413r_chip_info ad74412r_chip_info_data = {
+ .hart_support = false,
+ .name = "ad74412r",
+};
+
+static const struct ad74413r_chip_info ad74413r_chip_info_data = {
+ .hart_support = true,
+ .name = "ad74413r",
+};
+
+static const struct of_device_id ad74413r_dt_id[] = {
+ {
+ .compatible = "adi,ad74412r",
+ .data = &ad74412r_chip_info_data,
+ },
+ {
+ .compatible = "adi,ad74413r",
+ .data = &ad74413r_chip_info_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad74413r_dt_id);
+
+static const struct spi_device_id ad74413r_spi_id[] = {
+ { .name = "ad74412r", .driver_data = (kernel_ulong_t)&ad74412r_chip_info_data },
+ { .name = "ad74413r", .driver_data = (kernel_ulong_t)&ad74413r_chip_info_data },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad74413r_spi_id);
+
+static struct spi_driver ad74413r_driver = {
+ .driver = {
+ .name = "ad74413r",
+ .of_match_table = ad74413r_dt_id,
+ },
+ .probe = ad74413r_probe,
+ .id_table = ad74413r_spi_id,
+};
+
+module_driver(ad74413r_driver,
+ ad74413r_register_driver,
+ ad74413r_unregister_driver);
+
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD74413R ADDAC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/addac/stx104.c b/drivers/iio/addac/stx104.c
new file mode 100644
index 000000000..6207eb2fa
--- /dev/null
+++ b/drivers/iio/addac/stx104.c
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the Apex Embedded Systems STX104
+ * Copyright (C) 2016 William Breathitt Gray
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/regmap.h>
+#include <linux/i8254.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define STX104_OUT_CHAN(chan) { \
+ .type = IIO_VOLTAGE, \
+ .channel = chan, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .indexed = 1, \
+ .output = 1 \
+}
+#define STX104_IN_CHAN(chan, diff) { \
+ .type = IIO_VOLTAGE, \
+ .channel = chan, \
+ .channel2 = chan, \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \
+ BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .indexed = 1, \
+ .differential = diff \
+}
+
+#define STX104_NUM_OUT_CHAN 2
+
+#define STX104_EXTENT 16
+
+static unsigned int base[max_num_isa_dev(STX104_EXTENT)];
+static unsigned int num_stx104;
+module_param_hw_array(base, uint, ioport, &num_stx104, 0);
+MODULE_PARM_DESC(base, "Apex Embedded Systems STX104 base addresses");
+
+#define STX104_AIO_BASE 0x0
+#define STX104_SOFTWARE_STROBE STX104_AIO_BASE
+#define STX104_ADC_DATA STX104_AIO_BASE
+#define STX104_ADC_CHANNEL (STX104_AIO_BASE + 0x2)
+#define STX104_DIO_REG (STX104_AIO_BASE + 0x3)
+#define STX104_DAC_BASE (STX104_AIO_BASE + 0x4)
+#define STX104_ADC_STATUS (STX104_AIO_BASE + 0x8)
+#define STX104_ADC_CONTROL (STX104_AIO_BASE + 0x9)
+#define STX104_ADC_CONFIGURATION (STX104_AIO_BASE + 0x11)
+#define STX104_I8254_BASE (STX104_AIO_BASE + 0x12)
+
+#define STX104_AIO_DATA_STRIDE 2
+#define STX104_DAC_OFFSET(_channel) (STX104_DAC_BASE + STX104_AIO_DATA_STRIDE * (_channel))
+
+/* ADC Channel */
+#define STX104_FC GENMASK(3, 0)
+#define STX104_LC GENMASK(7, 4)
+#define STX104_SINGLE_CHANNEL(_channel) \
+ (u8_encode_bits(_channel, STX104_FC) | u8_encode_bits(_channel, STX104_LC))
+
+/* ADC Status */
+#define STX104_SD BIT(5)
+#define STX104_CNV BIT(7)
+#define STX104_DIFFERENTIAL 1
+
+/* ADC Control */
+#define STX104_ALSS GENMASK(1, 0)
+#define STX104_SOFTWARE_TRIGGER u8_encode_bits(0x0, STX104_ALSS)
+
+/* ADC Configuration */
+#define STX104_GAIN GENMASK(1, 0)
+#define STX104_ADBU BIT(2)
+#define STX104_RBK GENMASK(7, 4)
+#define STX104_BIPOLAR 0
+#define STX104_GAIN_X1 0
+#define STX104_GAIN_X2 1
+#define STX104_GAIN_X4 2
+#define STX104_GAIN_X8 3
+
+/**
+ * struct stx104_iio - IIO device private data structure
+ * @lock: synchronization lock to prevent I/O race conditions
+ * @aio_data_map: Regmap for analog I/O data
+ * @aio_ctl_map: Regmap for analog I/O control
+ */
+struct stx104_iio {
+ struct mutex lock;
+ struct regmap *aio_data_map;
+ struct regmap *aio_ctl_map;
+};
+
+static const struct regmap_range aio_ctl_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x0), regmap_reg_range(0x2, 0x2), regmap_reg_range(0x9, 0x9),
+ regmap_reg_range(0x11, 0x11),
+};
+static const struct regmap_range aio_ctl_rd_ranges[] = {
+ regmap_reg_range(0x2, 0x2), regmap_reg_range(0x8, 0x9), regmap_reg_range(0x11, 0x11),
+};
+static const struct regmap_range aio_ctl_volatile_ranges[] = {
+ regmap_reg_range(0x8, 0x8),
+};
+static const struct regmap_access_table aio_ctl_wr_table = {
+ .yes_ranges = aio_ctl_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(aio_ctl_wr_ranges),
+};
+static const struct regmap_access_table aio_ctl_rd_table = {
+ .yes_ranges = aio_ctl_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(aio_ctl_rd_ranges),
+};
+static const struct regmap_access_table aio_ctl_volatile_table = {
+ .yes_ranges = aio_ctl_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(aio_ctl_volatile_ranges),
+};
+
+static const struct regmap_config aio_ctl_regmap_config = {
+ .name = "aio_ctl",
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .reg_base = STX104_AIO_BASE,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &aio_ctl_wr_table,
+ .rd_table = &aio_ctl_rd_table,
+ .volatile_table = &aio_ctl_volatile_table,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_range aio_data_wr_ranges[] = {
+ regmap_reg_range(0x4, 0x6),
+};
+static const struct regmap_range aio_data_rd_ranges[] = {
+ regmap_reg_range(0x0, 0x0),
+};
+static const struct regmap_access_table aio_data_wr_table = {
+ .yes_ranges = aio_data_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(aio_data_wr_ranges),
+};
+static const struct regmap_access_table aio_data_rd_table = {
+ .yes_ranges = aio_data_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(aio_data_rd_ranges),
+};
+
+static const struct regmap_config aio_data_regmap_config = {
+ .name = "aio_data",
+ .reg_bits = 16,
+ .reg_stride = STX104_AIO_DATA_STRIDE,
+ .reg_base = STX104_AIO_BASE,
+ .val_bits = 16,
+ .io_port = true,
+ .wr_table = &aio_data_wr_table,
+ .rd_table = &aio_data_rd_table,
+ .volatile_table = &aio_data_rd_table,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config dio_regmap_config = {
+ .name = "dio",
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .reg_base = STX104_DIO_REG,
+ .val_bits = 8,
+ .io_port = true,
+};
+
+static const struct regmap_range pit_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x3),
+};
+static const struct regmap_range pit_rd_ranges[] = {
+ regmap_reg_range(0x0, 0x2),
+};
+static const struct regmap_access_table pit_wr_table = {
+ .yes_ranges = pit_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(pit_wr_ranges),
+};
+static const struct regmap_access_table pit_rd_table = {
+ .yes_ranges = pit_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(pit_rd_ranges),
+};
+
+static const struct regmap_config pit_regmap_config = {
+ .name = "i8254",
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .reg_base = STX104_I8254_BASE,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &pit_wr_table,
+ .rd_table = &pit_rd_table,
+};
+
+static int stx104_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+ struct stx104_iio *const priv = iio_priv(indio_dev);
+ int err;
+ unsigned int adc_config;
+ unsigned int value;
+ unsigned int adc_status;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config);
+ if (err)
+ return err;
+
+ *val = BIT(u8_get_bits(adc_config, STX104_GAIN));
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_RAW:
+ if (chan->output) {
+ err = regmap_read(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel),
+ &value);
+ if (err)
+ return err;
+ *val = value;
+ return IIO_VAL_INT;
+ }
+
+ mutex_lock(&priv->lock);
+
+ /* select ADC channel */
+ err = regmap_write(priv->aio_ctl_map, STX104_ADC_CHANNEL,
+ STX104_SINGLE_CHANNEL(chan->channel));
+ if (err) {
+ mutex_unlock(&priv->lock);
+ return err;
+ }
+
+ /*
+ * Trigger ADC sample capture by writing to the 8-bit Software Strobe Register and
+ * wait for completion; the conversion time range is 5 microseconds to 53.68 seconds
+ * in steps of 25 nanoseconds. The actual Analog Input Frame Timer time interval is
+ * calculated as:
+ * ai_time_frame_ns = ( AIFT + 1 ) * ( 25 nanoseconds ).
+ * Where 0 <= AIFT <= 2147483648.
+ */
+ err = regmap_write(priv->aio_ctl_map, STX104_SOFTWARE_STROBE, 0);
+ if (err) {
+ mutex_unlock(&priv->lock);
+ return err;
+ }
+ err = regmap_read_poll_timeout(priv->aio_ctl_map, STX104_ADC_STATUS, adc_status,
+ !u8_get_bits(adc_status, STX104_CNV), 0, 53687092);
+ if (err) {
+ mutex_unlock(&priv->lock);
+ return err;
+ }
+
+ err = regmap_read(priv->aio_data_map, STX104_ADC_DATA, &value);
+ if (err) {
+ mutex_unlock(&priv->lock);
+ return err;
+ }
+ *val = value;
+
+ mutex_unlock(&priv->lock);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OFFSET:
+ /* get ADC bipolar/unipolar configuration */
+ err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config);
+ if (err)
+ return err;
+
+ *val = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? -32768 : 0;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* get ADC bipolar/unipolar and gain configuration */
+ err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config);
+ if (err)
+ return err;
+
+ *val = 5;
+ *val2 = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? 14 : 15;
+ *val2 += u8_get_bits(adc_config, STX104_GAIN);
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+
+ return -EINVAL;
+}
+
+static int stx104_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+ struct stx104_iio *const priv = iio_priv(indio_dev);
+ u8 gain;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ /* Only four gain states (x1, x2, x4, x8) */
+ switch (val) {
+ case 1:
+ gain = STX104_GAIN_X1;
+ break;
+ case 2:
+ gain = STX104_GAIN_X2;
+ break;
+ case 4:
+ gain = STX104_GAIN_X4;
+ break;
+ case 8:
+ gain = STX104_GAIN_X8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, gain);
+ case IIO_CHAN_INFO_RAW:
+ if (!chan->output)
+ return -EINVAL;
+
+ if (val < 0 || val > U16_MAX)
+ return -EINVAL;
+
+ return regmap_write(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel), val);
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info stx104_info = {
+ .read_raw = stx104_read_raw,
+ .write_raw = stx104_write_raw
+};
+
+/* single-ended input channels configuration */
+static const struct iio_chan_spec stx104_channels_sing[] = {
+ STX104_OUT_CHAN(0), STX104_OUT_CHAN(1),
+ STX104_IN_CHAN(0, 0), STX104_IN_CHAN(1, 0), STX104_IN_CHAN(2, 0),
+ STX104_IN_CHAN(3, 0), STX104_IN_CHAN(4, 0), STX104_IN_CHAN(5, 0),
+ STX104_IN_CHAN(6, 0), STX104_IN_CHAN(7, 0), STX104_IN_CHAN(8, 0),
+ STX104_IN_CHAN(9, 0), STX104_IN_CHAN(10, 0), STX104_IN_CHAN(11, 0),
+ STX104_IN_CHAN(12, 0), STX104_IN_CHAN(13, 0), STX104_IN_CHAN(14, 0),
+ STX104_IN_CHAN(15, 0)
+};
+/* differential input channels configuration */
+static const struct iio_chan_spec stx104_channels_diff[] = {
+ STX104_OUT_CHAN(0), STX104_OUT_CHAN(1),
+ STX104_IN_CHAN(0, 1), STX104_IN_CHAN(1, 1), STX104_IN_CHAN(2, 1),
+ STX104_IN_CHAN(3, 1), STX104_IN_CHAN(4, 1), STX104_IN_CHAN(5, 1),
+ STX104_IN_CHAN(6, 1), STX104_IN_CHAN(7, 1)
+};
+
+static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op,
+ const unsigned int base, unsigned int offset,
+ unsigned int *const reg, unsigned int *const mask)
+{
+ /* Output lines are located at same register bit offsets as input lines */
+ if (offset >= 4)
+ offset -= 4;
+
+ *reg = base;
+ *mask = BIT(offset);
+
+ return 0;
+}
+
+#define STX104_NGPIO 8
+static const char *stx104_names[STX104_NGPIO] = {
+ "DIN0", "DIN1", "DIN2", "DIN3", "DOUT0", "DOUT1", "DOUT2", "DOUT3"
+};
+
+static int bank_select_i8254(struct regmap *map)
+{
+ const u8 select_i8254[] = { 0x3, 0xB, 0xA };
+ size_t i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(select_i8254); i++) {
+ err = regmap_write_bits(map, STX104_ADC_CONFIGURATION, STX104_RBK, select_i8254[i]);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int stx104_init_hw(struct stx104_iio *const priv)
+{
+ int err;
+
+ /* configure device for software trigger operation */
+ err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONTROL, STX104_SOFTWARE_TRIGGER);
+ if (err)
+ return err;
+
+ /* initialize gain setting to x1 */
+ err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, STX104_GAIN_X1);
+ if (err)
+ return err;
+
+ /* initialize DAC outputs to 0V */
+ err = regmap_write(priv->aio_data_map, STX104_DAC_BASE, 0);
+ if (err)
+ return err;
+ err = regmap_write(priv->aio_data_map, STX104_DAC_BASE + STX104_AIO_DATA_STRIDE, 0);
+ if (err)
+ return err;
+
+ return bank_select_i8254(priv->aio_ctl_map);
+}
+
+static int stx104_probe(struct device *dev, unsigned int id)
+{
+ struct iio_dev *indio_dev;
+ struct stx104_iio *priv;
+ struct gpio_regmap_config gpio_config;
+ struct i8254_regmap_config pit_config;
+ void __iomem *stx104_base;
+ struct regmap *aio_ctl_map;
+ struct regmap *aio_data_map;
+ struct regmap *dio_map;
+ int err;
+ unsigned int adc_status;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ if (!devm_request_region(dev, base[id], STX104_EXTENT,
+ dev_name(dev))) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + STX104_EXTENT);
+ return -EBUSY;
+ }
+
+ stx104_base = devm_ioport_map(dev, base[id], STX104_EXTENT);
+ if (!stx104_base)
+ return -ENOMEM;
+
+ aio_ctl_map = devm_regmap_init_mmio(dev, stx104_base, &aio_ctl_regmap_config);
+ if (IS_ERR(aio_ctl_map))
+ return dev_err_probe(dev, PTR_ERR(aio_ctl_map),
+ "Unable to initialize aio_ctl register map\n");
+
+ aio_data_map = devm_regmap_init_mmio(dev, stx104_base, &aio_data_regmap_config);
+ if (IS_ERR(aio_data_map))
+ return dev_err_probe(dev, PTR_ERR(aio_data_map),
+ "Unable to initialize aio_data register map\n");
+
+ dio_map = devm_regmap_init_mmio(dev, stx104_base, &dio_regmap_config);
+ if (IS_ERR(dio_map))
+ return dev_err_probe(dev, PTR_ERR(dio_map),
+ "Unable to initialize dio register map\n");
+
+ pit_config.map = devm_regmap_init_mmio(dev, stx104_base, &pit_regmap_config);
+ if (IS_ERR(pit_config.map))
+ return dev_err_probe(dev, PTR_ERR(pit_config.map),
+ "Unable to initialize i8254 register map\n");
+
+ priv = iio_priv(indio_dev);
+ priv->aio_ctl_map = aio_ctl_map;
+ priv->aio_data_map = aio_data_map;
+
+ indio_dev->info = &stx104_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ err = regmap_read(aio_ctl_map, STX104_ADC_STATUS, &adc_status);
+ if (err)
+ return err;
+
+ if (u8_get_bits(adc_status, STX104_SD) == STX104_DIFFERENTIAL) {
+ indio_dev->num_channels = ARRAY_SIZE(stx104_channels_diff);
+ indio_dev->channels = stx104_channels_diff;
+ } else {
+ indio_dev->num_channels = ARRAY_SIZE(stx104_channels_sing);
+ indio_dev->channels = stx104_channels_sing;
+ }
+
+ indio_dev->name = dev_name(dev);
+
+ mutex_init(&priv->lock);
+
+ err = stx104_init_hw(priv);
+ if (err)
+ return err;
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err)
+ return err;
+
+ gpio_config = (struct gpio_regmap_config) {
+ .parent = dev,
+ .regmap = dio_map,
+ .ngpio = STX104_NGPIO,
+ .names = stx104_names,
+ .reg_dat_base = GPIO_REGMAP_ADDR(STX104_DIO_REG),
+ .reg_set_base = GPIO_REGMAP_ADDR(STX104_DIO_REG),
+ .ngpio_per_reg = STX104_NGPIO,
+ .reg_mask_xlate = stx104_reg_mask_xlate,
+ .drvdata = dio_map,
+ };
+
+ err = PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+ if (err)
+ return err;
+
+ pit_config.parent = dev;
+
+ return devm_i8254_regmap_register(dev, &pit_config);
+}
+
+static struct isa_driver stx104_driver = {
+ .probe = stx104_probe,
+ .driver = {
+ .name = "stx104"
+ },
+};
+
+module_isa_driver(stx104_driver, num_stx104);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("Apex Embedded Systems STX104 IIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("I8254");