2022-11-03 09:55:39

by Cosmin Tanislav

[permalink] [raw]
Subject: [PATCH v2 0/2] AD74115

The AD74115H is a single-channel, software-configurable, input and
output device for industrial control applications. The AD74115H
provides a wide range of use cases, integrated on a single chip.

These use cases include analog output, analog input, digital output,
digital input, resistance temperature detector (RTD), and thermocouple
measurement capability. The AD74115H also has an integrated HART modem.

A serial peripheral interface (SPI) is used to handle all communications
to the device, including communications with the HART modem. The digital
input and digital outputs can be accessed via the SPI or the
general-purpose input and output (GPIO) pins to support higher
speed data rates.

The device features a 16-bit, sigma-delta analog-to-digital converter
(ADC) and a 14-bit digital-to-analog converter (DAC).
The AD74115H contains a high accuracy 2.5 V on-chip reference that can
be used as the DAC and ADC reference.

V1 -> V2:
* dt-bindings: add spi peripheral allOf
* dt-bindings: remove cs-gpios
* dt-bindings: remove refin supply from example
* dt-bindings: remove status
* sort includes
* ad74115_parse_fw_prop -> ad74115_apply_fw_prop
* ad74115_setup_{comp,}_gc -> ad74115_setup_{comp,}_gpio_chip
* gpiod_get_optional -> devm_gpiod_get_optional
* add support for reset-gpios
* add support for running without an interrupt
* fix comma after terminating member
* fix order of rate and range masks
* fix reset pin wait time and out of reset time
* fix rtd mode reading
* pass chan spec only where needed
* remove -en suffix
* remove default 0 values
* remove diag support
* remove unecessary prop storage
* reorder switch cases to match enum
* set burnout enable bit based on burnout current
* set default value for props
* simplify dac hart slew usage
* simplify prop value validation
* use bool for 4 wire rtd mode
* use bool for burnout current polarity
* use bool for dac sc current limit
* use bool for debounce mode
* use bool for din sink range
* use bool for din threshold mode
* use devm_regulator_bulk_get_enable
* use IIO_VAL_FRACTIONAL for resistance offset
* use struct assignment for gpiochip
* warn on empty prop mask

Cosmin Tanislav (2):
dt-bindings: iio: addac: add AD74115
iio: addac: add AD74115 driver

.../bindings/iio/addac/adi,ad74115.yaml | 370 ++++
MAINTAINERS | 8 +
drivers/iio/addac/Kconfig | 14 +
drivers/iio/addac/Makefile | 1 +
drivers/iio/addac/ad74115.c | 1901 +++++++++++++++++
5 files changed, 2294 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
create mode 100644 drivers/iio/addac/ad74115.c

--
2.38.1



2022-11-03 10:13:03

by Cosmin Tanislav

[permalink] [raw]
Subject: [PATCH v2 2/2] iio: addac: add AD74115 driver

From: Cosmin Tanislav <[email protected]>

The AD74115H is a single-channel, software-configurable, input and
output device for industrial control applications. The AD74115H
provides a wide range of use cases, integrated on a single chip.

These use cases include analog output, analog input, digital output,
digital input, resistance temperature detector (RTD), and thermocouple
measurement capability. The AD74115H also has an integrated HART modem.

A serial peripheral interface (SPI) is used to handle all communications
to the device, including communications with the HART modem. The digital
input and digital outputs can be accessed via the SPI or the
general-purpose input and output (GPIO) pins to support higher
speed data rates.

The device features a 16-bit, sigma-delta analog-to-digital converter
(ADC) and a 14-bit digital-to-analog converter (DAC).
The AD74115H contains a high accuracy 2.5 V on-chip reference that can
be used as the DAC and ADC reference.

Signed-off-by: Cosmin Tanislav <[email protected]>
---
MAINTAINERS | 1 +
drivers/iio/addac/Kconfig | 14 +
drivers/iio/addac/Makefile | 1 +
drivers/iio/addac/ad74115.c | 1901 +++++++++++++++++++++++++++++++++++
4 files changed, 1917 insertions(+)
create mode 100644 drivers/iio/addac/ad74115.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5b5533f54f67..91400ea5591c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1181,6 +1181,7 @@ L: [email protected]
S: Supported
W: http://ez.analog.com/community/linux-device-drivers
F: Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
+F: drivers/iio/addac/ad74115.c

ANALOG DEVICES INC AD74413R DRIVER
M: Cosmin Tanislav <[email protected]>
diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig
index fcf6d2269bfc..2843fcb70e24 100644
--- a/drivers/iio/addac/Kconfig
+++ b/drivers/iio/addac/Kconfig
@@ -5,6 +5,20 @@

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
diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile
index 17de20ef0d8e..577777276e43 100644
--- a/drivers/iio/addac/Makefile
+++ b/drivers/iio/addac/Makefile
@@ -4,5 +4,6 @@
#

# 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 000000000000..648fa8e2496b
--- /dev/null
+++ b/drivers/iio/addac/ad74115.c
@@ -0,0 +1,1901 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <[email protected]>
+ */
+
+#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 <asm/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>
+
+/* 5.10 compatibility */
+static int iio_device_id(struct iio_dev *indio_dev)
+{
+ return indio_dev->id;
+}
+
+#include <linux/slab.h>
+#define IIO_DMA_MINALIGN ARCH_KMALLOC_MINALIGN
+/* end 5.10 compatibility */
+
+#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 {
+ struct iio_chan_spec *channels;
+ unsigned int num_channels;
+};
+
+struct ad74115_state {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+ struct regulator *avdd;
+
+ /*
+ * 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;
+
+ 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_dac_slew_rate_hz_tbl[] = {
+ 4000, 64000, 150000, 240000
+};
+
+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[] = {
+ [AD74115_ADC_CH_CONV1] = 0x44,
+ [AD74115_ADC_CH_CONV2] = 0x46,
+};
+
+static const unsigned int ad74115_adc_ch_en_bit[] = {
+ [AD74115_ADC_CH_CONV1] = BIT(0),
+ [AD74115_ADC_CH_CONV2] = BIT(1),
+};
+
+static const bool ad74115_adc_bipolar[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 int ad74115_adc_conv_mul[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 int AD74115_adc_gain[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 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 void ad74115_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct ad74115_state *st = gpiochip_get_data(gc);
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset),
+ AD74115_GPIO_CONFIG_GPO_DATA,
+ FIELD_PREP(AD74115_GPIO_CONFIG_GPO_DATA, value));
+ if (ret)
+ dev_err(dev, "Failed to set GPIO %u output value, err: %d\n",
+ offset, ret);
+}
+
+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[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[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->spi->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[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;
+
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&st->lock);
+ ret = _ad74115_get_adc_code(st, channel, val);
+ mutex_unlock(&st->lock);
+
+ iio_device_release_direct_mode(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_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) {
+ *val = AD74115_adc_gain[range][1] * AD74115_REF_RESISTOR_OHMS;
+ *val2 = AD74115_adc_gain[range][0];
+
+ if (ad74115_adc_bipolar[range])
+ *val2 *= AD74115_ADC_CODE_HALF;
+ else
+ *val2 *= AD74115_ADC_CODE_MAX;
+
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ *val = ad74115_adc_conv_mul[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_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) {
+ unsigned int d = 10 * AD74115_REF_RESISTOR_OHMS
+ * AD74115_adc_gain[range][1];
+
+ *val = 5;
+
+ if (ad74115_adc_bipolar[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[range][0];
+
+ if (ad74115_adc_bipolar[range])
+ v *= AD74115_ADC_CODE_HALF;
+ else
+ v *= AD74115_ADC_CODE_MAX;
+
+ *val += v;
+ }
+
+ *val2 = d;
+
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ if (ad74115_adc_bipolar[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 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 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 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 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 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 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 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 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-range", AD74115_ADC_RANGE_MAX,
+ AD74115_ADC_CONFIG_REG, GENMASK(9, 7)),
+ AD74115_FW_PROP("adi,conv2-mux", 3,
+ AD74115_ADC_CONFIG_REG, GENMASK(3, 2)),
+
+ AD74115_FW_PROP_BOOL_NEG("adi,sense-agnd-buffer-lp",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(4)),
+ AD74115_FW_PROP_BOOL_NEG("adi,lf-buffer-lp",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(3)),
+ AD74115_FW_PROP_BOOL_NEG("adi,hf-buffer-lp",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(2)),
+ AD74115_FW_PROP_BOOL_NEG("adi,ext2-buffer-lp",
+ AD74115_PWR_OPTIM_CONFIG_REG, BIT(1)),
+ AD74115_FW_PROP_BOOL_NEG("adi,ext1-buffer-lp",
+ 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_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, *chans;
+ unsigned int num_chans;
+
+ channels = devm_kcalloc(dev, sizeof(*channels),
+ indio_dev->num_channels, GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ indio_dev->channels = channels;
+
+ chans = ad74115_channels_map[st->ch_func].channels;
+ num_chans = ad74115_channels_map[st->ch_func].num_channels;
+
+ memcpy(channels, chans, sizeof(*channels) * num_chans);
+
+ 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;
+
+ 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) {
+ ret = regulator_get_voltage(st->avdd);
+ if (ret < 0)
+ return ret;
+
+ st->avdd_mv = ret / 1000;
+ }
+
+ 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 void ad74115_regulator_disable(void *data)
+{
+ regulator_disable(data);
+}
+
+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;
+
+ if (!st->spi->irq)
+ return 0;
+
+ ret = devm_request_irq(dev, st->spi->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", "aldo1v8", "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;
+ mutex_init(&st->lock);
+ init_completion(&st->adc_data_completion);
+
+ indio_dev->name = AD74115_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad74115_info;
+
+ st->avdd = devm_regulator_get(dev, "avdd");
+ if (IS_ERR(st->avdd))
+ return PTR_ERR(st->avdd);
+
+ ret = regulator_enable(st->avdd);
+ if (ret) {
+ dev_err(dev, "Failed to enable avdd regulator\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, ad74115_regulator_disable, st->avdd);
+ if (ret)
+ return ret;
+
+ 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,
+ iio_pollfunc_store_time,
+ 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[] = {
+ { "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 <[email protected]>");
+MODULE_DESCRIPTION("Analog Devices AD74115 ADDAC");
+MODULE_LICENSE("GPL");
--
2.38.1


2022-11-03 10:30:18

by Cosmin Tanislav

[permalink] [raw]
Subject: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

From: Cosmin Tanislav <[email protected]>

The AD74115H is a single-channel, software-configurable, input and
output device for industrial control applications. The AD74115H
provides a wide range of use cases, integrated on a single chip.

These use cases include analog output, analog input, digital output,
digital input, resistance temperature detector (RTD), and thermocouple
measurement capability. The AD74115H also has an integrated HART modem.

A serial peripheral interface (SPI) is used to handle all communications
to the device, including communications with the HART modem. The digital
input and digital outputs can be accessed via the SPI or the
general-purpose input and output (GPIO) pins to support higher
speed data rates.

The device features a 16-bit, sigma-delta analog-to-digital converter
(ADC) and a 14-bit digital-to-analog converter (DAC).
The AD74115H contains a high accuracy 2.5 V on-chip reference that can
be used as the DAC and ADC reference.

Signed-off-by: Cosmin Tanislav <[email protected]>
---
.../bindings/iio/addac/adi,ad74115.yaml | 370 ++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 377 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml

diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
new file mode 100644
index 000000000000..621f11d5c1f3
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
@@ -0,0 +1,370 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/addac/adi,ad74115.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD74115H device
+
+maintainers:
+ - Cosmin Tanislav <[email protected]>
+
+description: |
+ The AD74115H is a single-channel software configurable input/output
+ device for industrial control applications. It contains functionality for
+ analog output, analog input, digital output, digital input, resistance
+ temperature detector, and thermocouple measurements integrated into a single
+ chip solution with an SPI interface. The device features a 16-bit ADC and a
+ 14-bit DAC.
+
+ https://www.analog.com/en/products/ad74115h.html
+
+properties:
+ compatible:
+ enum:
+ - adi,ad74115h
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ spi-max-frequency:
+ maximum: 24000000
+
+ spi-cpol: true
+
+ reset-gpios: true
+
+ interrupts:
+ maxItems: 1
+
+ avdd-supply: true
+ avcc-supply: true
+ dvcc-supply: true
+ aldo1v8-supply: true
+ dovdd-supply: true
+ refin-supply: true
+
+ adi,ch-func:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Channel function.
+ 0 - High impedance
+ 1 - Voltage output
+ 2 - Current output
+ 3 - Voltage input
+ 4 - Current input, externally-powered
+ 5 - Current input, loop-powered
+ 6 - Resistance input
+ 7 - RTD measure
+ 8 - Digital input logic
+ 9 - Digital input, loop-powered
+ 10 - Current output with HART
+ 11 - Current input, externally-powered, with HART
+ 12 - Current input, loop-powered, with HART
+ minimum: 0
+ maximum: 12
+ default: 0
+
+ adi,conv2-mux:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Input node for ADC conversion 2.
+ 0 - SENSE_EXT1 to AGND_SENSE
+ 1 - SENSE_EXT2 to AGND_SENSE
+ 2 - SENSE_EXT2 to SENSE_EXT1
+ 3 - AGND to AGND
+ minimum: 0
+ maximum: 3
+ default: 0
+
+ adi,conv2-range:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Conversion range for ADC conversion 2.
+ 0 - 0V to 12V
+ 1 - -12V to +12V
+ 2 - -2.5V to +2.5V
+ 3 - -2.5V to 0V
+ 4 - 0V to 2.5V
+ 5 - 0V to 0.625V
+ 6 - -104mV to +104mV
+ 7 - 0V to 12V
+ minimum: 0
+ maximum: 7
+ default: 0
+
+ adi,sense-agnd-buffer-lp:
+ type: boolean
+ description: |
+ Whether to enable low-power buffered mode for the AGND sense pin.
+
+ adi,lf-buffer-lp:
+ type: boolean
+ description: |
+ Whether to enable low-power buffered mode for the low-side filtered
+ sense pin.
+
+ adi,hf-buffer-lp:
+ type: boolean
+ description: |
+ Whether to enable low-power buffered mode for the high-side filtered
+ sense pin.
+
+ adi,ext2-buffer-lp:
+ type: boolean
+ description: Whether to enable low-power buffered mode for the EXT2 pin.
+
+ adi,ext1-buffer-lp:
+ type: boolean
+ description: Whether to enable low-power buffered mode for the EXT1 pin.
+
+ adi,comparator-invert:
+ type: boolean
+ description: Whether to invert the comparator output.
+
+ adi,digital-input-sink-range-high:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ When not present, the digital input range is from 0 to 3700uA in steps
+ of 120uA, with a ~2k series resistance.
+ When present, the digital input range is from 0 to 7400uA in steps
+ of 240uA, with a ~1k series resistance.
+
+ adi,digital-input-sink-microamp:
+ description: Sink current in digital input mode.
+ minimum: 0
+ maximum: 3700
+ default: 0
+
+ adi,digital-input-debounce-mode-counter-reset:
+ type: boolean
+ description: |
+ When not present, a counter increments when the signal is asserted
+ and decrements when the signal is de-asserted.
+ When present, a counter increments while the signal is asserted and
+ resets when the signal de-asserts
+
+ adi,digital-input-unbuffered:
+ type: boolean
+ description: Whether to buffer digital input signals.
+
+ adi,digital-input-short-circuit-detection:
+ type: boolean
+ description: Whether to detect digital input short circuits.
+
+ adi,digital-input-open-circuit-detection:
+ type: boolean
+ description: Whether to detect digital input open circuits.
+
+ adi,digital-input-threshold-mode-fixed:
+ type: boolean
+ description: |
+ When not present, the digital input threshold range is -0.96 * AVDD
+ to AVDD.
+ When present, the threshold range is fixed from -19V to 30V.
+
+ adi,dac-bipolar:
+ type: boolean
+ description: |
+ When not present, the DAC operates in the 0V to 12V range.
+ When present, the DAC operates in the -12V to 12V range.
+
+ adi,charge-pump:
+ type: boolean
+ description: Whether to enable the internal charge pump.
+
+ adi,dac-hart-slew:
+ type: boolean
+ description: Whether to use a HART-compatible slew rate.
+
+ adi,dac-current-limit-low:
+ type: boolean
+ description: |
+ When not present, the DAC short-circuit current limit is 32mA in
+ either source or sink for VOUT and 4mA sink for IOUT.
+ When present, the limit is 16mA in either source or sink for VOUT,
+ 1mA sink for IOUT.
+
+ adi,4-wire-rtd:
+ type: boolean
+ description: |
+ When not present, the ADC should be used for measuring 3-wire RTDs.
+ When present, the ADC should be used for measuring 4-wire RTDs.
+
+ adi,3-wire-rtd-excitation-swap:
+ type: boolean
+ description: Whether to swap the excitation for 3-wire RTD.
+
+ adi,rtd-excitation-current-microamp:
+ description: Excitation current to apply to RTD.
+ enum: [250, 500, 750, 1000]
+ default: 250
+
+ adi,ext1-burnout:
+ type: boolean
+ description: Whether to enable burnout current for EXT1.
+
+ adi,ext1-burnout-current-nanoamp:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Burnout current in nanoamps to be applied to EXT1.
+ enum: [0, 50, 500, 1000, 10000]
+ default: 0
+
+ adi,ext1-burnout-current-polarity-sourcing:
+ type: boolean
+ description: |
+ When not present, the burnout current polarity for EXT1 is sinking.
+ When present, the burnout current polarity for EXT1 is sourcing.
+
+ adi,ext2-burnout:
+ type: boolean
+ description: Whether to enable burnout current for EXT2.
+
+ adi,ext2-burnout-current-nanoamp:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Burnout current in nanoamps to be applied to EXT2.
+ enum: [0, 50, 500, 1000, 10000]
+ default: 0
+
+ adi,ext2-burnout-current-polarity-sourcing:
+ type: boolean
+ description: |
+ When not present, the burnout current polarity for EXT2 is sinking.
+ When present, the burnout current polarity for EXT2 is sourcing.
+
+ adi,viout-burnout:
+ type: boolean
+ description: Whether to enable burnout current for VIOUT.
+
+ adi,viout-burnout-current-nanoamp:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Burnout current in nanoamps to be applied to VIOUT.
+ enum: [0, 1000, 10000]
+ default: 0
+
+ adi,viout-burnout-current-polarity-sourcing:
+ type: boolean
+ description: |
+ When not present, the burnout current polarity for VIOUT is sinking.
+ When present, the burnout current polarity for VIOUT is sourcing.
+
+ adi,gpio0-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ GPIO functions.
+ 0 - Disabled
+ 1 - Logic I/O
+ 2 - Comparator output
+ 3 - Control HART CD
+ 4 - Monitor HART CD
+ 5 - Monitor HART EOM status
+ minimum: 0
+ maximum: 5
+ default: 0
+
+ adi,gpio1-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ GPIO functions.
+ 0 - Disabled
+ 1 - Logic I/O
+ 2 - Drive external digital output FET
+ 3 - Control HART RXD
+ 4 - Monitor HART RXD
+ 5 - Monitor HART SOM status
+ minimum: 0
+ maximum: 5
+ default: 0
+
+ adi,gpio2-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ GPIO functions.
+ 0 - Disabled
+ 1 - Logic I/O
+ 2 - Drive internal digital output FET
+ 3 - Control HART TXD
+ 4 - Monitor HART TXD
+ 5 - Monitor HART TX complete status
+ minimum: 0
+ maximum: 5
+ default: 0
+
+ adi,gpio3-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ GPIO functions.
+ 0 - Disabled
+ 1 - Logic I/O
+ 2 - High impedance
+ 3 - Control HART RTS
+ 4 - Monitor HART RTS
+ 5 - Monitor HART CD complete status
+ minimum: 0
+ maximum: 5
+ default: 0
+
+required:
+ - compatible
+ - reg
+ - spi-cpol
+ - avdd-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ adi,digital-input-sink-range-high: true
+ then:
+ properties:
+ adi,digital-input-sink-microamp:
+ maximum: 7400
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ addac@0 {
+ compatible = "adi,ad74115h";
+ reg = <0>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ spi-max-frequency = <12000000>;
+ spi-cpol;
+
+ reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
+
+ interrupt-parent = <&gpio>;
+ interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
+
+ avdd-supply = <&ad74115_avdd>;
+
+ adi,ch-func = <1>;
+ adi,conv2-mux = <2>;
+ adi,conv2-range = <1>;
+
+ adi,gpio0-mode = <1>;
+ adi,gpio1-mode = <1>;
+ adi,gpio2-mode = <1>;
+ adi,gpio3-mode = <1>;
+
+ adi,dac-bipolar;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 7a56c344ca67..5b5533f54f67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1175,6 +1175,13 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad7780.yaml
F: drivers/iio/adc/ad7780.c

+ANALOG DEVICES INC AD74115 DRIVER
+M: Cosmin Tanislav <[email protected]>
+L: [email protected]
+S: Supported
+W: http://ez.analog.com/community/linux-device-drivers
+F: Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
+
ANALOG DEVICES INC AD74413R DRIVER
M: Cosmin Tanislav <[email protected]>
L: [email protected]
--
2.38.1


2022-11-06 15:53:42

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

On Thu, 3 Nov 2022 11:44:35 +0200
Cosmin Tanislav <[email protected]> wrote:

> From: Cosmin Tanislav <[email protected]>
>
> The AD74115H is a single-channel, software-configurable, input and
> output device for industrial control applications. The AD74115H
> provides a wide range of use cases, integrated on a single chip.
>
> These use cases include analog output, analog input, digital output,
> digital input, resistance temperature detector (RTD), and thermocouple
> measurement capability. The AD74115H also has an integrated HART modem.
>
> A serial peripheral interface (SPI) is used to handle all communications
> to the device, including communications with the HART modem. The digital
> input and digital outputs can be accessed via the SPI or the
> general-purpose input and output (GPIO) pins to support higher
> speed data rates.
>
> The device features a 16-bit, sigma-delta analog-to-digital converter
> (ADC) and a 14-bit digital-to-analog converter (DAC).
> The AD74115H contains a high accuracy 2.5 V on-chip reference that can
> be used as the DAC and ADC reference.
>
> Signed-off-by: Cosmin Tanislav <[email protected]>

Hi Cosmin,

A few questions inline. Complex device so I'll doubt we'll ever get this
binding to be as tidy as for simpler devices. Hence most of the below are
suggestions rather than requirements from me.

Jonathan

> ---
> .../bindings/iio/addac/adi,ad74115.yaml | 370 ++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 377 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> new file mode 100644
> index 000000000000..621f11d5c1f3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> @@ -0,0 +1,370 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/addac/adi,ad74115.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD74115H device
> +
> +maintainers:
> + - Cosmin Tanislav <[email protected]>
> +
> +description: |
> + The AD74115H is a single-channel software configurable input/output
> + device for industrial control applications. It contains functionality for
> + analog output, analog input, digital output, digital input, resistance
> + temperature detector, and thermocouple measurements integrated into a single
> + chip solution with an SPI interface. The device features a 16-bit ADC and a
> + 14-bit DAC.
> +
> + https://www.analog.com/en/products/ad74115h.html
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad74115h
> +
> + reg:
> + maxItems: 1
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0

I'm not seeing any child nodes, so why do we need these two?

> +
> + avdd-supply: true
> + avcc-supply: true
> + dvcc-supply: true
> + aldo1v8-supply: true

aldo1v8 is an output pin. "1.8 V Analog LDO Output. Do not use ALDO1V8 externally."
The associated input is avcc. Given we shouldn't connect anything to the pin,
we don't want it in the binding docs

> + dovdd-supply: true
> + refin-supply: true
> +

...

> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + Conversion range for ADC conversion 2.
> + 0 - 0V to 12V
> + 1 - -12V to +12V
> + 2 - -2.5V to +2.5V
> + 3 - -2.5V to 0V
> + 4 - 0V to 2.5V
> + 5 - 0V to 0.625V
> + 6 - -104mV to +104mV
> + 7 - 0V to 12V

For a lot of similar cases we handle these numerically to give
a human readable dts. Is there a strong reason not to do so here (in mv)


> + minimum: 0
> + maximum: 7
> + default: 0
> +
> + adi,sense-agnd-buffer-lp:
lp is a little ambiguous, given we have a habit of using it for low pass
in filters etc. Perhaps worth spelling these out?
adi,sens-agnd-buffer-low-power etc?

> + type: boolean
> + description: |
> + Whether to enable low-power buffered mode for the AGND sense pin.
> +
> + adi,lf-buffer-lp:
> + type: boolean
> + description: |
> + Whether to enable low-power buffered mode for the low-side filtered
> + sense pin.
> +
> + adi,hf-buffer-lp:
> + type: boolean
> + description: |
> + Whether to enable low-power buffered mode for the high-side filtered
> + sense pin.
> +
> + adi,ext2-buffer-lp:
> + type: boolean
> + description: Whether to enable low-power buffered mode for the EXT2 pin.
> +
> + adi,ext1-buffer-lp:
> + type: boolean
> + description: Whether to enable low-power buffered mode for the EXT1 pin.

> +
> +required:
> + - compatible
> + - reg
> + - spi-cpol
> + - avdd-supply
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> + - if:
> + properties:
> + adi,digital-input-sink-range-high: true
> + then:
> + properties:
> + adi,digital-input-sink-microamp:
> + maximum: 7400
> +
> +additionalProperties: false

Does this need to be unevalutatedProperties to allow
for the extra ones in spi-periphera-props.yaml?

> +

2022-11-06 16:45:05

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] iio: addac: add AD74115 driver

On Thu, 3 Nov 2022 11:44:36 +0200
Cosmin Tanislav <[email protected]> wrote:

> From: Cosmin Tanislav <[email protected]>
>
> The AD74115H is a single-channel, software-configurable, input and
> output device for industrial control applications. The AD74115H
> provides a wide range of use cases, integrated on a single chip.
>
> These use cases include analog output, analog input, digital output,
> digital input, resistance temperature detector (RTD), and thermocouple
> measurement capability. The AD74115H also has an integrated HART modem.
>
> A serial peripheral interface (SPI) is used to handle all communications
> to the device, including communications with the HART modem. The digital
> input and digital outputs can be accessed via the SPI or the
> general-purpose input and output (GPIO) pins to support higher
> speed data rates.
>
> The device features a 16-bit, sigma-delta analog-to-digital converter
> (ADC) and a 14-bit digital-to-analog converter (DAC).
> The AD74115H contains a high accuracy 2.5 V on-chip reference that can
> be used as the DAC and ADC reference.
>
> Signed-off-by: Cosmin Tanislav <[email protected]>

Hi Cosmin,

Inevitably there are a few things I noticed this time that I missed before.

I'll be looking for Linus W input on GPIO chip code. Probably best to cc linux-gpio
on next posting so it ends up in correct filtered email locations and in
patchwork etc (not sure if gpio uses patchwork).

Biggest thing in here is around IRQ bindings and how they are handled if
the driver later supports more than one (I think the hardware has several
lines where you might do this). We have walled ourselves into a corner
in the past by not thinking about the binding based on the fact the driver
doesn't yet support it. Best to add to the binding now and then just not
read it in the driver. In short, just use a named IRQ and get it by name
rather than spi->irq.

Jonathan



> diff --git a/drivers/iio/addac/ad74115.c b/drivers/iio/addac/ad74115.c
> new file mode 100644
> index 000000000000..648fa8e2496b
> --- /dev/null
> +++ b/drivers/iio/addac/ad74115.c
> @@ -0,0 +1,1901 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2022 Analog Devices, Inc.
> + * Author: Cosmin Tanislav <[email protected]>
> + */
> +
> +#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 <asm/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>
> +
> +/* 5.10 compatibility */
> +static int iio_device_id(struct iio_dev *indio_dev)
> +{
> + return indio_dev->id;
> +}
> +
> +#include <linux/slab.h>
> +#define IIO_DMA_MINALIGN ARCH_KMALLOC_MINALIGN

This should not be in the upstream driver, though indeed will be needed for
back porting.

> +/* end 5.10 compatibility */
> +

...




> +
> +static const int ad74115_dac_rate_tbl[] = {

For these, it might be nice to have a few comments on units if relevant...

> + 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 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->spi->irq) {

As below. I would not rely on that being the 'right irq'.
It will make a mess of the case where someone attaches just the alert one
and not the adc ready one. The old rule - if someone can wire up an annoying
combination of pins, they will...

> + 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[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_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);

See below. This is another one I think would be nicer as an if /else
> +}
> +


> +
> +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) {
> + unsigned int d = 10 * AD74115_REF_RESISTOR_OHMS
> + * AD74115_adc_gain[range][1];
> +
> + *val = 5;
> +
> + if (ad74115_adc_bipolar[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[range][0];
> +
> + if (ad74115_adc_bipolar[range])
> + v *= AD74115_ADC_CODE_HALF;
> + else
> + v *= AD74115_ADC_CODE_MAX;
> +
> + *val += v;
> + }
> +
> + *val2 = d;
> +
> + return IIO_VAL_FRACTIONAL;
> + }

Really minor but...

Given the indent below here isn't too high, I would prefer an else
} else {
to make it clear that we have two equivalent branches of the code just handling
different channel types.

In other similar cases, there indent is huge, so ignore this nice to have and
indeed do it like you have done here (if block that returns then everything else
without an else statement).

> +
> + if (ad74115_adc_bipolar[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 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,
> +};

> +
> +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, *chans;
> + unsigned int num_chans;
> +
> + channels = devm_kcalloc(dev, sizeof(*channels),
> + indio_dev->num_channels, GFP_KERNEL);
> + if (!channels)
> + return -ENOMEM;
> +
> + indio_dev->channels = channels;
> +
> + chans = ad74115_channels_map[st->ch_func].channels;

I'd find this a little more readable if you just put this parameter directly
into the memcpy call. Having channels and chans is less clear than it could perhaps
be.

> + num_chans = ad74115_channels_map[st->ch_func].num_channels;

Likewise with this.

memcpy(channels, ad74115_channels_map[st->ch_func].channels,
ad74115_channels_map[st->ch_func].num_channels);

isn't too bad.
> +
> + memcpy(channels, chans, sizeof(*channels) * num_chans);
> +
> + 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_trigger(struct iio_dev *indio_dev)
> +{
> + struct ad74115_state *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + if (!st->spi->irq)

Unusual for a chip this complex to only have one thing that would be treated
as an IRQ. I checked the datahsheet and there are at least two (ADC_RDY, ALERT)
Hence use a fwnode_irq_get_by_name() and require the names in the binding.

It's possible people will wire either one individually, or both together and
we need to handle that in the binding. In the driver we can just ignore any
we don't support.

> + return 0;
> +
> + ret = devm_request_irq(dev, st->spi->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)
> +{

...


> + ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> + iio_pollfunc_store_time,

You store the time but then I don't immediately spot it being used.

> + ad74115_trigger_handler,
> + &ad74115_buffer_ops);


> +
> +module_driver(ad74115_driver,
> + ad74115_register_driver,
> + ad74115_unregister_driver);

Trivial but could wrap this less as:
module_driver(ad74115_driver,
ad74115_register_driver, ad74115_unregister_driver);

(or the other obvious combination if you prefer.



2022-11-07 16:02:02

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] iio: addac: add AD74115 driver

Hi Cosmin,

I love your patch! Yet something to improve:

[auto build test ERROR on jic23-iio/togreg]
[also build test ERROR on linus/master v6.1-rc4 next-20221107]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/AD74115/20221103-174734
base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link: https://lore.kernel.org/r/20221103094436.2136698-3-demonsingur%40gmail.com
patch subject: [PATCH v2 2/2] iio: addac: add AD74115 driver
config: arm-randconfig-c002-20221106
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 2bbafe04fe785a9469bea5a3737f8d7d3ce4aca2)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# install arm cross compiling tool for clang build
# apt-get install binutils-arm-linux-gnueabi
# https://github.com/intel-lab-lkp/linux/commit/645566fcbcd39a1b546e8223d4af13fe902df3fc
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Cosmin-Tanislav/AD74115/20221103-174734
git checkout 645566fcbcd39a1b546e8223d4af13fe902df3fc
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=arm SHELL=/bin/bash drivers/iio/addac/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>

All errors (new ones prefixed by >>):

drivers/iio/addac/ad74115.c:28:12: error: static declaration of 'iio_device_id' follows non-static declaration
static int iio_device_id(struct iio_dev *indio_dev)
^
include/linux/iio/iio.h:598:5: note: previous declaration is here
int iio_device_id(struct iio_dev *indio_dev);
^
>> drivers/iio/addac/ad74115.c:30:20: error: no member named 'id' in 'struct iio_dev'
return indio_dev->id;
~~~~~~~~~ ^
drivers/iio/addac/ad74115.c:1828:8: error: call to undeclared function 'devm_regulator_bulk_get_enable'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
^
drivers/iio/addac/ad74115.c:1828:8: note: did you mean 'devm_regulator_bulk_get_const'?
include/linux/regulator/consumer.h:247:18: note: 'devm_regulator_bulk_get_const' declared here
int __must_check devm_regulator_bulk_get_const(
^
3 errors generated.


vim +30 drivers/iio/addac/ad74115.c

26
27 /* 5.10 compatibility */
> 28 static int iio_device_id(struct iio_dev *indio_dev)
29 {
> 30 return indio_dev->id;
31 }
32

--
0-DAY CI Kernel Test Service
https://01.org/lkp


Attachments:
(No filename) (3.11 kB)
config (223.92 kB)
Download all attachments

2022-11-07 17:19:15

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

On Thu, Nov 03, 2022 at 11:44:35AM +0200, Cosmin Tanislav wrote:
> From: Cosmin Tanislav <[email protected]>
>
> The AD74115H is a single-channel, software-configurable, input and
> output device for industrial control applications. The AD74115H
> provides a wide range of use cases, integrated on a single chip.
>
> These use cases include analog output, analog input, digital output,
> digital input, resistance temperature detector (RTD), and thermocouple
> measurement capability. The AD74115H also has an integrated HART modem.
>
> A serial peripheral interface (SPI) is used to handle all communications
> to the device, including communications with the HART modem. The digital
> input and digital outputs can be accessed via the SPI or the
> general-purpose input and output (GPIO) pins to support higher
> speed data rates.
>
> The device features a 16-bit, sigma-delta analog-to-digital converter
> (ADC) and a 14-bit digital-to-analog converter (DAC).
> The AD74115H contains a high accuracy 2.5 V on-chip reference that can
> be used as the DAC and ADC reference.
>
> Signed-off-by: Cosmin Tanislav <[email protected]>
> ---
> .../bindings/iio/addac/adi,ad74115.yaml | 370 ++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 377 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> new file mode 100644
> index 000000000000..621f11d5c1f3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> @@ -0,0 +1,370 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/addac/adi,ad74115.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD74115H device
> +
> +maintainers:
> + - Cosmin Tanislav <[email protected]>
> +
> +description: |
> + The AD74115H is a single-channel software configurable input/output
> + device for industrial control applications. It contains functionality for
> + analog output, analog input, digital output, digital input, resistance
> + temperature detector, and thermocouple measurements integrated into a single
> + chip solution with an SPI interface. The device features a 16-bit ADC and a
> + 14-bit DAC.
> +
> + https://www.analog.com/en/products/ad74115h.html
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad74115h
> +
> + reg:
> + maxItems: 1
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> + spi-max-frequency:
> + maximum: 24000000
> +
> + spi-cpol: true
> +
> + reset-gpios: true
> +
> + interrupts:
> + maxItems: 1
> +
> + avdd-supply: true
> + avcc-supply: true
> + dvcc-supply: true
> + aldo1v8-supply: true
> + dovdd-supply: true
> + refin-supply: true
> +
> + adi,ch-func:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + Channel function.
> + 0 - High impedance
> + 1 - Voltage output
> + 2 - Current output
> + 3 - Voltage input
> + 4 - Current input, externally-powered
> + 5 - Current input, loop-powered
> + 6 - Resistance input
> + 7 - RTD measure
> + 8 - Digital input logic
> + 9 - Digital input, loop-powered
> + 10 - Current output with HART
> + 11 - Current input, externally-powered, with HART
> + 12 - Current input, loop-powered, with HART
> + minimum: 0
> + maximum: 12
> + default: 0
> +
> + adi,conv2-mux:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + Input node for ADC conversion 2.
> + 0 - SENSE_EXT1 to AGND_SENSE
> + 1 - SENSE_EXT2 to AGND_SENSE
> + 2 - SENSE_EXT2 to SENSE_EXT1
> + 3 - AGND to AGND
> + minimum: 0
> + maximum: 3
> + default: 0
> +
> + adi,conv2-range:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + Conversion range for ADC conversion 2.
> + 0 - 0V to 12V
> + 1 - -12V to +12V
> + 2 - -2.5V to +2.5V
> + 3 - -2.5V to 0V
> + 4 - 0V to 2.5V
> + 5 - 0V to 0.625V
> + 6 - -104mV to +104mV
> + 7 - 0V to 12V
> + minimum: 0
> + maximum: 7
> + default: 0
> +
> + adi,sense-agnd-buffer-lp:
> + type: boolean
> + description: |

Don't need '|' if no formatting to preserve.

> + Whether to enable low-power buffered mode for the AGND sense pin.
> +
> + adi,lf-buffer-lp:
> + type: boolean
> + description: |
> + Whether to enable low-power buffered mode for the low-side filtered
> + sense pin.
> +
> + adi,hf-buffer-lp:
> + type: boolean
> + description: |
> + Whether to enable low-power buffered mode for the high-side filtered
> + sense pin.
> +
> + adi,ext2-buffer-lp:
> + type: boolean
> + description: Whether to enable low-power buffered mode for the EXT2 pin.
> +
> + adi,ext1-buffer-lp:
> + type: boolean
> + description: Whether to enable low-power buffered mode for the EXT1 pin.
> +
> + adi,comparator-invert:
> + type: boolean
> + description: Whether to invert the comparator output.
> +
> + adi,digital-input-sink-range-high:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + When not present, the digital input range is from 0 to 3700uA in steps
> + of 120uA, with a ~2k series resistance.
> + When present, the digital input range is from 0 to 7400uA in steps
> + of 240uA, with a ~1k series resistance.
> +
> + adi,digital-input-sink-microamp:
> + description: Sink current in digital input mode.
> + minimum: 0
> + maximum: 3700
> + default: 0
> +
> + adi,digital-input-debounce-mode-counter-reset:
> + type: boolean
> + description: |
> + When not present, a counter increments when the signal is asserted
> + and decrements when the signal is de-asserted.
> + When present, a counter increments while the signal is asserted and
> + resets when the signal de-asserts
> +
> + adi,digital-input-unbuffered:
> + type: boolean
> + description: Whether to buffer digital input signals.
> +
> + adi,digital-input-short-circuit-detection:
> + type: boolean
> + description: Whether to detect digital input short circuits.
> +
> + adi,digital-input-open-circuit-detection:
> + type: boolean
> + description: Whether to detect digital input open circuits.
> +
> + adi,digital-input-threshold-mode-fixed:
> + type: boolean
> + description: |
> + When not present, the digital input threshold range is -0.96 * AVDD
> + to AVDD.
> + When present, the threshold range is fixed from -19V to 30V.
> +
> + adi,dac-bipolar:
> + type: boolean
> + description: |
> + When not present, the DAC operates in the 0V to 12V range.
> + When present, the DAC operates in the -12V to 12V range.
> +
> + adi,charge-pump:
> + type: boolean
> + description: Whether to enable the internal charge pump.
> +
> + adi,dac-hart-slew:
> + type: boolean
> + description: Whether to use a HART-compatible slew rate.
> +
> + adi,dac-current-limit-low:
> + type: boolean
> + description: |
> + When not present, the DAC short-circuit current limit is 32mA in
> + either source or sink for VOUT and 4mA sink for IOUT.
> + When present, the limit is 16mA in either source or sink for VOUT,
> + 1mA sink for IOUT.
> +
> + adi,4-wire-rtd:
> + type: boolean
> + description: |
> + When not present, the ADC should be used for measuring 3-wire RTDs.
> + When present, the ADC should be used for measuring 4-wire RTDs.
> +
> + adi,3-wire-rtd-excitation-swap:
> + type: boolean
> + description: Whether to swap the excitation for 3-wire RTD.
> +
> + adi,rtd-excitation-current-microamp:
> + description: Excitation current to apply to RTD.
> + enum: [250, 500, 750, 1000]
> + default: 250
> +
> + adi,ext1-burnout:
> + type: boolean
> + description: Whether to enable burnout current for EXT1.
> +
> + adi,ext1-burnout-current-nanoamp:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + Burnout current in nanoamps to be applied to EXT1.
> + enum: [0, 50, 500, 1000, 10000]
> + default: 0
> +
> + adi,ext1-burnout-current-polarity-sourcing:
> + type: boolean
> + description: |
> + When not present, the burnout current polarity for EXT1 is sinking.
> + When present, the burnout current polarity for EXT1 is sourcing.
> +
> + adi,ext2-burnout:
> + type: boolean
> + description: Whether to enable burnout current for EXT2.
> +
> + adi,ext2-burnout-current-nanoamp:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Burnout current in nanoamps to be applied to EXT2.
> + enum: [0, 50, 500, 1000, 10000]
> + default: 0
> +
> + adi,ext2-burnout-current-polarity-sourcing:
> + type: boolean
> + description: |
> + When not present, the burnout current polarity for EXT2 is sinking.
> + When present, the burnout current polarity for EXT2 is sourcing.
> +
> + adi,viout-burnout:
> + type: boolean
> + description: Whether to enable burnout current for VIOUT.
> +
> + adi,viout-burnout-current-nanoamp:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Burnout current in nanoamps to be applied to VIOUT.
> + enum: [0, 1000, 10000]
> + default: 0
> +
> + adi,viout-burnout-current-polarity-sourcing:
> + type: boolean
> + description: |
> + When not present, the burnout current polarity for VIOUT is sinking.
> + When present, the burnout current polarity for VIOUT is sourcing.
> +
> + adi,gpio0-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + GPIO functions.
> + 0 - Disabled
> + 1 - Logic I/O
> + 2 - Comparator output
> + 3 - Control HART CD
> + 4 - Monitor HART CD
> + 5 - Monitor HART EOM status
> + minimum: 0
> + maximum: 5
> + default: 0
> +
> + adi,gpio1-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + GPIO functions.
> + 0 - Disabled
> + 1 - Logic I/O
> + 2 - Drive external digital output FET
> + 3 - Control HART RXD
> + 4 - Monitor HART RXD
> + 5 - Monitor HART SOM status
> + minimum: 0
> + maximum: 5
> + default: 0
> +
> + adi,gpio2-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + GPIO functions.
> + 0 - Disabled
> + 1 - Logic I/O
> + 2 - Drive internal digital output FET
> + 3 - Control HART TXD
> + 4 - Monitor HART TXD
> + 5 - Monitor HART TX complete status
> + minimum: 0
> + maximum: 5
> + default: 0
> +
> + adi,gpio3-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: |
> + GPIO functions.
> + 0 - Disabled
> + 1 - Logic I/O
> + 2 - High impedance
> + 3 - Control HART RTS
> + 4 - Monitor HART RTS
> + 5 - Monitor HART CD complete status
> + minimum: 0
> + maximum: 5
> + default: 0
> +
> +required:
> + - compatible
> + - reg
> + - spi-cpol
> + - avdd-supply
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> + - if:
> + properties:
> + adi,digital-input-sink-range-high: true

The 'if' is true if adi,digital-input-sink-range-high is not present
which is probably not what you want. Use 'required' instead.

> + then:
> + properties:
> + adi,digital-input-sink-microamp:
> + maximum: 7400
> +
> +additionalProperties: false

As pointed out, yes, this needs to be unevaluatedProperties.

> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + addac@0 {
> + compatible = "adi,ad74115h";
> + reg = <0>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + spi-max-frequency = <12000000>;
> + spi-cpol;
> +
> + reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
> +
> + interrupt-parent = <&gpio>;
> + interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
> +
> + avdd-supply = <&ad74115_avdd>;
> +
> + adi,ch-func = <1>;
> + adi,conv2-mux = <2>;
> + adi,conv2-range = <1>;
> +
> + adi,gpio0-mode = <1>;
> + adi,gpio1-mode = <1>;
> + adi,gpio2-mode = <1>;
> + adi,gpio3-mode = <1>;
> +
> + adi,dac-bipolar;
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7a56c344ca67..5b5533f54f67 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1175,6 +1175,13 @@ W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/adc/adi,ad7780.yaml
> F: drivers/iio/adc/ad7780.c
>
> +ANALOG DEVICES INC AD74115 DRIVER
> +M: Cosmin Tanislav <[email protected]>
> +L: [email protected]
> +S: Supported
> +W: http://ez.analog.com/community/linux-device-drivers
> +F: Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> +
> ANALOG DEVICES INC AD74413R DRIVER
> M: Cosmin Tanislav <[email protected]>
> L: [email protected]
> --
> 2.38.1
>
>

2022-11-08 09:07:39

by Cosmin Tanislav

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

On Sun, 2022-11-06 at 15:46 +0000, Jonathan Cameron wrote:
> On Thu, 3 Nov 2022 11:44:35 +0200
> Cosmin Tanislav <[email protected]> wrote:
>
> > From: Cosmin Tanislav <[email protected]>
> >
> > The AD74115H is a single-channel, software-configurable, input and
> > output device for industrial control applications. The AD74115H
> > provides a wide range of use cases, integrated on a single chip.
> >
> > These use cases include analog output, analog input, digital output,
> > digital input, resistance temperature detector (RTD), and thermocouple
> > measurement capability. The AD74115H also has an integrated HART modem.
> >
> > A serial peripheral interface (SPI) is used to handle all communications
> > to the device, including communications with the HART modem. The digital
> > input and digital outputs can be accessed via the SPI or the
> > general-purpose input and output (GPIO) pins to support higher
> > speed data rates.
> >
> > The device features a 16-bit, sigma-delta analog-to-digital converter
> > (ADC) and a 14-bit digital-to-analog converter (DAC).
> > The AD74115H contains a high accuracy 2.5 V on-chip reference that can
> > be used as the DAC and ADC reference.
> >
> > Signed-off-by: Cosmin Tanislav <[email protected]>
>
> Hi Cosmin,
>
> A few questions inline. Complex device so I'll doubt we'll ever get this
> binding to be as tidy as for simpler devices. Hence most of the below are
> suggestions rather than requirements from me.
>
> Jonathan
>
> > ---
> > .../bindings/iio/addac/adi,ad74115.yaml | 370 ++++++++++++++++++
> > MAINTAINERS | 7 +
> > 2 files changed, 377 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> > new file mode 100644
> > index 000000000000..621f11d5c1f3
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> > @@ -0,0 +1,370 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/iio/addac/adi,ad74115.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Analog Devices AD74115H device
> > +
> > +maintainers:
> > + - Cosmin Tanislav <[email protected]>
> > +
> > +description: |
> > + The AD74115H is a single-channel software configurable input/output
> > + device for industrial control applications. It contains functionality for
> > + analog output, analog input, digital output, digital input, resistance
> > + temperature detector, and thermocouple measurements integrated into a single
> > + chip solution with an SPI interface. The device features a 16-bit ADC and a
> > + 14-bit DAC.
> > +
> > + https://www.analog.com/en/products/ad74115h.html
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - adi,ad74115h
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + '#address-cells':
> > + const: 1
> > +
> > + '#size-cells':
> > + const: 0
>
> I'm not seeing any child nodes, so why do we need these two?
>

Will fix.

> > +
> > + avdd-supply: true
> > + avcc-supply: true
> > + dvcc-supply: true
> > + aldo1v8-supply: true
>
> aldo1v8 is an output pin. "1.8 V Analog LDO Output. Do not use ALDO1V8 externally."
> The associated input is avcc. Given we shouldn't connect anything to the pin,
> we don't want it in the binding docs
>

Will fix.

> > + dovdd-supply: true
> > + refin-supply: true
> > +
>
> ...
>
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: |
> > + Conversion range for ADC conversion 2.
> > + 0 - 0V to 12V
> > + 1 - -12V to +12V
> > + 2 - -2.5V to +2.5V
> > + 3 - -2.5V to 0V
> > + 4 - 0V to 2.5V
> > + 5 - 0V to 0.625V
> > + 6 - -104mV to +104mV
> > + 7 - 0V to 12V
>
> For a lot of similar cases we handle these numerically to give
> a human readable dts. Is there a strong reason not to do so here (in mv)
>

I used this approach mostly because it maps dirrectly to register values
and because it's easier to parse. dts isn't exactly nice at handling
negative values. I can switch it to mv array if you insist.

>
> > + minimum: 0
> > + maximum: 7
> > + default: 0
> > +
> > + adi,sense-agnd-buffer-lp:
> lp is a little ambiguous, given we have a habit of using it for low pass
> in filters etc. Perhaps worth spelling these out?
> adi,sens-agnd-buffer-low-power etc?

Will fix.

>
> > + type: boolean
> > + description: |
> > + Whether to enable low-power buffered mode for the AGND sense pin.
> > +
> > + adi,lf-buffer-lp:
> > + type: boolean
> > + description: |
> > + Whether to enable low-power buffered mode for the low-side filtered
> > + sense pin.
> > +
> > + adi,hf-buffer-lp:
> > + type: boolean
> > + description: |
> > + Whether to enable low-power buffered mode for the high-side filtered
> > + sense pin.
> > +
> > + adi,ext2-buffer-lp:
> > + type: boolean
> > + description: Whether to enable low-power buffered mode for the EXT2 pin.
> > +
> > + adi,ext1-buffer-lp:
> > + type: boolean
> > + description: Whether to enable low-power buffered mode for the EXT1 pin.
>
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - spi-cpol
> > + - avdd-supply
> > +
> > +allOf:
> > + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> > + - if:
> > + properties:
> > + adi,digital-input-sink-range-high: true
> > + then:
> > + properties:
> > + adi,digital-input-sink-microamp:
> > + maximum: 7400
> > +
> > +additionalProperties: false
>
> Does this need to be unevalutatedProperties to allow
> for the extra ones in spi-periphera-props.yaml?
>

Will fix.

> > +


2022-11-12 16:06:02

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115


> >
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + description: |
> > > + Conversion range for ADC conversion 2.
> > > + 0 - 0V to 12V
> > > + 1 - -12V to +12V
> > > + 2 - -2.5V to +2.5V
> > > + 3 - -2.5V to 0V
> > > + 4 - 0V to 2.5V
> > > + 5 - 0V to 0.625V
> > > + 6 - -104mV to +104mV
> > > + 7 - 0V to 12V
> >
> > For a lot of similar cases we handle these numerically to give
> > a human readable dts. Is there a strong reason not to do so here (in mv)
> >
>
> I used this approach mostly because it maps dirrectly to register values
> and because it's easier to parse. dts isn't exactly nice at handling
> negative values. I can switch it to mv array if you insist.

We have quite a few existing cases of
adi,[output-]range-microvolt so it would be good to copy that style here.

>
> >
> > > + minimum: 0
> > > + maximum: 7
> > > + default: 0
> > > +

2022-11-15 13:09:18

by Cosmin Tanislav

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

On Sat, 2022-11-12 at 15:40 +0000, Jonathan Cameron wrote:
> > >   
> > > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > > + description: |
> > > > + Conversion range for ADC conversion 2.
> > > > + 0 - 0V to 12V
> > > > + 1 - -12V to +12V
> > > > + 2 - -2.5V to +2.5V
> > > > + 3 - -2.5V to 0V
> > > > + 4 - 0V to 2.5V
> > > > + 5 - 0V to 0.625V
> > > > + 6 - -104mV to +104mV
> > > > + 7 - 0V to 12V
> > >
> > > For a lot of similar cases we handle these numerically to give
> > > a human readable dts. Is there a strong reason not to do so here (in mv)
> > >   
> >
> > I used this approach mostly because it maps dirrectly to register values
> > and because it's easier to parse. dts isn't exactly nice at handling
> > negative values. I can switch it to mv array if you insist.
>
> We have quite a few existing cases of
> adi,[output-]range-microvolt so it would be good to copy that style here.
>

With this:

adi,conv2-range-microvolt:
description: Conversion range for ADC conversion 2.
oneOf:
- items:
- enum: [-2500000, 0]
- const: 2500000
- items:
- enum: [-12000000, 0]
- const: 12000000
- items:
- const: -2500000
- const: 0
- items:
- const: -104000
- const: 104000
- items:
- const: 0
- const: 625000

And this:

adi,conv2-range-microvolt = <(-12000000) 12000000>;

I get this:

Documentation/devicetree/bindings/iio/addac/adi,ad74115.example.dtb:
addac@0: adi,conv2-range-microvolt: 'oneOf' conditional failed,
one must be fixed:
4282967296 is not one of [-2500000, 0]
4282967296 is not one of [-12000000, 0]
-2500000 was expected
-104000 was expected
625000 was expected
From schema: Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml

As I said, negative numbers don't play too nice...

> >
> > >   
> > > > + minimum: 0
> > > > + maximum: 7
> > > > + default: 0
> > > > +


2022-11-15 18:23:35

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] dt-bindings: iio: addac: add AD74115

On Tue, Nov 15, 2022 at 10:07 AM Jonathan Cameron
<[email protected]> wrote:
>
> On Tue, 15 Nov 2022 14:43:53 +0200
> Cosmin Tanislav <[email protected]> wrote:
>
> > On Sat, 2022-11-12 at 15:40 +0000, Jonathan Cameron wrote:
> > > > >
> > > > > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > > > > + description: |
> > > > > > + Conversion range for ADC conversion 2.
> > > > > > + 0 - 0V to 12V
> > > > > > + 1 - -12V to +12V
> > > > > > + 2 - -2.5V to +2.5V
> > > > > > + 3 - -2.5V to 0V
> > > > > > + 4 - 0V to 2.5V
> > > > > > + 5 - 0V to 0.625V
> > > > > > + 6 - -104mV to +104mV
> > > > > > + 7 - 0V to 12V
> > > > >
> > > > > For a lot of similar cases we handle these numerically to give
> > > > > a human readable dts. Is there a strong reason not to do so here (in mv)
> > > > >
> > > >
> > > > I used this approach mostly because it maps dirrectly to register values
> > > > and because it's easier to parse. dts isn't exactly nice at handling
> > > > negative values. I can switch it to mv array if you insist.
> > >
> > > We have quite a few existing cases of
> > > adi,[output-]range-microvolt so it would be good to copy that style here.
> > >
> >
> > With this:
> >
> > adi,conv2-range-microvolt:
> > description: Conversion range for ADC conversion 2.
> > oneOf:
> > - items:
> > - enum: [-2500000, 0]
> > - const: 2500000
> > - items:
> > - enum: [-12000000, 0]
> > - const: 12000000
> > - items:
> > - const: -2500000
> > - const: 0
> > - items:
> > - const: -104000
> > - const: 104000
> > - items:
> > - const: 0
> > - const: 625000
> >
> > And this:
> >
> > adi,conv2-range-microvolt = <(-12000000) 12000000>;
> >
> > I get this:
> >
> > Documentation/devicetree/bindings/iio/addac/adi,ad74115.example.dtb:
> > addac@0: adi,conv2-range-microvolt: 'oneOf' conditional failed,
> > one must be fixed:
> > 4282967296 is not one of [-2500000, 0]
> > 4282967296 is not one of [-12000000, 0]
> > -2500000 was expected
> > -104000 was expected
> > 625000 was expected
> > From schema: Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml
> >
> > As I said, negative numbers don't play too nice...
>
> From what I recall we just ignore those warnings :)
>
> Rob, do I remember correctly that there was a plan to make this work longer term?

Yes, but handling signed types is working now (since the move to
validating dtbs directly).

The issue here is -microvolt is defined as unsigned. IIRC, I had some
issue changing it, but I think that was just with the YAML encoding
which I intend to remove. I'll give it another look and update the
type if there's no issues.

Rob