diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/iio/addac | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/iio/addac')
-rw-r--r-- | drivers/iio/addac/Kconfig | 54 | ||||
-rw-r--r-- | drivers/iio/addac/Makefile | 9 | ||||
-rw-r--r-- | drivers/iio/addac/ad74115.c | 1943 | ||||
-rw-r--r-- | drivers/iio/addac/ad74413r.c | 1545 | ||||
-rw-r--r-- | drivers/iio/addac/stx104.c | 466 |
5 files changed, 4017 insertions, 0 deletions
diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig new file mode 100644 index 0000000000..397544f23b --- /dev/null +++ b/drivers/iio/addac/Kconfig @@ -0,0 +1,54 @@ +# +# ADC DAC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog to digital and digital to analog converters" + +config AD74115 + tristate "Analog Devices AD74115H driver" + depends on GPIOLIB && SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD74115H + single-channel software configurable input/output solution. + + To compile this driver as a module, choose M here: the + module will be called ad74115. + +config AD74413R + tristate "Analog Devices AD74412R/AD74413R driver" + depends on GPIOLIB && SPI + select REGMAP_SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD74412R/AD74413R + quad-channel software configurable input/output solution. + + To compile this driver as a module, choose M here: the + module will be called ad74413r. + +config STX104 + tristate "Apex Embedded Systems STX104 driver" + depends on PC104 && X86 + select ISA_BUS_API + select REGMAP_MMIO + select GPIOLIB + select GPIO_REGMAP + help + Say yes here to build support for the Apex Embedded Systems STX104 + integrated analog PC/104 card. + + This driver supports the 16 channels of single-ended (8 channels of + differential) analog inputs, 2 channels of analog output, 4 digital + inputs, and 4 digital outputs provided by the STX104. + + The base port addresses for the devices may be configured via the base + array module parameter. + +endmenu diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile new file mode 100644 index 0000000000..577777276e --- /dev/null +++ b/drivers/iio/addac/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O ADDAC drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD74115) += ad74115.o +obj-$(CONFIG_AD74413R) += ad74413r.o +obj-$(CONFIG_STX104) += stx104.o diff --git a/drivers/iio/addac/ad74115.c b/drivers/iio/addac/ad74115.c new file mode 100644 index 0000000000..e6bc5eb378 --- /dev/null +++ b/drivers/iio/addac/ad74115.c @@ -0,0 +1,1943 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Analog Devices, Inc. + * Author: Cosmin Tanislav <cosmin.tanislav@analog.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/crc8.h> +#include <linux/device.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/units.h> + +#include <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> + +#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; + int irq; + + unsigned int avdd_mv; + unsigned long gpio_valid_mask; + bool dac_bipolar; + bool dac_hart_slew; + bool rtd_mode_4_wire; + enum ad74115_ch_func ch_func; + enum ad74115_din_threshold_mode din_threshold_mode; + + struct completion adc_data_completion; + struct spi_message adc_samples_msg; + struct spi_transfer adc_samples_xfer[AD74115_ADC_CH_NUM + 1]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 reg_tx_buf[AD74115_FRAME_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 reg_rx_buf[AD74115_FRAME_SIZE]; + u8 adc_samples_tx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM]; + u8 adc_samples_rx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM]; +}; + +struct ad74115_fw_prop { + const char *name; + bool is_boolean; + bool negate; + unsigned int max; + unsigned int reg; + unsigned int mask; + const unsigned int *lookup_tbl; + unsigned int lookup_tbl_len; +}; + +#define AD74115_FW_PROP(_name, _max, _reg, _mask) \ +{ \ + .name = (_name), \ + .max = (_max), \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +#define AD74115_FW_PROP_TBL(_name, _tbl, _reg, _mask) \ +{ \ + .name = (_name), \ + .reg = (_reg), \ + .mask = (_mask), \ + .lookup_tbl = (_tbl), \ + .lookup_tbl_len = ARRAY_SIZE(_tbl), \ +} + +#define AD74115_FW_PROP_BOOL(_name, _reg, _mask) \ +{ \ + .name = (_name), \ + .is_boolean = true, \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +#define AD74115_FW_PROP_BOOL_NEG(_name, _reg, _mask) \ +{ \ + .name = (_name), \ + .is_boolean = true, \ + .negate = true, \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +static const int ad74115_dac_rate_tbl[] = { + 0, + 4 * 8, + 4 * 15, + 4 * 61, + 4 * 222, + 64 * 8, + 64 * 15, + 64 * 61, + 64 * 222, + 150 * 8, + 150 * 15, + 150 * 61, + 150 * 222, + 240 * 8, + 240 * 15, + 240 * 61, + 240 * 222, +}; + +static const unsigned int ad74115_dac_rate_step_tbl[][3] = { + { AD74115_SLEW_MODE_DISABLED }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_240KHZ }, +}; + +static const unsigned int ad74115_rtd_excitation_current_ua_tbl[] = { + 250, 500, 750, 1000 +}; + +static const unsigned int ad74115_burnout_current_na_tbl[] = { + 0, 50, 0, 500, 1000, 0, 10000, 0 +}; + +static const unsigned int ad74115_viout_burnout_current_na_tbl[] = { + 0, 0, 0, 0, 1000, 0, 10000, 0 +}; + +static const unsigned int ad74115_gpio_mode_tbl[] = { + 0, 0, 0, 1, 2, 3, 4, 5 +}; + +static const unsigned int ad74115_adc_conv_rate_tbl[] = { + 10, 20, 1200, 4800, 9600 +}; + +static const unsigned int ad74115_debounce_tbl[] = { + 0, 13, 18, 24, 32, 42, 56, 75, + 100, 130, 180, 240, 320, 420, 560, 750, + 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500, + 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000, +}; + +static const unsigned int ad74115_adc_ch_data_regs_tbl[] = { + [AD74115_ADC_CH_CONV1] = 0x44, + [AD74115_ADC_CH_CONV2] = 0x46, +}; + +static const unsigned int ad74115_adc_ch_en_bit_tbl[] = { + [AD74115_ADC_CH_CONV1] = BIT(0), + [AD74115_ADC_CH_CONV2] = BIT(1), +}; + +static const bool ad74115_adc_bipolar_tbl[AD74115_ADC_RANGE_NUM] = { + [AD74115_ADC_RANGE_12V_BIPOLAR] = true, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = true, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = true, +}; + +static const unsigned int ad74115_adc_conv_mul_tbl[AD74115_ADC_RANGE_NUM] = { + [AD74115_ADC_RANGE_12V] = 12000, + [AD74115_ADC_RANGE_12V_BIPOLAR] = 24000, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = 5000, + [AD74115_ADC_RANGE_2_5V_NEG] = 2500, + [AD74115_ADC_RANGE_2_5V] = 2500, + [AD74115_ADC_RANGE_0_625V] = 625, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = 208, + [AD74115_ADC_RANGE_12V_OTHER] = 12000, +}; + +static const unsigned int ad74115_adc_gain_tbl[AD74115_ADC_RANGE_NUM][2] = { + [AD74115_ADC_RANGE_12V] = { 5, 24 }, + [AD74115_ADC_RANGE_12V_BIPOLAR] = { 5, 24 }, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { 1, 1 }, + [AD74115_ADC_RANGE_2_5V_NEG] = { 1, 1 }, + [AD74115_ADC_RANGE_2_5V] = { 1, 1 }, + [AD74115_ADC_RANGE_0_625V] = { 4, 1 }, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = { 24, 1 }, + [AD74115_ADC_RANGE_12V_OTHER] = { 5, 24 }, +}; + +static const int ad74115_adc_range_tbl[AD74115_ADC_RANGE_NUM][2] = { + [AD74115_ADC_RANGE_12V] = { 0, 12000000 }, + [AD74115_ADC_RANGE_12V_BIPOLAR] = { -12000000, 12000000 }, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { -2500000, 2500000 }, + [AD74115_ADC_RANGE_2_5V_NEG] = { -2500000, 0 }, + [AD74115_ADC_RANGE_2_5V] = { 0, 2500000 }, + [AD74115_ADC_RANGE_0_625V] = { 0, 625000 }, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = { -104000, 104000 }, + [AD74115_ADC_RANGE_12V_OTHER] = { 0, 12000000 }, +}; + +static int _ad74115_find_tbl_index(const unsigned int *tbl, unsigned int tbl_len, + unsigned int val, unsigned int *index) +{ + unsigned int i; + + for (i = 0; i < tbl_len; i++) + if (val == tbl[i]) { + *index = i; + return 0; + } + + return -EINVAL; +} + +#define ad74115_find_tbl_index(tbl, val, index) \ + _ad74115_find_tbl_index(tbl, ARRAY_SIZE(tbl), val, index) + +static int ad74115_crc(u8 *buf) +{ + return crc8(ad74115_crc8_table, buf, 3, 0); +} + +static void ad74115_format_reg_write(u8 reg, u16 val, u8 *buf) +{ + buf[0] = reg; + put_unaligned_be16(val, &buf[1]); + buf[3] = ad74115_crc(buf); +} + +static int ad74115_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad74115_state *st = context; + + ad74115_format_reg_write(reg, val, st->reg_tx_buf); + + return spi_write(st->spi, st->reg_tx_buf, AD74115_FRAME_SIZE); +} + +static int ad74115_crc_check(struct ad74115_state *st, u8 *buf) +{ + struct device *dev = &st->spi->dev; + u8 expected_crc = ad74115_crc(buf); + + if (buf[3] != expected_crc) { + dev_err(dev, "Bad CRC %02x for %02x%02x%02x, expected %02x\n", + buf[3], buf[0], buf[1], buf[2], expected_crc); + return -EINVAL; + } + + return 0; +} + +static int ad74115_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad74115_state *st = context; + struct spi_transfer reg_read_xfer[] = { + { + .tx_buf = st->reg_tx_buf, + .len = sizeof(st->reg_tx_buf), + .cs_change = 1, + }, + { + .rx_buf = st->reg_rx_buf, + .len = sizeof(st->reg_rx_buf), + }, + }; + int ret; + + ad74115_format_reg_write(AD74115_READ_SELECT_REG, reg, st->reg_tx_buf); + + ret = spi_sync_transfer(st->spi, reg_read_xfer, ARRAY_SIZE(reg_read_xfer)); + if (ret) + return ret; + + ret = ad74115_crc_check(st, st->reg_rx_buf); + if (ret) + return ret; + + *val = get_unaligned_be16(&st->reg_rx_buf[1]); + + return 0; +} + +static const struct regmap_config ad74115_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_read = ad74115_reg_read, + .reg_write = ad74115_reg_write, +}; + +static int ad74115_gpio_config_set(struct ad74115_state *st, unsigned int offset, + enum ad74115_gpio_config cfg) +{ + return regmap_update_bits(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), + AD74115_GPIO_CONFIG_SELECT_MASK, + FIELD_PREP(AD74115_GPIO_CONFIG_SELECT_MASK, cfg)); +} + +static int ad74115_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + *valid_mask = st->gpio_valid_mask; + + return 0; +} + +static int ad74115_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val); + if (ret) + return ret; + + return FIELD_GET(AD74115_GPIO_CONFIG_SELECT_MASK, val) == AD74115_GPIO_CONFIG_INPUT; +} + +static int ad74115_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_INPUT); +} + +static int ad74115_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_OUTPUT_BUFFERED); +} + +static int ad74115_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val); + if (ret) + return ret; + + return FIELD_GET(AD74115_GPIO_CONFIG_GPI_DATA, val); +} + +static 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_tbl[channel]; + + return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG, mask, + status ? mask : 0); +} + +static int ad74115_set_adc_conv_seq(struct ad74115_state *st, + enum ad74115_adc_conv_seq conv_seq) +{ + return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG, + AD74115_ADC_CONV_SEQ_MASK, + FIELD_PREP(AD74115_ADC_CONV_SEQ_MASK, conv_seq)); +} + +static int ad74115_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct spi_transfer *xfer = st->adc_samples_xfer; + u8 *rx_buf = st->adc_samples_rx_buf; + u8 *tx_buf = st->adc_samples_tx_buf; + unsigned int i; + int ret = 0; + + mutex_lock(&st->lock); + + spi_message_init(&st->adc_samples_msg); + + for_each_clear_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) { + ret = ad74115_set_adc_ch_en(st, i, false); + if (ret) + goto out; + } + + /* + * The read select register is used to select which register's value + * will be sent by the slave on the next SPI frame. + * + * Create an SPI message that, on each step, writes to the read select + * register to select the ADC result of the next enabled channel, and + * reads the ADC result of the previous enabled channel. + * + * Example: + * W: [WCH1] [WCH2] [WCH2] [WCH3] [ ] + * R: [ ] [RCH1] [RCH2] [RCH3] [RCH4] + */ + for_each_set_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) { + ret = ad74115_set_adc_ch_en(st, i, true); + if (ret) + goto out; + + if (xfer == st->adc_samples_xfer) + xfer->rx_buf = NULL; + else + xfer->rx_buf = rx_buf; + + xfer->tx_buf = tx_buf; + xfer->len = AD74115_FRAME_SIZE; + xfer->cs_change = 1; + + ad74115_format_reg_write(AD74115_READ_SELECT_REG, + ad74115_adc_ch_data_regs_tbl[i], tx_buf); + + spi_message_add_tail(xfer, &st->adc_samples_msg); + + tx_buf += AD74115_FRAME_SIZE; + if (xfer != st->adc_samples_xfer) + rx_buf += AD74115_FRAME_SIZE; + xfer++; + } + + xfer->rx_buf = rx_buf; + xfer->tx_buf = NULL; + xfer->len = AD74115_FRAME_SIZE; + xfer->cs_change = 0; + + spi_message_add_tail(xfer, &st->adc_samples_msg); + +out: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad74115_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + return ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_CONTINUOUS); +} + +static int ad74115_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + unsigned int i; + int ret; + + mutex_lock(&st->lock); + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY); + if (ret) + goto out; + + /* + * update_scan_mode() is not called in the disable path, disable all + * channels here. + */ + for (i = 0; i < AD74115_ADC_CH_NUM; i++) { + ret = ad74115_set_adc_ch_en(st, i, false); + if (ret) + goto out; + } + +out: + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_buffer_setup_ops ad74115_buffer_ops = { + .postenable = &ad74115_buffer_postenable, + .predisable = &ad74115_buffer_predisable, +}; + +static const struct iio_trigger_ops ad74115_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad74115_get_adc_rate(struct ad74115_state *st, + enum ad74115_adc_ch channel, int *val) +{ + unsigned int i; + int ret; + + ret = regmap_read(st->regmap, AD74115_ADC_CONFIG_REG, &i); + if (ret) + return ret; + + if (channel == AD74115_ADC_CH_CONV1) + i = FIELD_GET(AD74115_ADC_CONFIG_CONV1_RATE_MASK, i); + else + i = FIELD_GET(AD74115_ADC_CONFIG_CONV2_RATE_MASK, i); + + *val = ad74115_adc_conv_rate_tbl[i]; + + return IIO_VAL_INT; +} + +static int _ad74115_get_adc_code(struct ad74115_state *st, + enum ad74115_adc_ch channel, int *val) +{ + unsigned int uval; + int ret; + + reinit_completion(&st->adc_data_completion); + + ret = ad74115_set_adc_ch_en(st, channel, true); + if (ret) + return ret; + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_SINGLE); + if (ret) + return ret; + + if (st->irq) { + ret = wait_for_completion_timeout(&st->adc_data_completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + } else { + unsigned int regval, wait_time; + int rate; + + ret = ad74115_get_adc_rate(st, channel, &rate); + if (ret < 0) + return ret; + + wait_time = DIV_ROUND_CLOSEST(AD74115_CONV_TIME_US, rate); + + ret = regmap_read_poll_timeout(st->regmap, AD74115_LIVE_STATUS_REG, + regval, regval & AD74115_ADC_DATA_RDY_MASK, + wait_time, 5 * wait_time); + if (ret) + return ret; + + /* + * The ADC_DATA_RDY bit is W1C. + * See datasheet page 98, Table 62. Bit Descriptions for + * LIVE_STATUS. + * Although the datasheet mentions that the bit will auto-clear + * when writing to the ADC_CONV_CTRL register, this does not + * seem to happen. + */ + ret = regmap_write_bits(st->regmap, AD74115_LIVE_STATUS_REG, + AD74115_ADC_DATA_RDY_MASK, + FIELD_PREP(AD74115_ADC_DATA_RDY_MASK, 1)); + if (ret) + return ret; + } + + ret = regmap_read(st->regmap, ad74115_adc_ch_data_regs_tbl[channel], &uval); + if (ret) + return ret; + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY); + if (ret) + return ret; + + ret = ad74115_set_adc_ch_en(st, channel, false); + if (ret) + return ret; + + *val = uval; + + return IIO_VAL_INT; +} + +static int ad74115_get_adc_code(struct iio_dev *indio_dev, + enum ad74115_adc_ch channel, int *val) +{ + struct ad74115_state *st = iio_priv(indio_dev); + int ret; + + 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_resistance_scale(struct ad74115_state *st, + unsigned int range, + int *val, int *val2) +{ + *val = ad74115_adc_gain_tbl[range][1] * AD74115_REF_RESISTOR_OHMS; + *val2 = ad74115_adc_gain_tbl[range][0]; + + if (ad74115_adc_bipolar_tbl[range]) + *val2 *= AD74115_ADC_CODE_HALF; + else + *val2 *= AD74115_ADC_CODE_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_scale(struct ad74115_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74115_get_adc_range(st, chan->channel, &range); + if (ret) + return ret; + + if (chan->type == IIO_RESISTANCE) + return ad74115_get_adc_resistance_scale(st, range, val, val2); + + *val = ad74115_adc_conv_mul_tbl[range]; + *val2 = AD74115_ADC_CODE_MAX; + + if (chan->type == IIO_CURRENT) + *val2 *= AD74115_SENSE_RESISTOR_OHMS; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_resistance_offset(struct ad74115_state *st, + unsigned int range, + int *val, int *val2) +{ + unsigned int d = 10 * AD74115_REF_RESISTOR_OHMS + * ad74115_adc_gain_tbl[range][1]; + + *val = 5; + + if (ad74115_adc_bipolar_tbl[range]) + *val -= AD74115_ADC_CODE_HALF; + + *val *= d; + + if (!st->rtd_mode_4_wire) { + /* Add 0.2 Ohm to the final result for 3-wire RTD. */ + unsigned int v = 2 * ad74115_adc_gain_tbl[range][0]; + + if (ad74115_adc_bipolar_tbl[range]) + v *= AD74115_ADC_CODE_HALF; + else + v *= AD74115_ADC_CODE_MAX; + + *val += v; + } + + *val2 = d; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_offset(struct ad74115_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74115_get_adc_range(st, chan->channel, &range); + if (ret) + return ret; + + if (chan->type == IIO_RESISTANCE) + return ad74115_get_adc_resistance_offset(st, range, val, val2); + + if (ad74115_adc_bipolar_tbl[range]) + *val = -AD74115_ADC_CODE_HALF; + else if (range == AD74115_ADC_RANGE_2_5V_NEG) + *val = -AD74115_ADC_CODE_MAX; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int ad74115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad74115_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (chan->output) + return ad74115_get_dac_code(st, chan->channel, val); + + return ad74115_get_adc_code(indio_dev, chan->channel, val); + case IIO_CHAN_INFO_PROCESSED: + ret = ad74115_get_adc_code(indio_dev, chan->channel, val); + if (ret) + return ret; + + return ad74115_adc_code_to_resistance(*val, val, val2); + case IIO_CHAN_INFO_SCALE: + if (chan->output) + return ad74115_get_dac_scale(st, chan, val, val2); + + return ad74115_get_adc_scale(st, chan, val, val2); + case IIO_CHAN_INFO_OFFSET: + if (chan->output) + return ad74115_get_dac_offset(st, chan, val); + + return ad74115_get_adc_offset(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) + return ad74115_get_dac_rate(st, val); + + return ad74115_get_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74115_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long info) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + return ad74115_set_dac_code(st, chan->channel, val); + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) + return ad74115_set_dac_rate(st, val); + + return ad74115_set_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74115_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) { + *vals = ad74115_dac_rate_tbl; + *length = ARRAY_SIZE(ad74115_dac_rate_tbl); + } else { + *vals = ad74115_adc_conv_rate_tbl; + *length = ARRAY_SIZE(ad74115_adc_conv_rate_tbl); + } + + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad74115_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info ad74115_info = { + .read_raw = ad74115_read_raw, + .write_raw = ad74115_write_raw, + .read_avail = ad74115_read_avail, + .update_scan_mode = ad74115_update_scan_mode, + .debugfs_reg_access = ad74115_reg_access, +}; + +#define AD74115_DAC_CHANNEL(_type, index) \ + { \ + .type = (_type), \ + .channel = (index), \ + .indexed = 1, \ + .output = 1, \ + .scan_index = -1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + } + +#define _AD74115_ADC_CHANNEL(_type, index, extra_mask_separate) \ + { \ + .type = (_type), \ + .channel = (index), \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | (extra_mask_separate), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD74115_ADC_CHANNEL(_type, index) \ + _AD74115_ADC_CHANNEL(_type, index, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +static 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-mux", 3, + AD74115_ADC_CONFIG_REG, GENMASK(3, 2)), + + AD74115_FW_PROP_BOOL_NEG("adi,sense-agnd-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(4)), + AD74115_FW_PROP_BOOL_NEG("adi,lf-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(3)), + AD74115_FW_PROP_BOOL_NEG("adi,hf-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(2)), + AD74115_FW_PROP_BOOL_NEG("adi,ext2-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(1)), + AD74115_FW_PROP_BOOL_NEG("adi,ext1-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(0)), + + AD74115_FW_PROP_BOOL("adi,comparator-invert", + AD74115_DIN_CONFIG1_REG, BIT(14)), + AD74115_FW_PROP_BOOL("adi,digital-input-debounce-mode-counter-reset", + AD74115_DIN_CONFIG1_REG, BIT(6)), + + AD74115_FW_PROP_BOOL("adi,digital-input-unbuffered", + AD74115_DIN_CONFIG2_REG, BIT(10)), + AD74115_FW_PROP_BOOL("adi,digital-input-short-circuit-detection", + AD74115_DIN_CONFIG2_REG, BIT(9)), + AD74115_FW_PROP_BOOL("adi,digital-input-open-circuit-detection", + AD74115_DIN_CONFIG2_REG, BIT(8)), + + AD74115_FW_PROP_BOOL("adi,dac-current-limit-low", + AD74115_OUTPUT_CONFIG_REG, BIT(0)), + + AD74115_FW_PROP_BOOL("adi,3-wire-rtd-excitation-swap", + AD74115_RTD3W4W_CONFIG_REG, BIT(2)), + AD74115_FW_PROP_TBL("adi,rtd-excitation-current-microamp", + ad74115_rtd_excitation_current_ua_tbl, + AD74115_RTD3W4W_CONFIG_REG, GENMASK(1, 0)), + + AD74115_FW_PROP_BOOL("adi,ext2-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(11)), + AD74115_FW_PROP_BOOL("adi,ext1-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(6)), + AD74115_FW_PROP_BOOL("adi,viout-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(1)), + + AD74115_FW_PROP_BOOL("adi,charge-pump", + AD74115_CHARGE_PUMP_REG, BIT(0)), +}; + +static int ad74115_apply_fw_prop(struct ad74115_state *st, + const struct ad74115_fw_prop *prop, u32 *retval) +{ + struct device *dev = &st->spi->dev; + u32 val = 0; + int ret; + + if (prop->is_boolean) { + val = device_property_read_bool(dev, prop->name); + } else { + ret = device_property_read_u32(dev, prop->name, &val); + if (ret && prop->lookup_tbl) + val = prop->lookup_tbl[0]; + } + + *retval = val; + + if (prop->negate) + val = !val; + + if (prop->lookup_tbl) + ret = _ad74115_find_tbl_index(prop->lookup_tbl, + prop->lookup_tbl_len, val, &val); + else if (prop->max && val > prop->max) + ret = -EINVAL; + else + ret = 0; + + if (ret) + return dev_err_probe(dev, -EINVAL, + "Invalid value %u for prop %s\n", + val, prop->name); + + WARN(!prop->mask, "Prop %s mask is empty\n", prop->name); + + val = (val << __ffs(prop->mask)) & prop->mask; + + return regmap_update_bits(st->regmap, prop->reg, prop->mask, val); +} + +static int ad74115_setup_adc_conv2_range(struct ad74115_state *st) +{ + unsigned int tbl_len = ARRAY_SIZE(ad74115_adc_range_tbl); + const char *prop_name = "adi,conv2-range-microvolt"; + s32 vals[2] = { + ad74115_adc_range_tbl[0][0], + ad74115_adc_range_tbl[0][1], + }; + struct device *dev = &st->spi->dev; + unsigned int i; + + device_property_read_u32_array(dev, prop_name, vals, 2); + + for (i = 0; i < tbl_len; i++) + if (vals[0] == ad74115_adc_range_tbl[i][0] && + vals[1] == ad74115_adc_range_tbl[i][1]) + break; + + if (i == tbl_len) + return dev_err_probe(dev, -EINVAL, + "Invalid value %d, %d for prop %s\n", + vals[0], vals[1], prop_name); + + return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG, + AD74115_ADC_CONFIG_CONV2_RANGE_MASK, + FIELD_PREP(AD74115_ADC_CONFIG_CONV2_RANGE_MASK, i)); +} + +static int ad74115_setup_iio_channels(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct iio_chan_spec *channels; + + channels = devm_kcalloc(dev, sizeof(*channels), + indio_dev->num_channels, GFP_KERNEL); + if (!channels) + return -ENOMEM; + + indio_dev->channels = channels; + + memcpy(channels, ad74115_channels_map[st->ch_func].channels, + sizeof(*channels) * ad74115_channels_map[st->ch_func].num_channels); + + if (channels[0].output && channels[0].channel == AD74115_DAC_CH_MAIN && + channels[0].type == IIO_VOLTAGE && !st->dac_hart_slew) { + channels[0].info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + channels[0].info_mask_separate_available |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + } + + return 0; +} + +static int ad74115_setup_gpio_chip(struct ad74115_state *st) +{ + struct device *dev = &st->spi->dev; + + if (!st->gpio_valid_mask) + return 0; + + st->gc = (struct gpio_chip) { + .owner = THIS_MODULE, + .label = AD74115_NAME, + .base = -1, + .ngpio = AD74115_GPIO_NUM, + .parent = dev, + .can_sleep = true, + .init_valid_mask = ad74115_gpio_init_valid_mask, + .get_direction = ad74115_gpio_get_direction, + .direction_input = ad74115_gpio_direction_input, + .direction_output = ad74115_gpio_direction_output, + .get = ad74115_gpio_get, + .set = ad74115_gpio_set, + }; + + return devm_gpiochip_add_data(dev, &st->gc, st); +} + +static int ad74115_setup_comp_gpio_chip(struct ad74115_state *st) +{ + struct device *dev = &st->spi->dev; + u32 val; + int ret; + + ret = regmap_read(st->regmap, AD74115_DIN_CONFIG1_REG, &val); + if (ret) + return ret; + + if (!(val & AD74115_DIN_COMPARATOR_EN_MASK)) + return 0; + + st->comp_gc = (struct gpio_chip) { + .owner = THIS_MODULE, + .label = AD74115_NAME, + .base = -1, + .ngpio = 1, + .parent = dev, + .can_sleep = true, + .get_direction = ad74115_comp_gpio_get_direction, + .get = ad74115_comp_gpio_get, + .set_config = ad74115_comp_gpio_set_config, + }; + + return devm_gpiochip_add_data(dev, &st->comp_gc, st); +} + +static int ad74115_setup(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 val, din_range_high; + unsigned int i; + int ret; + + ret = ad74115_apply_fw_prop(st, &ad74115_ch_func_fw_prop, &val); + if (ret) + return ret; + + indio_dev->num_channels += ad74115_channels_map[val].num_channels; + st->ch_func = val; + + ret = ad74115_setup_adc_conv2_range(st); + if (ret) + return ret; + + val = device_property_read_bool(dev, "adi,dac-hart-slew"); + if (val) { + st->dac_hart_slew = val; + + ret = regmap_update_bits(st->regmap, AD74115_OUTPUT_CONFIG_REG, + AD74115_OUTPUT_SLEW_EN_MASK, + FIELD_PREP(AD74115_OUTPUT_SLEW_EN_MASK, + AD74115_SLEW_MODE_HART)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_din_range_fw_prop, + &din_range_high); + if (ret) + return ret; + + ret = device_property_read_u32(dev, "adi,digital-input-sink-microamp", &val); + if (!ret) { + if (din_range_high) + val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_LOW_STEP); + else + val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_HIGH_STEP); + + if (val > AD74115_DIN_SINK_MAX) + val = AD74115_DIN_SINK_MAX; + + ret = regmap_update_bits(st->regmap, AD74115_DIN_CONFIG1_REG, + AD74115_DIN_SINK_MASK, + FIELD_PREP(AD74115_DIN_SINK_MASK, val)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_din_threshold_mode_fw_prop, &val); + if (ret) + return ret; + + if (val == AD74115_DIN_THRESHOLD_MODE_AVDD) { + 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; + + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "adc_rdy"); + + if (st->irq == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (st->irq < 0) { + st->irq = 0; + return 0; + } + + ret = devm_request_irq(dev, st->irq, ad74115_adc_data_interrupt, + 0, AD74115_NAME, indio_dev); + if (ret) + return ret; + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", AD74115_NAME, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad74115_trigger_ops; + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} + +static int ad74115_probe(struct spi_device *spi) +{ + static const char * const regulator_names[] = { + "avcc", "dvcc", "dovdd", "refin", + }; + struct device *dev = &spi->dev; + struct ad74115_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + 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, NULL, + ad74115_trigger_handler, + &ad74115_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad74115_unregister_driver(struct spi_driver *spi) +{ + spi_unregister_driver(spi); + + return 0; +} + +static int __init ad74115_register_driver(struct spi_driver *spi) +{ + crc8_populate_msb(ad74115_crc8_table, AD74115_CRC_POLYNOMIAL); + + return spi_register_driver(spi); +} + +static const struct spi_device_id ad74115_spi_id[] = { + { "ad74115h" }, + { } +}; + +MODULE_DEVICE_TABLE(spi, ad74115_spi_id); + +static const struct of_device_id ad74115_dt_id[] = { + { .compatible = "adi,ad74115h" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad74115_dt_id); + +static struct spi_driver ad74115_driver = { + .driver = { + .name = "ad74115", + .of_match_table = ad74115_dt_id, + }, + .probe = ad74115_probe, + .id_table = ad74115_spi_id, +}; + +module_driver(ad74115_driver, + ad74115_register_driver, ad74115_unregister_driver); + +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD74115 ADDAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/addac/ad74413r.c b/drivers/iio/addac/ad74413r.c new file mode 100644 index 0000000000..6b0e8218f1 --- /dev/null +++ b/drivers/iio/addac/ad74413r.c @@ -0,0 +1,1545 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav <cosmin.tanislav@analog.com> + */ + +#include <asm/unaligned.h> +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/interrupt.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <dt-bindings/iio/addac/adi,ad74413r.h> + +#define AD74413R_CRC_POLYNOMIAL 0x7 +DECLARE_CRC8_TABLE(ad74413r_crc8_table); + +#define AD74413R_CHANNEL_MAX 4 + +#define AD74413R_FRAME_SIZE 4 + +struct ad74413r_chip_info { + const char *name; + bool hart_support; +}; + +struct ad74413r_channel_config { + u32 func; + u32 drive_strength; + bool gpo_comparator; + bool initialized; +}; + +struct ad74413r_channels { + struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ad74413r_state { + struct ad74413r_channel_config channel_configs[AD74413R_CHANNEL_MAX]; + unsigned int gpo_gpio_offsets[AD74413R_CHANNEL_MAX]; + unsigned int comp_gpio_offsets[AD74413R_CHANNEL_MAX]; + struct gpio_chip gpo_gpiochip; + struct gpio_chip comp_gpiochip; + struct completion adc_data_completion; + unsigned int num_gpo_gpios; + unsigned int num_comparator_gpios; + u32 sense_resistor_ohms; + + /* + * Synchronize consecutive operations when doing a one-shot + * conversion and when updating the ADC samples SPI message. + */ + struct mutex lock; + + const struct ad74413r_chip_info *chip_info; + struct spi_device *spi; + struct regulator *refin_reg; + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + struct gpio_desc *reset_gpio; + + size_t adc_active_channels; + struct spi_message adc_samples_msg; + struct spi_transfer adc_samples_xfer[AD74413R_CHANNEL_MAX + 1]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + struct { + u8 rx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX]; + s64 timestamp; + } adc_samples_buf __aligned(IIO_DMA_MINALIGN); + + u8 adc_samples_tx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX]; + u8 reg_tx_buf[AD74413R_FRAME_SIZE]; + u8 reg_rx_buf[AD74413R_FRAME_SIZE]; +}; + +#define AD74413R_REG_NOP 0x00 + +#define AD74413R_REG_CH_FUNC_SETUP_X(x) (0x01 + (x)) +#define AD74413R_CH_FUNC_SETUP_MASK GENMASK(3, 0) + +#define AD74413R_REG_ADC_CONFIG_X(x) (0x05 + (x)) +#define AD74413R_ADC_CONFIG_RANGE_MASK GENMASK(7, 5) +#define AD74413R_ADC_CONFIG_REJECTION_MASK GENMASK(4, 3) +#define AD74413R_ADC_CONFIG_CH_200K_TO_GND BIT(2) +#define AD74413R_ADC_RANGE_10V 0b000 +#define AD74413R_ADC_RANGE_2P5V_EXT_POW 0b001 +#define AD74413R_ADC_RANGE_2P5V_INT_POW 0b010 +#define AD74413R_ADC_RANGE_5V_BI_DIR 0b011 +#define AD74413R_ADC_REJECTION_50_60 0b00 +#define AD74413R_ADC_REJECTION_NONE 0b01 +#define AD74413R_ADC_REJECTION_50_60_HART 0b10 +#define AD74413R_ADC_REJECTION_HART 0b11 + +#define AD74413R_REG_DIN_CONFIG_X(x) (0x09 + (x)) +#define AD74413R_DIN_DEBOUNCE_MASK GENMASK(4, 0) +#define AD74413R_DIN_DEBOUNCE_LEN BIT(5) +#define AD74413R_DIN_SINK_MASK GENMASK(9, 6) + +#define AD74413R_REG_DAC_CODE_X(x) (0x16 + (x)) +#define AD74413R_DAC_CODE_MAX GENMASK(12, 0) +#define AD74413R_DAC_VOLTAGE_MAX 11000 + +#define AD74413R_REG_GPO_PAR_DATA 0x0d +#define AD74413R_REG_GPO_CONFIG_X(x) (0x0e + (x)) +#define AD74413R_GPO_CONFIG_DATA_MASK BIT(3) +#define AD74413R_GPO_CONFIG_SELECT_MASK GENMASK(2, 0) +#define AD74413R_GPO_CONFIG_100K_PULL_DOWN 0b000 +#define AD74413R_GPO_CONFIG_LOGIC 0b001 +#define AD74413R_GPO_CONFIG_LOGIC_PARALLEL 0b010 +#define AD74413R_GPO_CONFIG_COMPARATOR 0b011 +#define AD74413R_GPO_CONFIG_HIGH_IMPEDANCE 0b100 + +#define AD74413R_REG_ADC_CONV_CTRL 0x23 +#define AD74413R_CONV_SEQ_MASK GENMASK(9, 8) +#define AD74413R_CONV_SEQ_ON 0b00 +#define AD74413R_CONV_SEQ_SINGLE 0b01 +#define AD74413R_CONV_SEQ_CONTINUOUS 0b10 +#define AD74413R_CONV_SEQ_OFF 0b11 +#define AD74413R_CH_EN_MASK(x) BIT(x) + +#define AD74413R_REG_DIN_COMP_OUT 0x25 + +#define AD74413R_REG_ADC_RESULT_X(x) (0x26 + (x)) +#define AD74413R_ADC_RESULT_MAX GENMASK(15, 0) + +#define AD74413R_REG_READ_SELECT 0x41 + +#define AD74413R_REG_CMD_KEY 0x44 +#define AD74413R_CMD_KEY_LDAC 0x953a +#define AD74413R_CMD_KEY_RESET1 0x15fa +#define AD74413R_CMD_KEY_RESET2 0xaf51 + +static const int ad74413r_adc_sampling_rates[] = { + 20, 4800, +}; + +static const int ad74413r_adc_sampling_rates_hart[] = { + 10, 20, 1200, 4800, +}; + +static int ad74413r_crc(u8 *buf) +{ + return crc8(ad74413r_crc8_table, buf, 3, 0); +} + +static void ad74413r_format_reg_write(u8 reg, u16 val, u8 *buf) +{ + buf[0] = reg; + put_unaligned_be16(val, &buf[1]); + buf[3] = ad74413r_crc(buf); +} + +static int ad74413r_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad74413r_state *st = context; + + ad74413r_format_reg_write(reg, val, st->reg_tx_buf); + + return spi_write(st->spi, st->reg_tx_buf, AD74413R_FRAME_SIZE); +} + +static int ad74413r_crc_check(struct ad74413r_state *st, u8 *buf) +{ + u8 expected_crc = ad74413r_crc(buf); + + if (buf[3] != expected_crc) { + dev_err(st->dev, "Bad CRC %02x for %02x%02x%02x\n", + buf[3], buf[0], buf[1], buf[2]); + return -EINVAL; + } + + return 0; +} + +static int ad74413r_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad74413r_state *st = context; + struct spi_transfer reg_read_xfer[] = { + { + .tx_buf = st->reg_tx_buf, + .len = AD74413R_FRAME_SIZE, + .cs_change = 1, + }, + { + .rx_buf = st->reg_rx_buf, + .len = AD74413R_FRAME_SIZE, + }, + }; + int ret; + + ad74413r_format_reg_write(AD74413R_REG_READ_SELECT, reg, + st->reg_tx_buf); + + ret = spi_sync_transfer(st->spi, reg_read_xfer, + ARRAY_SIZE(reg_read_xfer)); + if (ret) + return ret; + + ret = ad74413r_crc_check(st, st->reg_rx_buf); + if (ret) + return ret; + + *val = get_unaligned_be16(&st->reg_rx_buf[1]); + + return 0; +} + +static const struct regmap_config ad74413r_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_read = ad74413r_reg_read, + .reg_write = ad74413r_reg_write, +}; + +static int ad74413r_set_gpo_config(struct ad74413r_state *st, + unsigned int offset, u8 mode) +{ + return regmap_update_bits(st->regmap, AD74413R_REG_GPO_CONFIG_X(offset), + AD74413R_GPO_CONFIG_SELECT_MASK, mode); +} + +static const unsigned int ad74413r_debounce_map[AD74413R_DIN_DEBOUNCE_LEN] = { + 0, 13, 18, 24, 32, 42, 56, 75, + 100, 130, 180, 240, 320, 420, 560, 750, + 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500, + 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000, +}; + +static int ad74413r_set_comp_debounce(struct ad74413r_state *st, + unsigned int offset, + unsigned int debounce) +{ + unsigned int val = AD74413R_DIN_DEBOUNCE_LEN - 1; + unsigned int i; + + for (i = 0; i < AD74413R_DIN_DEBOUNCE_LEN; i++) + if (debounce <= ad74413r_debounce_map[i]) { + val = i; + break; + } + + return regmap_update_bits(st->regmap, + AD74413R_REG_DIN_CONFIG_X(offset), + AD74413R_DIN_DEBOUNCE_MASK, + val); +} + +static int ad74413r_set_comp_drive_strength(struct ad74413r_state *st, + unsigned int offset, + unsigned int strength) +{ + strength = min(strength, 1800U); + + return regmap_update_bits(st->regmap, AD74413R_REG_DIN_CONFIG_X(offset), + AD74413R_DIN_SINK_MASK, + FIELD_PREP(AD74413R_DIN_SINK_MASK, strength / 120)); +} + + +static void ad74413r_gpio_set(struct gpio_chip *chip, + unsigned int offset, int val) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + int ret; + + ret = ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_LOGIC); + if (ret) + return; + + regmap_update_bits(st->regmap, AD74413R_REG_GPO_CONFIG_X(real_offset), + AD74413R_GPO_CONFIG_DATA_MASK, + val ? AD74413R_GPO_CONFIG_DATA_MASK : 0); +} + +static void ad74413r_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned long real_mask = 0; + unsigned long real_bits = 0; + unsigned int offset; + int ret; + + for_each_set_bit(offset, mask, chip->ngpio) { + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + + ret = ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_LOGIC_PARALLEL); + if (ret) + return; + + real_mask |= BIT(real_offset); + if (*bits & offset) + real_bits |= BIT(real_offset); + } + + regmap_update_bits(st->regmap, AD74413R_REG_GPO_PAR_DATA, + real_mask, real_bits); +} + +static int ad74413r_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->comp_gpio_offsets[offset]; + unsigned int status; + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &status); + if (ret) + return ret; + + status &= BIT(real_offset); + + return status ? 1 : 0; +} + +static int ad74413r_gpio_get_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int offset; + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &val); + if (ret) + return ret; + + for_each_set_bit(offset, mask, chip->ngpio) { + unsigned int real_offset = st->comp_gpio_offsets[offset]; + + __assign_bit(offset, bits, val & BIT(real_offset)); + } + + return ret; +} + +static int ad74413r_gpio_get_gpo_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad74413r_gpio_get_comp_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_IN; +} + +static int ad74413r_gpio_set_gpo_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long config) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_BIAS_PULL_DOWN: + return ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_100K_PULL_DOWN); + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + return ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_HIGH_IMPEDANCE); + default: + return -ENOTSUPP; + } +} + +static int ad74413r_gpio_set_comp_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long config) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->comp_gpio_offsets[offset]; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_INPUT_DEBOUNCE: + return ad74413r_set_comp_debounce(st, real_offset, + pinconf_to_config_argument(config)); + default: + return -ENOTSUPP; + } +} + +static int ad74413r_reset(struct ad74413r_state *st) +{ + int ret; + + if (st->reset_gpio) { + gpiod_set_value_cansleep(st->reset_gpio, 1); + fsleep(50); + gpiod_set_value_cansleep(st->reset_gpio, 0); + return 0; + } + + ret = regmap_write(st->regmap, AD74413R_REG_CMD_KEY, + AD74413R_CMD_KEY_RESET1); + if (ret) + return ret; + + return regmap_write(st->regmap, AD74413R_REG_CMD_KEY, + AD74413R_CMD_KEY_RESET2); +} + +static int ad74413r_set_channel_dac_code(struct ad74413r_state *st, + unsigned int channel, int dac_code) +{ + struct reg_sequence reg_seq[2] = { + { AD74413R_REG_DAC_CODE_X(channel), dac_code }, + { AD74413R_REG_CMD_KEY, AD74413R_CMD_KEY_LDAC }, + }; + + return regmap_multi_reg_write(st->regmap, reg_seq, 2); +} + +static int ad74413r_set_channel_function(struct ad74413r_state *st, + unsigned int channel, u8 func) +{ + int ret; + + ret = regmap_update_bits(st->regmap, + AD74413R_REG_CH_FUNC_SETUP_X(channel), + AD74413R_CH_FUNC_SETUP_MASK, func); + if (ret) + return ret; + + if (func == CH_FUNC_CURRENT_INPUT_LOOP_POWER) + ret = regmap_set_bits(st->regmap, + AD74413R_REG_ADC_CONFIG_X(channel), + AD74413R_ADC_CONFIG_CH_200K_TO_GND); + + return ret; +} + +static int ad74413r_set_adc_conv_seq(struct ad74413r_state *st, + unsigned int status) +{ + int ret; + + /* + * These bits do not clear when a conversion completes. + * To enable a subsequent conversion, repeat the write. + */ + ret = regmap_write_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL, + AD74413R_CONV_SEQ_MASK, + FIELD_PREP(AD74413R_CONV_SEQ_MASK, status)); + if (ret) + return ret; + + /* + * Wait 100us before starting conversions. + */ + usleep_range(100, 120); + + return 0; +} + +static int ad74413r_set_adc_channel_enable(struct ad74413r_state *st, + unsigned int channel, + bool status) +{ + return regmap_update_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL, + AD74413R_CH_EN_MASK(channel), + status ? AD74413R_CH_EN_MASK(channel) : 0); +} + +static int ad74413r_get_adc_range(struct ad74413r_state *st, + unsigned int channel, + unsigned int *val) +{ + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val); + if (ret) + return ret; + + *val = FIELD_GET(AD74413R_ADC_CONFIG_RANGE_MASK, *val); + + return 0; +} + +static int ad74413r_get_adc_rejection(struct ad74413r_state *st, + unsigned int channel, + unsigned int *val) +{ + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val); + if (ret) + return ret; + + *val = FIELD_GET(AD74413R_ADC_CONFIG_REJECTION_MASK, *val); + + return 0; +} + +static int ad74413r_set_adc_rejection(struct ad74413r_state *st, + unsigned int channel, + unsigned int val) +{ + return regmap_update_bits(st->regmap, + AD74413R_REG_ADC_CONFIG_X(channel), + AD74413R_ADC_CONFIG_REJECTION_MASK, + FIELD_PREP(AD74413R_ADC_CONFIG_REJECTION_MASK, + val)); +} + +static int ad74413r_rejection_to_rate(struct ad74413r_state *st, + unsigned int rej, int *val) +{ + switch (rej) { + case AD74413R_ADC_REJECTION_50_60: + *val = 20; + return 0; + case AD74413R_ADC_REJECTION_NONE: + *val = 4800; + return 0; + case AD74413R_ADC_REJECTION_50_60_HART: + *val = 10; + return 0; + case AD74413R_ADC_REJECTION_HART: + *val = 1200; + return 0; + default: + dev_err(st->dev, "ADC rejection invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_rate_to_rejection(struct ad74413r_state *st, + int rate, unsigned int *val) +{ + switch (rate) { + case 20: + *val = AD74413R_ADC_REJECTION_50_60; + return 0; + case 4800: + *val = AD74413R_ADC_REJECTION_NONE; + return 0; + case 10: + *val = AD74413R_ADC_REJECTION_50_60_HART; + return 0; + case 1200: + *val = AD74413R_ADC_REJECTION_HART; + return 0; + default: + dev_err(st->dev, "ADC rate invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_range(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + *val = 10000; + return 0; + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + case AD74413R_ADC_RANGE_2P5V_INT_POW: + *val = 2500; + return 0; + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = 5000; + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_offset(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + *val = 0; + return 0; + case AD74413R_ADC_RANGE_2P5V_INT_POW: + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = -2500; + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_offset_raw(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + *val = 0; + return 0; + case AD74413R_ADC_RANGE_2P5V_INT_POW: + *val = -((int)AD74413R_ADC_RESULT_MAX); + return 0; + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = -((int)AD74413R_ADC_RESULT_MAX / 2); + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_get_output_voltage_scale(struct ad74413r_state *st, + int *val, int *val2) +{ + *val = AD74413R_DAC_VOLTAGE_MAX; + *val2 = AD74413R_DAC_CODE_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_output_current_scale(struct ad74413r_state *st, + int *val, int *val2) +{ + *val = regulator_get_voltage(st->refin_reg); + *val2 = st->sense_resistor_ohms * AD74413R_DAC_CODE_MAX * 1000; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_input_voltage_scale(struct ad74413r_state *st, + unsigned int channel, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, val); + if (ret) + return ret; + + *val2 = AD74413R_ADC_RESULT_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_input_voltage_offset(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_offset_raw(st, range, val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad74413r_get_input_current_scale(struct ad74413r_state *st, + unsigned int channel, int *val, + int *val2) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, val); + if (ret) + return ret; + + *val2 = AD74413R_ADC_RESULT_MAX * st->sense_resistor_ohms; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413_get_input_current_offset(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int range; + int voltage_range; + int voltage_offset; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, &voltage_range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_offset(st, range, &voltage_offset); + if (ret) + return ret; + + *val = voltage_offset * (int)AD74413R_ADC_RESULT_MAX / voltage_range; + + return IIO_VAL_INT; +} + +static int ad74413r_get_adc_rate(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int rejection; + int ret; + + ret = ad74413r_get_adc_rejection(st, channel, &rejection); + if (ret) + return ret; + + ret = ad74413r_rejection_to_rate(st, rejection, val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad74413r_set_adc_rate(struct ad74413r_state *st, + unsigned int channel, int val) +{ + unsigned int rejection; + int ret; + + ret = ad74413r_rate_to_rejection(st, val, &rejection); + if (ret) + return ret; + + return ad74413r_set_adc_rejection(st, channel, rejection); +} + +static irqreturn_t ad74413r_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad74413r_state *st = iio_priv(indio_dev); + u8 *rx_buf = st->adc_samples_buf.rx_buf; + unsigned int i; + int ret; + + ret = spi_sync(st->spi, &st->adc_samples_msg); + if (ret) + goto out; + + for (i = 0; i < st->adc_active_channels; i++) + ad74413r_crc_check(st, &rx_buf[i * AD74413R_FRAME_SIZE]); + + iio_push_to_buffers_with_timestamp(indio_dev, &st->adc_samples_buf, + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ad74413r_adc_data_interrupt(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad74413r_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->adc_data_completion); + + return IRQ_HANDLED; +} + +static int _ad74413r_get_single_adc_result(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int uval; + int ret; + + reinit_completion(&st->adc_data_completion); + + ret = ad74413r_set_adc_channel_enable(st, channel, true); + if (ret) + return ret; + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_SINGLE); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&st->adc_data_completion, + msecs_to_jiffies(1000)); + if (!ret) { + ret = -ETIMEDOUT; + return ret; + } + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_RESULT_X(channel), + &uval); + if (ret) + return ret; + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); + if (ret) + return ret; + + ret = ad74413r_set_adc_channel_enable(st, channel, false); + if (ret) + return ret; + + *val = uval; + + return IIO_VAL_INT; +} + +static int ad74413r_get_single_adc_result(struct iio_dev *indio_dev, + unsigned int channel, int *val) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + int ret; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + mutex_lock(&st->lock); + ret = _ad74413r_get_single_adc_result(st, channel, val); + mutex_unlock(&st->lock); + + iio_device_release_direct_mode(indio_dev); + + return ret; +} + +static void ad74413r_adc_to_resistance_result(int adc_result, int *val) +{ + if (adc_result == AD74413R_ADC_RESULT_MAX) + adc_result = AD74413R_ADC_RESULT_MAX - 1; + + *val = DIV_ROUND_CLOSEST(adc_result * 2100, + AD74413R_ADC_RESULT_MAX - adc_result); +} + +static int ad74413r_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct spi_transfer *xfer = st->adc_samples_xfer; + u8 *rx_buf = st->adc_samples_buf.rx_buf; + u8 *tx_buf = st->adc_samples_tx_buf; + unsigned int channel; + int ret = -EINVAL; + + mutex_lock(&st->lock); + + spi_message_init(&st->adc_samples_msg); + st->adc_active_channels = 0; + + for_each_clear_bit(channel, active_scan_mask, AD74413R_CHANNEL_MAX) { + ret = ad74413r_set_adc_channel_enable(st, channel, false); + if (ret) + goto out; + } + + if (*active_scan_mask == 0) + 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(channel, active_scan_mask, AD74413R_CHANNEL_MAX) { + ret = ad74413r_set_adc_channel_enable(st, channel, true); + if (ret) + goto out; + + st->adc_active_channels++; + + if (xfer == st->adc_samples_xfer) + xfer->rx_buf = NULL; + else + xfer->rx_buf = rx_buf; + + xfer->tx_buf = tx_buf; + xfer->len = AD74413R_FRAME_SIZE; + xfer->cs_change = 1; + + ad74413r_format_reg_write(AD74413R_REG_READ_SELECT, + AD74413R_REG_ADC_RESULT_X(channel), + tx_buf); + + spi_message_add_tail(xfer, &st->adc_samples_msg); + + tx_buf += AD74413R_FRAME_SIZE; + if (xfer != st->adc_samples_xfer) + rx_buf += AD74413R_FRAME_SIZE; + xfer++; + } + + xfer->rx_buf = rx_buf; + xfer->tx_buf = NULL; + xfer->len = AD74413R_FRAME_SIZE; + xfer->cs_change = 0; + + spi_message_add_tail(xfer, &st->adc_samples_msg); + +out: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad74413r_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_CONTINUOUS); +} + +static int ad74413r_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); +} + +static int ad74413r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) + return ad74413r_get_output_voltage_scale(st, + val, val2); + else + return ad74413r_get_input_voltage_scale(st, + chan->channel, val, val2); + case IIO_CURRENT: + if (chan->output) + return ad74413r_get_output_current_scale(st, + val, val2); + else + return ad74413r_get_input_current_scale(st, + chan->channel, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + return ad74413r_get_input_voltage_offset(st, + chan->channel, val); + case IIO_CURRENT: + return ad74413_get_input_current_offset(st, + chan->channel, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (chan->output) + return -EINVAL; + + return ad74413r_get_single_adc_result(indio_dev, chan->channel, + val); + case IIO_CHAN_INFO_PROCESSED: { + int ret; + + ret = ad74413r_get_single_adc_result(indio_dev, chan->channel, + val); + if (ret < 0) + return ret; + + ad74413r_adc_to_resistance_result(*val, val); + + return ret; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return ad74413r_get_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74413r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + if (val < 0 || val > AD74413R_DAC_CODE_MAX) { + dev_err(st->dev, "Invalid DAC code\n"); + return -EINVAL; + } + + return ad74413r_set_channel_dac_code(st, chan->channel, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad74413r_set_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74413r_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (st->chip_info->hart_support) { + *vals = ad74413r_adc_sampling_rates_hart; + *length = ARRAY_SIZE(ad74413r_adc_sampling_rates_hart); + } else { + *vals = ad74413r_adc_sampling_rates; + *length = ARRAY_SIZE(ad74413r_adc_sampling_rates); + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_buffer_setup_ops ad74413r_buffer_ops = { + .postenable = &ad74413r_buffer_postenable, + .predisable = &ad74413r_buffer_predisable, +}; + +static const struct iio_trigger_ops ad74413r_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static const struct iio_info ad74413r_info = { + .read_raw = &ad74413r_read_raw, + .write_raw = &ad74413r_write_raw, + .read_avail = &ad74413r_read_avail, + .update_scan_mode = &ad74413r_update_scan_mode, +}; + +#define AD74413R_DAC_CHANNEL(_type, extra_mask_separate) \ + { \ + .type = (_type), \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | (extra_mask_separate), \ + } + +#define AD74413R_ADC_CHANNEL(_type, extra_mask_separate) \ + { \ + .type = (_type), \ + .indexed = 1, \ + .output = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | (extra_mask_separate), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD74413R_ADC_VOLTAGE_CHANNEL \ + AD74413R_ADC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +#define AD74413R_ADC_CURRENT_CHANNEL \ + AD74413R_ADC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +static struct iio_chan_spec ad74413r_voltage_output_channels[] = { + AD74413R_DAC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static struct iio_chan_spec ad74413r_current_output_channels[] = { + AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +static struct iio_chan_spec ad74413r_voltage_input_channels[] = { + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +static struct iio_chan_spec ad74413r_current_input_channels[] = { + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static struct iio_chan_spec ad74413r_current_input_loop_channels[] = { + AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static struct iio_chan_spec ad74413r_resistance_input_channels[] = { + AD74413R_ADC_CHANNEL(IIO_RESISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)), +}; + +static struct iio_chan_spec ad74413r_digital_input_channels[] = { + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +#define _AD74413R_CHANNELS(_channels) \ + { \ + .channels = _channels, \ + .num_channels = ARRAY_SIZE(_channels), \ + } + +#define AD74413R_CHANNELS(name) \ + _AD74413R_CHANNELS(ad74413r_ ## name ## _channels) + +static const struct ad74413r_channels ad74413r_channels_map[] = { + [CH_FUNC_HIGH_IMPEDANCE] = AD74413R_CHANNELS(voltage_input), + [CH_FUNC_VOLTAGE_OUTPUT] = AD74413R_CHANNELS(voltage_output), + [CH_FUNC_CURRENT_OUTPUT] = AD74413R_CHANNELS(current_output), + [CH_FUNC_VOLTAGE_INPUT] = AD74413R_CHANNELS(voltage_input), + [CH_FUNC_CURRENT_INPUT_EXT_POWER] = AD74413R_CHANNELS(current_input), + [CH_FUNC_CURRENT_INPUT_LOOP_POWER] = AD74413R_CHANNELS(current_input_loop), + [CH_FUNC_RESISTANCE_INPUT] = AD74413R_CHANNELS(resistance_input), + [CH_FUNC_DIGITAL_INPUT_LOGIC] = AD74413R_CHANNELS(digital_input), + [CH_FUNC_DIGITAL_INPUT_LOOP_POWER] = AD74413R_CHANNELS(digital_input), + [CH_FUNC_CURRENT_INPUT_EXT_POWER_HART] = AD74413R_CHANNELS(current_input), + [CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART] = AD74413R_CHANNELS(current_input), +}; + +static int ad74413r_parse_channel_config(struct iio_dev *indio_dev, + struct fwnode_handle *channel_node) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct ad74413r_channel_config *config; + u32 index; + int ret; + + ret = fwnode_property_read_u32(channel_node, "reg", &index); + if (ret) { + dev_err(st->dev, "Failed to read channel reg: %d\n", ret); + return ret; + } + + if (index >= AD74413R_CHANNEL_MAX) { + dev_err(st->dev, "Channel index %u is too large\n", index); + return -EINVAL; + } + + config = &st->channel_configs[index]; + if (config->initialized) { + dev_err(st->dev, "Channel %u already initialized\n", index); + return -EINVAL; + } + + config->func = CH_FUNC_HIGH_IMPEDANCE; + fwnode_property_read_u32(channel_node, "adi,ch-func", &config->func); + + if (config->func < CH_FUNC_MIN || config->func > CH_FUNC_MAX) { + dev_err(st->dev, "Invalid channel function %u\n", config->func); + return -EINVAL; + } + + if (!st->chip_info->hart_support && + (config->func == CH_FUNC_CURRENT_INPUT_EXT_POWER_HART || + config->func == CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART)) { + dev_err(st->dev, "Unsupported HART function %u\n", config->func); + return -EINVAL; + } + + if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC || + config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER) + st->num_comparator_gpios++; + + config->gpo_comparator = fwnode_property_read_bool(channel_node, + "adi,gpo-comparator"); + + fwnode_property_read_u32(channel_node, "drive-strength-microamp", + &config->drive_strength); + + if (!config->gpo_comparator) + st->num_gpo_gpios++; + + indio_dev->num_channels += ad74413r_channels_map[config->func].num_channels; + + config->initialized = true; + + return 0; +} + +static int ad74413r_parse_channel_configs(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct fwnode_handle *channel_node = NULL; + int ret; + + fwnode_for_each_available_child_node(dev_fwnode(st->dev), channel_node) { + ret = ad74413r_parse_channel_config(indio_dev, channel_node); + if (ret) + goto put_channel_node; + } + + return 0; + +put_channel_node: + fwnode_handle_put(channel_node); + + return ret; +} + +static int ad74413r_setup_channels(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct ad74413r_channel_config *config; + struct iio_chan_spec *channels, *chans; + unsigned int i, num_chans, chan_i; + int ret; + + channels = devm_kcalloc(st->dev, sizeof(*channels), + indio_dev->num_channels, GFP_KERNEL); + if (!channels) + return -ENOMEM; + + indio_dev->channels = channels; + + for (i = 0; i < AD74413R_CHANNEL_MAX; i++) { + config = &st->channel_configs[i]; + chans = ad74413r_channels_map[config->func].channels; + num_chans = ad74413r_channels_map[config->func].num_channels; + + memcpy(channels, chans, num_chans * sizeof(*chans)); + + for (chan_i = 0; chan_i < num_chans; chan_i++) { + struct iio_chan_spec *chan = &channels[chan_i]; + + chan->channel = i; + if (chan->output) + chan->scan_index = -1; + else + chan->scan_index = i; + } + + ret = ad74413r_set_channel_function(st, i, config->func); + if (ret) + return ret; + + channels += num_chans; + } + + return 0; +} + +static int ad74413r_setup_gpios(struct ad74413r_state *st) +{ + struct ad74413r_channel_config *config; + unsigned int comp_gpio_i = 0; + unsigned int gpo_gpio_i = 0; + unsigned int i; + u8 gpo_config; + u32 strength; + int ret; + + for (i = 0; i < AD74413R_CHANNEL_MAX; i++) { + config = &st->channel_configs[i]; + + if (config->gpo_comparator) { + gpo_config = AD74413R_GPO_CONFIG_COMPARATOR; + } else { + gpo_config = AD74413R_GPO_CONFIG_LOGIC; + st->gpo_gpio_offsets[gpo_gpio_i++] = i; + } + + if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC || + config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER) { + st->comp_gpio_offsets[comp_gpio_i++] = i; + + strength = config->drive_strength; + ret = ad74413r_set_comp_drive_strength(st, i, strength); + if (ret) + return ret; + } + + ret = ad74413r_set_gpo_config(st, i, gpo_config); + if (ret) + return ret; + } + + return 0; +} + +static void ad74413r_regulator_disable(void *regulator) +{ + regulator_disable(regulator); +} + +static int ad74413r_probe(struct spi_device *spi) +{ + struct ad74413r_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + st->dev = &spi->dev; + st->chip_info = device_get_match_data(&spi->dev); + if (!st->chip_info) { + const struct spi_device_id *id = spi_get_device_id(spi); + + if (id) + st->chip_info = + (struct ad74413r_chip_info *)id->driver_data; + if (!st->chip_info) + return -EINVAL; + } + + mutex_init(&st->lock); + init_completion(&st->adc_data_completion); + + st->regmap = devm_regmap_init(st->dev, NULL, st, + &ad74413r_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + st->reset_gpio = devm_gpiod_get_optional(st->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(st->reset_gpio)) + return PTR_ERR(st->reset_gpio); + + st->refin_reg = devm_regulator_get(st->dev, "refin"); + if (IS_ERR(st->refin_reg)) + return dev_err_probe(st->dev, PTR_ERR(st->refin_reg), + "Failed to get refin regulator\n"); + + ret = regulator_enable(st->refin_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(st->dev, ad74413r_regulator_disable, + st->refin_reg); + if (ret) + return ret; + + st->sense_resistor_ohms = 100000000; + device_property_read_u32(st->dev, "shunt-resistor-micro-ohms", + &st->sense_resistor_ohms); + st->sense_resistor_ohms /= 1000000; + + st->trig = devm_iio_trigger_alloc(st->dev, "%s-dev%d", + st->chip_info->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad74413r_trigger_ops; + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_iio_trigger_register(st->dev, st->trig); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad74413r_info; + indio_dev->trig = iio_trigger_get(st->trig); + + ret = ad74413r_reset(st); + if (ret) + return ret; + + ret = ad74413r_parse_channel_configs(indio_dev); + if (ret) + return ret; + + ret = ad74413r_setup_channels(indio_dev); + if (ret) + return ret; + + ret = ad74413r_setup_gpios(st); + if (ret) + return ret; + + if (st->num_gpo_gpios) { + st->gpo_gpiochip.owner = THIS_MODULE; + st->gpo_gpiochip.label = st->chip_info->name; + st->gpo_gpiochip.base = -1; + st->gpo_gpiochip.ngpio = st->num_gpo_gpios; + st->gpo_gpiochip.parent = st->dev; + st->gpo_gpiochip.can_sleep = true; + st->gpo_gpiochip.set = ad74413r_gpio_set; + st->gpo_gpiochip.set_multiple = ad74413r_gpio_set_multiple; + st->gpo_gpiochip.set_config = ad74413r_gpio_set_gpo_config; + st->gpo_gpiochip.get_direction = + ad74413r_gpio_get_gpo_direction; + + ret = devm_gpiochip_add_data(st->dev, &st->gpo_gpiochip, st); + if (ret) + return ret; + } + + if (st->num_comparator_gpios) { + st->comp_gpiochip.owner = THIS_MODULE; + st->comp_gpiochip.label = st->chip_info->name; + st->comp_gpiochip.base = -1; + st->comp_gpiochip.ngpio = st->num_comparator_gpios; + st->comp_gpiochip.parent = st->dev; + st->comp_gpiochip.can_sleep = true; + st->comp_gpiochip.get = ad74413r_gpio_get; + st->comp_gpiochip.get_multiple = ad74413r_gpio_get_multiple; + st->comp_gpiochip.set_config = ad74413r_gpio_set_comp_config; + st->comp_gpiochip.get_direction = + ad74413r_gpio_get_comp_direction; + + ret = devm_gpiochip_add_data(st->dev, &st->comp_gpiochip, st); + if (ret) + return ret; + } + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); + if (ret) + return ret; + + ret = devm_request_irq(st->dev, spi->irq, ad74413r_adc_data_interrupt, + 0, st->chip_info->name, indio_dev); + if (ret) + return dev_err_probe(st->dev, ret, "Failed to request irq\n"); + + ret = devm_iio_triggered_buffer_setup(st->dev, indio_dev, + &iio_pollfunc_store_time, + &ad74413r_trigger_handler, + &ad74413r_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(st->dev, indio_dev); +} + +static int ad74413r_unregister_driver(struct spi_driver *spi) +{ + spi_unregister_driver(spi); + + return 0; +} + +static int __init ad74413r_register_driver(struct spi_driver *spi) +{ + crc8_populate_msb(ad74413r_crc8_table, AD74413R_CRC_POLYNOMIAL); + + return spi_register_driver(spi); +} + +static const struct ad74413r_chip_info ad74412r_chip_info_data = { + .hart_support = false, + .name = "ad74412r", +}; + +static const struct ad74413r_chip_info ad74413r_chip_info_data = { + .hart_support = true, + .name = "ad74413r", +}; + +static const struct of_device_id ad74413r_dt_id[] = { + { + .compatible = "adi,ad74412r", + .data = &ad74412r_chip_info_data, + }, + { + .compatible = "adi,ad74413r", + .data = &ad74413r_chip_info_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ad74413r_dt_id); + +static const struct spi_device_id ad74413r_spi_id[] = { + { .name = "ad74412r", .driver_data = (kernel_ulong_t)&ad74412r_chip_info_data }, + { .name = "ad74413r", .driver_data = (kernel_ulong_t)&ad74413r_chip_info_data }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad74413r_spi_id); + +static struct spi_driver ad74413r_driver = { + .driver = { + .name = "ad74413r", + .of_match_table = ad74413r_dt_id, + }, + .probe = ad74413r_probe, + .id_table = ad74413r_spi_id, +}; + +module_driver(ad74413r_driver, + ad74413r_register_driver, + ad74413r_unregister_driver); + +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD74413R ADDAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/addac/stx104.c b/drivers/iio/addac/stx104.c new file mode 100644 index 0000000000..d1f7ce033b --- /dev/null +++ b/drivers/iio/addac/stx104.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the Apex Embedded Systems STX104 + * Copyright (C) 2016 William Breathitt Gray + */ +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/types.h> +#include <linux/isa.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#define STX104_OUT_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .output = 1 \ +} +#define STX104_IN_CHAN(chan, diff) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .channel2 = chan, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .differential = diff \ +} + +#define STX104_NUM_OUT_CHAN 2 + +#define STX104_EXTENT 16 + +static unsigned int base[max_num_isa_dev(STX104_EXTENT)]; +static unsigned int num_stx104; +module_param_hw_array(base, uint, ioport, &num_stx104, 0); +MODULE_PARM_DESC(base, "Apex Embedded Systems STX104 base addresses"); + +#define STX104_AIO_BASE 0x0 +#define STX104_SOFTWARE_STROBE STX104_AIO_BASE +#define STX104_ADC_DATA STX104_AIO_BASE +#define STX104_ADC_CHANNEL (STX104_AIO_BASE + 0x2) +#define STX104_DIO_REG (STX104_AIO_BASE + 0x3) +#define STX104_DAC_BASE (STX104_AIO_BASE + 0x4) +#define STX104_ADC_STATUS (STX104_AIO_BASE + 0x8) +#define STX104_ADC_CONTROL (STX104_AIO_BASE + 0x9) +#define STX104_ADC_CONFIGURATION (STX104_AIO_BASE + 0x11) + +#define STX104_AIO_DATA_STRIDE 2 +#define STX104_DAC_OFFSET(_channel) (STX104_DAC_BASE + STX104_AIO_DATA_STRIDE * (_channel)) + +/* ADC Channel */ +#define STX104_FC GENMASK(3, 0) +#define STX104_LC GENMASK(7, 4) +#define STX104_SINGLE_CHANNEL(_channel) \ + (u8_encode_bits(_channel, STX104_FC) | u8_encode_bits(_channel, STX104_LC)) + +/* ADC Status */ +#define STX104_SD BIT(5) +#define STX104_CNV BIT(7) +#define STX104_DIFFERENTIAL 1 + +/* ADC Control */ +#define STX104_ALSS GENMASK(1, 0) +#define STX104_SOFTWARE_TRIGGER u8_encode_bits(0x0, STX104_ALSS) + +/* ADC Configuration */ +#define STX104_GAIN GENMASK(1, 0) +#define STX104_ADBU BIT(2) +#define STX104_BIPOLAR 0 +#define STX104_GAIN_X1 0 +#define STX104_GAIN_X2 1 +#define STX104_GAIN_X4 2 +#define STX104_GAIN_X8 3 + +/** + * struct stx104_iio - IIO device private data structure + * @lock: synchronization lock to prevent I/O race conditions + * @aio_data_map: Regmap for analog I/O data + * @aio_ctl_map: Regmap for analog I/O control + */ +struct stx104_iio { + struct mutex lock; + struct regmap *aio_data_map; + struct regmap *aio_ctl_map; +}; + +static const struct regmap_range aio_ctl_wr_ranges[] = { + regmap_reg_range(0x0, 0x0), regmap_reg_range(0x2, 0x2), regmap_reg_range(0x9, 0x9), + regmap_reg_range(0x11, 0x11), +}; +static const struct regmap_range aio_ctl_rd_ranges[] = { + regmap_reg_range(0x2, 0x2), regmap_reg_range(0x8, 0x9), regmap_reg_range(0x11, 0x11), +}; +static const struct regmap_range aio_ctl_volatile_ranges[] = { + regmap_reg_range(0x8, 0x8), +}; +static const struct regmap_access_table aio_ctl_wr_table = { + .yes_ranges = aio_ctl_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_wr_ranges), +}; +static const struct regmap_access_table aio_ctl_rd_table = { + .yes_ranges = aio_ctl_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_rd_ranges), +}; +static const struct regmap_access_table aio_ctl_volatile_table = { + .yes_ranges = aio_ctl_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_volatile_ranges), +}; + +static const struct regmap_config aio_ctl_regmap_config = { + .name = "aio_ctl", + .reg_bits = 8, + .reg_stride = 1, + .reg_base = STX104_AIO_BASE, + .val_bits = 8, + .io_port = true, + .wr_table = &aio_ctl_wr_table, + .rd_table = &aio_ctl_rd_table, + .volatile_table = &aio_ctl_volatile_table, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range aio_data_wr_ranges[] = { + regmap_reg_range(0x4, 0x6), +}; +static const struct regmap_range aio_data_rd_ranges[] = { + regmap_reg_range(0x0, 0x0), +}; +static const struct regmap_access_table aio_data_wr_table = { + .yes_ranges = aio_data_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_data_wr_ranges), +}; +static const struct regmap_access_table aio_data_rd_table = { + .yes_ranges = aio_data_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_data_rd_ranges), +}; + +static const struct regmap_config aio_data_regmap_config = { + .name = "aio_data", + .reg_bits = 16, + .reg_stride = STX104_AIO_DATA_STRIDE, + .reg_base = STX104_AIO_BASE, + .val_bits = 16, + .io_port = true, + .wr_table = &aio_data_wr_table, + .rd_table = &aio_data_rd_table, + .volatile_table = &aio_data_rd_table, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config dio_regmap_config = { + .name = "dio", + .reg_bits = 8, + .reg_stride = 1, + .reg_base = STX104_DIO_REG, + .val_bits = 8, + .io_port = true, +}; + +static int stx104_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + int err; + unsigned int adc_config; + unsigned int value; + unsigned int adc_status; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = BIT(u8_get_bits(adc_config, STX104_GAIN)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (chan->output) { + err = regmap_read(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel), + &value); + if (err) + return err; + *val = value; + return IIO_VAL_INT; + } + + mutex_lock(&priv->lock); + + /* select ADC channel */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CHANNEL, + STX104_SINGLE_CHANNEL(chan->channel)); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + + /* + * Trigger ADC sample capture by writing to the 8-bit Software Strobe Register and + * wait for completion; the conversion time range is 5 microseconds to 53.68 seconds + * in steps of 25 nanoseconds. The actual Analog Input Frame Timer time interval is + * calculated as: + * ai_time_frame_ns = ( AIFT + 1 ) * ( 25 nanoseconds ). + * Where 0 <= AIFT <= 2147483648. + */ + err = regmap_write(priv->aio_ctl_map, STX104_SOFTWARE_STROBE, 0); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + err = regmap_read_poll_timeout(priv->aio_ctl_map, STX104_ADC_STATUS, adc_status, + !u8_get_bits(adc_status, STX104_CNV), 0, 53687092); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + + err = regmap_read(priv->aio_data_map, STX104_ADC_DATA, &value); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + *val = value; + + mutex_unlock(&priv->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* get ADC bipolar/unipolar configuration */ + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? -32768 : 0; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* get ADC bipolar/unipolar and gain configuration */ + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = 5; + *val2 = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? 14 : 15; + *val2 += u8_get_bits(adc_config, STX104_GAIN); + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int stx104_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + u8 gain; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Only four gain states (x1, x2, x4, x8) */ + switch (val) { + case 1: + gain = STX104_GAIN_X1; + break; + case 2: + gain = STX104_GAIN_X2; + break; + case 4: + gain = STX104_GAIN_X4; + break; + case 8: + gain = STX104_GAIN_X8; + break; + default: + return -EINVAL; + } + + return regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, gain); + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + if (val < 0 || val > U16_MAX) + return -EINVAL; + + return regmap_write(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel), val); + } + + return -EINVAL; +} + +static const struct iio_info stx104_info = { + .read_raw = stx104_read_raw, + .write_raw = stx104_write_raw +}; + +/* single-ended input channels configuration */ +static const struct iio_chan_spec stx104_channels_sing[] = { + STX104_OUT_CHAN(0), STX104_OUT_CHAN(1), + STX104_IN_CHAN(0, 0), STX104_IN_CHAN(1, 0), STX104_IN_CHAN(2, 0), + STX104_IN_CHAN(3, 0), STX104_IN_CHAN(4, 0), STX104_IN_CHAN(5, 0), + STX104_IN_CHAN(6, 0), STX104_IN_CHAN(7, 0), STX104_IN_CHAN(8, 0), + STX104_IN_CHAN(9, 0), STX104_IN_CHAN(10, 0), STX104_IN_CHAN(11, 0), + STX104_IN_CHAN(12, 0), STX104_IN_CHAN(13, 0), STX104_IN_CHAN(14, 0), + STX104_IN_CHAN(15, 0) +}; +/* differential input channels configuration */ +static const struct iio_chan_spec stx104_channels_diff[] = { + STX104_OUT_CHAN(0), STX104_OUT_CHAN(1), + STX104_IN_CHAN(0, 1), STX104_IN_CHAN(1, 1), STX104_IN_CHAN(2, 1), + STX104_IN_CHAN(3, 1), STX104_IN_CHAN(4, 1), STX104_IN_CHAN(5, 1), + STX104_IN_CHAN(6, 1), STX104_IN_CHAN(7, 1) +}; + +static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base, + unsigned int offset, unsigned int *const reg, + unsigned int *const mask) +{ + /* Output lines are located at same register bit offsets as input lines */ + if (offset >= 4) + offset -= 4; + + *reg = base; + *mask = BIT(offset); + + return 0; +} + +#define STX104_NGPIO 8 +static const char *stx104_names[STX104_NGPIO] = { + "DIN0", "DIN1", "DIN2", "DIN3", "DOUT0", "DOUT1", "DOUT2", "DOUT3" +}; + +static int stx104_init_hw(struct stx104_iio *const priv) +{ + int err; + + /* configure device for software trigger operation */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONTROL, STX104_SOFTWARE_TRIGGER); + if (err) + return err; + + /* initialize gain setting to x1 */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, STX104_GAIN_X1); + if (err) + return err; + + /* initialize DAC outputs to 0V */ + err = regmap_write(priv->aio_data_map, STX104_DAC_BASE, 0); + if (err) + return err; + err = regmap_write(priv->aio_data_map, STX104_DAC_BASE + STX104_AIO_DATA_STRIDE, 0); + if (err) + return err; + + return 0; +} + +static int stx104_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct stx104_iio *priv; + struct gpio_regmap_config gpio_config; + void __iomem *stx104_base; + struct regmap *aio_ctl_map; + struct regmap *aio_data_map; + struct regmap *dio_map; + int err; + unsigned int adc_status; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + if (!devm_request_region(dev, base[id], STX104_EXTENT, + dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + base[id], base[id] + STX104_EXTENT); + return -EBUSY; + } + + stx104_base = devm_ioport_map(dev, base[id], STX104_EXTENT); + if (!stx104_base) + return -ENOMEM; + + aio_ctl_map = devm_regmap_init_mmio(dev, stx104_base, &aio_ctl_regmap_config); + if (IS_ERR(aio_ctl_map)) + return dev_err_probe(dev, PTR_ERR(aio_ctl_map), + "Unable to initialize aio_ctl register map\n"); + + aio_data_map = devm_regmap_init_mmio(dev, stx104_base, &aio_data_regmap_config); + if (IS_ERR(aio_data_map)) + return dev_err_probe(dev, PTR_ERR(aio_data_map), + "Unable to initialize aio_data register map\n"); + + dio_map = devm_regmap_init_mmio(dev, stx104_base, &dio_regmap_config); + if (IS_ERR(dio_map)) + return dev_err_probe(dev, PTR_ERR(dio_map), + "Unable to initialize dio register map\n"); + + priv = iio_priv(indio_dev); + priv->aio_ctl_map = aio_ctl_map; + priv->aio_data_map = aio_data_map; + + indio_dev->info = &stx104_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + err = regmap_read(aio_ctl_map, STX104_ADC_STATUS, &adc_status); + if (err) + return err; + + if (u8_get_bits(adc_status, STX104_SD) == STX104_DIFFERENTIAL) { + indio_dev->num_channels = ARRAY_SIZE(stx104_channels_diff); + indio_dev->channels = stx104_channels_diff; + } else { + indio_dev->num_channels = ARRAY_SIZE(stx104_channels_sing); + indio_dev->channels = stx104_channels_sing; + } + + indio_dev->name = dev_name(dev); + + mutex_init(&priv->lock); + + err = stx104_init_hw(priv); + if (err) + return err; + + err = devm_iio_device_register(dev, indio_dev); + if (err) + return err; + + gpio_config = (struct gpio_regmap_config) { + .parent = dev, + .regmap = dio_map, + .ngpio = STX104_NGPIO, + .names = stx104_names, + .reg_dat_base = GPIO_REGMAP_ADDR(STX104_DIO_REG), + .reg_set_base = GPIO_REGMAP_ADDR(STX104_DIO_REG), + .ngpio_per_reg = STX104_NGPIO, + .reg_mask_xlate = stx104_reg_mask_xlate, + .drvdata = dio_map, + }; + + return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config)); +} + +static struct isa_driver stx104_driver = { + .probe = stx104_probe, + .driver = { + .name = "stx104" + }, +}; + +module_isa_driver(stx104_driver, num_stx104); + +MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); +MODULE_DESCRIPTION("Apex Embedded Systems STX104 IIO driver"); +MODULE_LICENSE("GPL v2"); |