diff options
Diffstat (limited to 'drivers/iio/adc/ad7173.c')
| -rw-r--r-- | drivers/iio/adc/ad7173.c | 1424 |
1 files changed, 1171 insertions, 253 deletions
diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index b26d4575e256..d36612352b44 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -1,12 +1,14 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * AD717x family SPI ADC driver + * AD717x and AD411x family SPI ADC driver * * Supported devices: + * AD4111/AD4112/AD4113/AD4114/AD4115/AD4116 * AD7172-2/AD7172-4/AD7173-8/AD7175-2 * AD7175-8/AD7176-2/AD7177-2 * * Copyright (C) 2015, 2024 Analog Devices, Inc. + * Copyright (C) 2025 BayLibre, SAS */ #include <linux/array_size.h> @@ -34,6 +36,7 @@ #include <linux/units.h> #include <linux/iio/buffer.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> @@ -60,18 +63,32 @@ #define AD7173_CH_SETUP_AINPOS_MASK GENMASK(9, 5) #define AD7173_CH_SETUP_AINNEG_MASK GENMASK(4, 0) +#define AD7173_NO_AINS_PER_CHANNEL 2 #define AD7173_CH_ADDRESS(pos, neg) \ (FIELD_PREP(AD7173_CH_SETUP_AINPOS_MASK, pos) | \ FIELD_PREP(AD7173_CH_SETUP_AINNEG_MASK, neg)) #define AD7173_AIN_TEMP_POS 17 #define AD7173_AIN_TEMP_NEG 18 +#define AD7173_AIN_POW_MON_POS 19 +#define AD7173_AIN_POW_MON_NEG 20 +#define AD7173_AIN_REF_POS 21 +#define AD7173_AIN_REF_NEG 22 + +#define AD7173_IS_REF_INPUT(x) ((x) == AD7173_AIN_REF_POS || \ + (x) == AD7173_AIN_REF_NEG) #define AD7172_2_ID 0x00d0 -#define AD7175_ID 0x0cd0 #define AD7176_ID 0x0c90 +#define AD7175_ID 0x0cd0 #define AD7175_2_ID 0x0cd0 #define AD7172_4_ID 0x2050 #define AD7173_ID 0x30d0 +#define AD4111_ID AD7173_ID +#define AD4112_ID AD7173_ID +#define AD4114_ID AD7173_ID +#define AD4113_ID 0x31d0 +#define AD4116_ID 0x34d0 +#define AD4115_ID 0x38d0 #define AD7175_8_ID 0x3cd0 #define AD7177_ID 0x4fd0 #define AD7173_ID_MASK GENMASK(15, 4) @@ -87,6 +104,7 @@ #define AD7173_GPIO_PDSW BIT(14) #define AD7173_GPIO_OP_EN2_3 BIT(13) +#define AD4111_GPIO_GP_OW_EN BIT(12) #define AD7173_GPIO_MUX_IO BIT(12) #define AD7173_GPIO_SYNC_EN BIT(11) #define AD7173_GPIO_ERR_EN BIT(10) @@ -102,6 +120,7 @@ #define AD7173_GPO12_DATA(x) BIT((x) + 0) #define AD7173_GPO23_DATA(x) BIT((x) + 4) +#define AD4111_GPO01_DATA(x) BIT((x) + 6) #define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) : AD7173_GPO23_DATA(x)) #define AD7173_INTERFACE_DATA_STAT BIT(6) @@ -120,54 +139,99 @@ #define AD7173_VOLTAGE_INT_REF_uV 2500000 #define AD7173_TEMP_SENSIIVITY_uV_per_C 477 #define AD7177_ODR_START_VALUE 0x07 - -#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0) +#define AD4111_SHUNT_RESISTOR_OHM 50 +#define AD4111_DIVIDER_RATIO 10 +#define AD4111_CURRENT_CHAN_CUTOFF 16 +#define AD4111_VINCOM_INPUT 0x10 + +/* pin < num_voltage_in is a normal voltage input */ +/* pin >= num_voltage_in_div is a voltage input without a divider */ +#define AD4111_IS_VINCOM_MISMATCH(pin1, pin2) ((pin1) == AD4111_VINCOM_INPUT && \ + (pin2) < st->info->num_voltage_in && \ + (pin2) >= st->info->num_voltage_in_div) + +#define AD7173_FILTER_SINC3_MAP BIT(15) +#define AD7173_FILTER_SINC3_MAP_DIV GENMASK(14, 0) +#define AD7173_FILTER_ENHFILTEN BIT(11) +#define AD7173_FILTER_ENHFILT_MASK GENMASK(10, 8) +#define AD7173_FILTER_ORDER BIT(6) +#define AD7173_FILTER_ODR_MASK GENMASK(5, 0) #define AD7173_MAX_CONFIGS 8 +#define AD4111_OW_DET_THRSH_MV 300 -enum ad7173_ids { - ID_AD7172_2, - ID_AD7172_4, - ID_AD7173_8, - ID_AD7175_2, - ID_AD7175_8, - ID_AD7176_2, - ID_AD7177_2, -}; +#define AD7173_MODE_CAL_INT_ZERO 0x4 /* Internal Zero-Scale Calibration */ +#define AD7173_MODE_CAL_INT_FULL 0x5 /* Internal Full-Scale Calibration */ +#define AD7173_MODE_CAL_SYS_ZERO 0x6 /* System Zero-Scale Calibration */ +#define AD7173_MODE_CAL_SYS_FULL 0x7 /* System Full-Scale Calibration */ struct ad7173_device_info { const unsigned int *sinc5_data_rates; unsigned int num_sinc5_data_rates; unsigned int odr_start_value; + /* + * AD4116 has both inputs with a voltage divider and without. + * These inputs cannot be mixed in the channel configuration. + * Does not include the VINCOM input. + */ + unsigned int num_voltage_in_div; unsigned int num_channels; unsigned int num_configs; - unsigned int num_inputs; + unsigned int num_voltage_in; unsigned int clock; unsigned int id; char *name; + const struct ad_sigma_delta_info *sd_info; + bool has_current_inputs; + bool has_vincom_input; bool has_temp; + /* ((AVDD1 − AVSS)/5) */ + bool has_pow_supply_monitoring; + bool data_reg_only_16bit; bool has_input_buf; bool has_int_ref; bool has_ref2; + bool has_internal_fs_calibration; + bool has_openwire_det; + bool higher_gpio_bits; u8 num_gpios; }; +enum ad7173_filter_type { + AD7173_FILTER_SINC3, + AD7173_FILTER_SINC5_SINC1, + AD7173_FILTER_SINC5_SINC1_PF1, + AD7173_FILTER_SINC5_SINC1_PF2, + AD7173_FILTER_SINC5_SINC1_PF3, + AD7173_FILTER_SINC5_SINC1_PF4, +}; + struct ad7173_channel_config { + /* Openwire detection threshold */ + unsigned int openwire_thrsh_raw; + int openwire_comp_chan; u8 cfg_slot; bool live; - /* Following fields are used to compare equality. */ + /* + * Following fields are used to compare equality. If you + * make adaptations in it, you most likely also have to adapt + * ad7173_is_setup_equal(), too. + */ struct_group(config_props, bool bipolar; bool input_buf; - u8 odr; + u16 sinc3_odr_div; + u8 sinc5_odr_index; u8 ref_sel; + enum ad7173_filter_type filter_type; ); }; struct ad7173_channel { - unsigned int chan_reg; unsigned int ain; struct ad7173_channel_config cfg; + u8 syscalib_mode; + bool openwire_det_en; }; struct ad7173_state { @@ -181,12 +245,27 @@ struct ad7173_state { struct ida cfg_slots_status; unsigned long long config_usage_counter; unsigned long long *config_cnts; - struct clk *ext_clk; struct clk_hw int_clk_hw; -#if IS_ENABLED(CONFIG_GPIOLIB) struct regmap *reg_gpiocon_regmap; struct gpio_regmap *gpio_regmap; -#endif +}; + +static unsigned int ad4115_sinc5_data_rates[] = { + 24845000, 24845000, 20725000, 20725000, /* 0-3 */ + 15564000, 13841000, 10390000, 10390000, /* 4-7 */ + 4994000, 2499000, 1000000, 500000, /* 8-11 */ + 395500, 200000, 100000, 59890, /* 12-15 */ + 49920, 20000, 16660, 10000, /* 16-19 */ + 5000, 2500, 2500, /* 20-22 */ +}; + +static unsigned int ad4116_sinc5_data_rates[] = { + 12422360, 12422360, 12422360, 12422360, /* 0-3 */ + 10362690, 10362690, 7782100, 6290530, /* 4-7 */ + 5194800, 2496900, 1007600, 499900, /* 8-11 */ + 390600, 200300, 100000, 59750, /* 12-15 */ + 49840, 20000, 16650, 10000, /* 16-19 */ + 5000, 2500, 1250, /* 20-22 */ }; static const unsigned int ad7173_sinc5_data_rates[] = { @@ -204,108 +283,30 @@ static const unsigned int ad7175_sinc5_data_rates[] = { 5000, /* 20 */ }; -static const struct ad7173_device_info ad7173_device_info[] = { - [ID_AD7172_2] = { - .name = "ad7172-2", - .id = AD7172_2_ID, - .num_inputs = 5, - .num_channels = 4, - .num_configs = 4, - .num_gpios = 2, - .has_temp = true, - .has_input_buf = true, - .has_int_ref = true, - .clock = 2 * HZ_PER_MHZ, - .sinc5_data_rates = ad7173_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), - }, - [ID_AD7172_4] = { - .name = "ad7172-4", - .id = AD7172_4_ID, - .num_inputs = 9, - .num_channels = 8, - .num_configs = 8, - .num_gpios = 4, - .has_temp = false, - .has_input_buf = true, - .has_ref2 = true, - .clock = 2 * HZ_PER_MHZ, - .sinc5_data_rates = ad7173_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), - }, - [ID_AD7173_8] = { - .name = "ad7173-8", - .id = AD7173_ID, - .num_inputs = 17, - .num_channels = 16, - .num_configs = 8, - .num_gpios = 4, - .has_temp = true, - .has_input_buf = true, - .has_int_ref = true, - .has_ref2 = true, - .clock = 2 * HZ_PER_MHZ, - .sinc5_data_rates = ad7173_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), - }, - [ID_AD7175_2] = { - .name = "ad7175-2", - .id = AD7175_2_ID, - .num_inputs = 5, - .num_channels = 4, - .num_configs = 4, - .num_gpios = 2, - .has_temp = true, - .has_input_buf = true, - .has_int_ref = true, - .clock = 16 * HZ_PER_MHZ, - .sinc5_data_rates = ad7175_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), - }, - [ID_AD7175_8] = { - .name = "ad7175-8", - .id = AD7175_8_ID, - .num_inputs = 17, - .num_channels = 16, - .num_configs = 8, - .num_gpios = 4, - .has_temp = true, - .has_input_buf = true, - .has_int_ref = true, - .has_ref2 = true, - .clock = 16 * HZ_PER_MHZ, - .sinc5_data_rates = ad7175_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), - }, - [ID_AD7176_2] = { - .name = "ad7176-2", - .id = AD7176_ID, - .num_inputs = 5, - .num_channels = 4, - .num_configs = 4, - .num_gpios = 2, - .has_temp = false, - .has_input_buf = false, - .has_int_ref = true, - .clock = 16 * HZ_PER_MHZ, - .sinc5_data_rates = ad7175_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), - }, - [ID_AD7177_2] = { - .name = "ad7177-2", - .id = AD7177_ID, - .num_inputs = 5, - .num_channels = 4, - .num_configs = 4, - .num_gpios = 2, - .has_temp = true, - .has_input_buf = true, - .has_int_ref = true, - .clock = 16 * HZ_PER_MHZ, - .odr_start_value = AD7177_ODR_START_VALUE, - .sinc5_data_rates = ad7175_sinc5_data_rates, - .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), - }, +/** + * ad7173_sinc3_odr_div_from_odr() - Convert ODR to divider value + * @odr_millihz: ODR (sampling_frequency) in milliHz + * Returns: Divider value for SINC3 filter to pass. + */ +static u16 ad7173_sinc3_odr_div_from_odr(u32 odr_millihz) +{ + /* + * Divider is f_MOD (1 MHz) / 32 / ODR. ODR freq is in milliHz, so + * we need to convert f_MOD to the same units. When SING_CYC=1 or + * multiple channels are enabled (currently always the case), there + * is an additional factor of 3. + */ + u32 div = DIV_ROUND_CLOSEST(MEGA * MILLI, odr_millihz * 32 * 3); + /* Avoid divide by 0 and limit to register field size. */ + return clamp(div, 1U, AD7173_FILTER_SINC3_MAP_DIV); +} + +static unsigned int ad4111_current_channel_config[] = { + /* Ain sel: pos neg */ + 0x1E8, /* 15:IIN0+ 8:IIN0− */ + 0x1C9, /* 14:IIN1+ 9:IIN1− */ + 0x1AA, /* 13:IIN2+ 10:IIN2− */ + 0x18B, /* 12:IIN3+ 11:IIN3− */ }; static const char *const ad7173_ref_sel_str[] = { @@ -319,8 +320,6 @@ static const char *const ad7173_clk_sel[] = { "ext-clk", "xtal" }; -#if IS_ENABLED(CONFIG_GPIOLIB) - static const struct regmap_range ad7173_range_gpio[] = { regmap_reg_range(AD7173_REG_GPIO, AD7173_REG_GPIO), }; @@ -338,6 +337,230 @@ static const struct regmap_config ad7173_regmap_config = { .read_flag_mask = BIT(6), }; +enum { + AD7173_SYSCALIB_ZERO_SCALE, + AD7173_SYSCALIB_FULL_SCALE, +}; + +static const char * const ad7173_syscalib_modes[] = { + [AD7173_SYSCALIB_ZERO_SCALE] = "zero_scale", + [AD7173_SYSCALIB_FULL_SCALE] = "full_scale", +}; + +static int ad7173_set_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + st->channels[chan->address].syscalib_mode = mode; + + return 0; +} + +static int ad7173_get_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].syscalib_mode; +} + +static ssize_t ad7173_write_syscalib(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7173_state *st = iio_priv(indio_dev); + bool sys_calib; + int ret, mode; + + ret = kstrtobool(buf, &sys_calib); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mode = st->channels[chan->address].syscalib_mode; + if (sys_calib) { + if (mode == AD7173_SYSCALIB_ZERO_SCALE) + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_SYS_ZERO, + chan->address); + else + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_SYS_FULL, + chan->address); + } + + iio_device_release_direct(indio_dev); + + return ret ? : len; +} + +static const struct iio_enum ad7173_syscalib_mode_enum = { + .items = ad7173_syscalib_modes, + .num_items = ARRAY_SIZE(ad7173_syscalib_modes), + .set = ad7173_set_syscalib_mode, + .get = ad7173_get_syscalib_mode +}; + +static const char * const ad7173_filter_types_str[] = { + [AD7173_FILTER_SINC3] = "sinc3", + [AD7173_FILTER_SINC5_SINC1] = "sinc5+sinc1", + [AD7173_FILTER_SINC5_SINC1_PF1] = "sinc5+sinc1+pf1", + [AD7173_FILTER_SINC5_SINC1_PF2] = "sinc5+sinc1+pf2", + [AD7173_FILTER_SINC5_SINC1_PF3] = "sinc5+sinc1+pf3", + [AD7173_FILTER_SINC5_SINC1_PF4] = "sinc5+sinc1+pf4", +}; + +static int ad7173_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + st->channels[chan->address].cfg.filter_type = val; + st->channels[chan->address].cfg.live = false; + + iio_device_release_direct(indio_dev); + + return 0; +} + +static int ad7173_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7173_filter_type_enum = { + .items = ad7173_filter_types_str, + .num_items = ARRAY_SIZE(ad7173_filter_types_str), + .set = ad7173_set_filter_type, + .get = ad7173_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad7173_chan_spec_ext_info[] = { + { + .name = "sys_calibration", + .write = ad7173_write_syscalib, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("sys_calibration_mode", IIO_SEPARATE, + &ad7173_syscalib_mode_enum), + IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, + &ad7173_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad7173_temp_chan_spec_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static int ad7173_calibrate_all(struct ad7173_state *st, struct iio_dev *indio_dev) +{ + int ret; + int i; + + for (i = 0; i < st->num_channels; i++) { + if (indio_dev->channels[i].type != IIO_VOLTAGE) + continue; + + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_INT_ZERO, i); + if (ret < 0) + return ret; + + if (st->info->has_internal_fs_calibration) { + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_INT_FULL, i); + if (ret < 0) + return ret; + } + } + + return 0; +} + +/* + * Associative array of channel pairs for open wire detection + * The array is indexed by ain and gives the associated channel pair + * to perform the open wire detection with + * the channel pair [0] is for non differential and pair [1] + * is for differential inputs + */ +static int openwire_ain_to_channel_pair[][2][2] = { +/* AIN Single Differential */ + [0] = { { 0, 15 }, { 1, 2 } }, + [1] = { { 1, 2 }, { 2, 1 } }, + [2] = { { 3, 4 }, { 5, 6 } }, + [3] = { { 5, 6 }, { 6, 5 } }, + [4] = { { 7, 8 }, { 9, 10 } }, + [5] = { { 9, 10 }, { 10, 9 } }, + [6] = { { 11, 12 }, { 13, 14 } }, + [7] = { { 13, 14 }, { 14, 13 } }, +}; + +/* + * Openwire detection on ad4111 works by running the same input measurement + * on two different channels and compare if the difference between the two + * measurements exceeds a certain value (typical 300mV) + */ +static int ad4111_openwire_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + struct ad7173_channel_config *cfg = &adchan->cfg; + int ret, val1, val2; + + ret = regmap_set_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, + AD4111_GPIO_GP_OW_EN); + if (ret) + return ret; + + adchan->cfg.openwire_comp_chan = + openwire_ain_to_channel_pair[chan->channel][chan->differential][0]; + + ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val1); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Error running ad_sigma_delta single conversion: %d", ret); + goto out; + } + + adchan->cfg.openwire_comp_chan = + openwire_ain_to_channel_pair[chan->channel][chan->differential][1]; + + ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val2); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Error running ad_sigma_delta single conversion: %d", ret); + goto out; + } + + if (abs(val1 - val2) > cfg->openwire_thrsh_raw) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, chan->address, + IIO_EV_TYPE_FAULT, IIO_EV_DIR_FAULT_OPENWIRE), + iio_get_time_ns(indio_dev)); + +out: + adchan->cfg.openwire_comp_chan = -1; + regmap_clear_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, + AD4111_GPIO_GP_OW_EN); + return ret; +} + static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) @@ -347,6 +570,15 @@ static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base, return 0; } +static int ad4111_mask_xlate(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask) +{ + *mask = AD4111_GPO01_DATA(offset); + *reg = base; + return 0; +} + static void ad7173_gpio_disable(void *data) { struct ad7173_state *st = data; @@ -379,7 +611,10 @@ static int ad7173_gpio_init(struct ad7173_state *st) gpio_regmap.regmap = st->reg_gpiocon_regmap; gpio_regmap.ngpio = st->info->num_gpios; gpio_regmap.reg_set_base = AD7173_REG_GPIO; - gpio_regmap.reg_mask_xlate = ad7173_mask_xlate; + if (st->info->higher_gpio_bits) + gpio_regmap.reg_mask_xlate = ad4111_mask_xlate; + else + gpio_regmap.reg_mask_xlate = ad7173_mask_xlate; st->gpio_regmap = devm_gpio_regmap_register(dev, &gpio_regmap); ret = PTR_ERR_OR_ZERO(st->gpio_regmap); @@ -388,12 +623,6 @@ static int ad7173_gpio_init(struct ad7173_state *st) return 0; } -#else -static int ad7173_gpio_init(struct ad7173_state *st) -{ - return 0; -} -#endif /* CONFIG_GPIOLIB */ static struct ad7173_state *ad_sigma_delta_to_ad7173(struct ad_sigma_delta *sd) { @@ -418,19 +647,51 @@ static void ad7173_reset_usage_cnts(struct ad7173_state *st) st->config_usage_counter = 0; } +/** + * ad7173_is_setup_equal - Compare two channel setups + * @cfg1: First channel configuration + * @cfg2: Second channel configuration + * + * Compares all configuration options that affect the registers connected to + * SETUP_SEL, namely CONFIGx, FILTERx, GAINx and OFFSETx. + * + * Returns: true if the setups are identical, false otherwise + */ +static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1, + const struct ad7173_channel_config *cfg2) +{ + /* + * This is just to make sure that the comparison is adapted after + * struct ad7173_channel_config was changed. + */ + static_assert(sizeof_field(struct ad7173_channel_config, config_props) == + sizeof(struct { + bool bipolar; + bool input_buf; + u16 sinc3_odr_div; + u8 sinc5_odr_index; + u8 ref_sel; + enum ad7173_filter_type filter_type; + })); + + return cfg1->bipolar == cfg2->bipolar && + cfg1->input_buf == cfg2->input_buf && + cfg1->sinc3_odr_div == cfg2->sinc3_odr_div && + cfg1->sinc5_odr_index == cfg2->sinc5_odr_index && + cfg1->ref_sel == cfg2->ref_sel && + cfg1->filter_type == cfg2->filter_type; +} + static struct ad7173_channel_config * ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg) { struct ad7173_channel_config *cfg_aux; - ptrdiff_t cmp_size; int i; - cmp_size = sizeof_field(struct ad7173_channel_config, config_props); for (i = 0; i < st->num_channels; i++) { cfg_aux = &st->channels[i].cfg; - if (cfg_aux->live && - !memcmp(&cfg->config_props, &cfg_aux->config_props, cmp_size)) + if (cfg_aux->live && ad7173_is_setup_equal(cfg, cfg_aux)) return cfg_aux; } return NULL; @@ -459,6 +720,7 @@ static int ad7173_load_config(struct ad7173_state *st, { unsigned int config; int free_cfg_slot, ret; + u8 post_filter_enable, post_filter_select; free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0, st->info->num_configs - 1, GFP_KERNEL); @@ -478,8 +740,49 @@ static int ad7173_load_config(struct ad7173_state *st, if (ret) return ret; + /* + * When SINC3_MAP flag is enabled, the rest of the register has a + * different meaning. We are using this option to allow the most + * possible sampling frequencies with SINC3 filter. + */ + if (cfg->filter_type == AD7173_FILTER_SINC3) + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 1) | + FIELD_PREP(AD7173_FILTER_SINC3_MAP_DIV, + cfg->sinc3_odr_div)); + + switch (cfg->filter_type) { + case AD7173_FILTER_SINC5_SINC1_PF1: + post_filter_enable = 1; + post_filter_select = 2; + break; + case AD7173_FILTER_SINC5_SINC1_PF2: + post_filter_enable = 1; + post_filter_select = 3; + break; + case AD7173_FILTER_SINC5_SINC1_PF3: + post_filter_enable = 1; + post_filter_select = 5; + break; + case AD7173_FILTER_SINC5_SINC1_PF4: + post_filter_enable = 1; + post_filter_select = 6; + break; + default: + post_filter_enable = 0; + post_filter_select = 0; + break; + } + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, - AD7173_FILTER_ODR0_MASK & cfg->odr); + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_select) | + FIELD_PREP(AD7173_FILTER_ORDER, 0) | + FIELD_PREP(AD7173_FILTER_ODR_MASK, + cfg->sinc5_odr_index)); } static int ad7173_config_channel(struct ad7173_state *st, int addr) @@ -523,6 +826,9 @@ static int ad7173_set_channel(struct ad_sigma_delta *sd, unsigned int channel) FIELD_PREP(AD7173_CH_SETUP_SEL_MASK, st->channels[channel].cfg.cfg_slot) | st->channels[channel].ain; + if (st->channels[channel].cfg.openwire_comp_chan >= 0) + channel = st->channels[channel].cfg.openwire_comp_chan; + return ad_sd_write_reg(&st->sd, AD7173_REG_CH(channel), 2, val); } @@ -569,16 +875,301 @@ static int ad7173_disable_all(struct ad_sigma_delta *sd) return 0; } -static struct ad_sigma_delta_info ad7173_sigma_delta_info = { +static int ad7173_disable_one(struct ad_sigma_delta *sd, unsigned int chan) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + + if (st->channels[chan].cfg.openwire_comp_chan >= 0) + chan = st->channels[chan].cfg.openwire_comp_chan; + + return ad_sd_write_reg(sd, AD7173_REG_CH(chan), 2, 0); +} + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_4_slots = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, + .set_mode = ad7173_set_mode, + .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 4, +}; + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_8_slots = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, + .set_mode = ad7173_set_mode, + .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 8, +}; + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_16_slots = { .set_channel = ad7173_set_channel, .append_status = ad7173_append_status, .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, .set_mode = ad7173_set_mode, .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 16, +}; + +static const struct ad7173_device_info ad4111_device_info = { + .name = "ad4111", + .id = AD4111_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .higher_gpio_bits = true, + .has_temp = true, + .has_vincom_input = true, + .has_input_buf = true, + .has_current_inputs = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .has_openwire_det = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4112_device_info = { + .name = "ad4112", + .id = AD4112_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .higher_gpio_bits = true, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_current_inputs = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4113_device_info = { + .name = "ad4113", + .id = AD4113_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .data_reg_only_16bit = true, + .higher_gpio_bits = true, + .has_vincom_input = true, + .has_input_buf = true, + .has_int_ref = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4114_device_info = { + .name = "ad4114", + .id = AD4114_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 16, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4115_device_info = { + .name = "ad4115", + .id = AD4115_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 16, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 8 * HZ_PER_MHZ, + .sinc5_data_rates = ad4115_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad4115_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4116_device_info = { + .name = "ad4116", + .id = AD4116_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 11, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 4 * HZ_PER_MHZ, + .sinc5_data_rates = ad4116_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad4116_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7172_2_device_info = { + .name = "ad7172-2", + .id = AD7172_2_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7172_4_device_info = { + .name = "ad7172-4", + .id = AD7172_4_ID, + .sd_info = &ad7173_sigma_delta_info_8_slots, + .num_voltage_in = 9, + .num_channels = 8, + .num_configs = 8, + .num_gpios = 4, + .has_input_buf = true, + .has_ref2 = true, + .has_pow_supply_monitoring = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7173_8_device_info = { + .name = "ad7173-8", + .id = AD7173_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_ref2 = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7175_2_device_info = { + .name = "ad7175-2", + .id = AD7175_2_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7175_8_device_info = { + .name = "ad7175-8", + .id = AD7175_8_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_ref2 = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7176_2_device_info = { + .name = "ad7176-2", + .id = AD7176_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_int_ref = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7177_2_device_info = { + .name = "ad7177-2", + .id = AD7177_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .odr_start_value = AD7177_ODR_START_VALUE, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), }; static int ad7173_setup(struct iio_dev *indio_dev) @@ -616,6 +1207,10 @@ static int ad7173_setup(struct iio_dev *indio_dev) if (!st->config_cnts) return -ENOMEM; + ret = ad7173_calibrate_all(st, indio_dev); + if (ret) + return ret; + /* All channels are enabled by default after a reset */ return ad7173_disable_all(&st->sd); } @@ -668,25 +1263,41 @@ static int ad7173_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; - /* disable channel after single conversion */ - ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(chan->address), 2, 0); - if (ret < 0) - return ret; + if (ch->openwire_det_en) { + ret = ad4111_openwire_event(indio_dev, chan); + if (ret < 0) + return ret; + } return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - if (chan->type == IIO_TEMP) { + + switch (chan->type) { + case IIO_TEMP: temp = AD7173_VOLTAGE_INT_REF_uV * MILLI; temp /= AD7173_TEMP_SENSIIVITY_uV_per_C; *val = temp; *val2 = chan->scan_type.realbits; - } else { + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_VOLTAGE: *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel); *val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar); + + if (chan->channel < st->info->num_voltage_in_div) + *val *= AD4111_DIVIDER_RATIO; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CURRENT: + *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel); + *val /= AD4111_SHUNT_RESISTOR_OHM; + *val2 = chan->scan_type.realbits - ch->cfg.bipolar; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; } - return IIO_VAL_FRACTIONAL_LOG2; case IIO_CHAN_INFO_OFFSET: - if (chan->type == IIO_TEMP) { + + switch (chan->type) { + case IIO_TEMP: /* 0 Kelvin -> raw sample */ temp = -ABSOLUTE_ZERO_MILLICELSIUS; temp *= AD7173_TEMP_SENSIIVITY_uV_per_C; @@ -695,12 +1306,23 @@ static int ad7173_read_raw(struct iio_dev *indio_dev, AD7173_VOLTAGE_INT_REF_uV * MILLI); *val = -temp; - } else { + return IIO_VAL_INT; + case IIO_VOLTAGE: + case IIO_CURRENT: *val = -BIT(chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; } - return IIO_VAL_INT; case IIO_CHAN_INFO_SAMP_FREQ: - reg = st->channels[chan->address].cfg.odr; + if (st->channels[chan->address].cfg.filter_type == AD7173_FILTER_SINC3) { + /* Inverse operation of ad7173_sinc3_odr_div_from_odr() */ + *val = MEGA; + *val2 = 3 * 32 * st->channels[chan->address].cfg.sinc3_odr_div; + return IIO_VAL_FRACTIONAL; + } + + reg = st->channels[chan->address].cfg.sinc5_odr_index; *val = st->info->sinc5_data_rates[reg] / MILLI; *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI); @@ -718,13 +1340,31 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, struct ad7173_state *st = iio_priv(indio_dev); struct ad7173_channel_config *cfg; unsigned int freq, i; - int ret; + int ret = 0; - ret = iio_device_claim_direct_mode(indio_dev); - if (ret) - return ret; + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; switch (info) { + /* + * This attribute sets the sampling frequency for each channel individually. + * There are no issues for raw or buffered reads of an individual channel. + * + * When multiple channels are enabled in buffered mode, the effective + * sampling rate of a channel is lowered in correlation to the number + * of channels enabled and the sampling rate of the other channels. + * + * Example: 3 channels enabled with rates CH1:6211sps CH2,CH3:10sps + * While the reading of CH1 takes only 0.16ms, the reading of CH2 and CH3 + * will take 100ms each. + * + * This will cause the reading of CH1 to be actually done once every + * 200.16ms, an effective rate of 4.99sps. + * + * Both the sinc5 and sinc3 rates are set here so that if the filter + * type is changed, the requested rate will still be set (aside from + * rounding differences). + */ case IIO_CHAN_INFO_SAMP_FREQ: freq = val * MILLI + val2 / MILLI; for (i = st->info->odr_start_value; i < st->info->num_sinc5_data_rates - 1; i++) @@ -732,7 +1372,8 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, break; cfg = &st->channels[chan->address].cfg; - cfg->odr = i; + cfg->sinc5_odr_index = i; + cfg->sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(freq); cfg->live = false; break; @@ -741,7 +1382,7 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, break; } - iio_device_release_direct_mode(indio_dev); + iio_device_release_direct(indio_dev); return ret; } @@ -749,17 +1390,88 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct ad7173_state *st = iio_priv(indio_dev); - int i, ret; + u16 sinc3_count = 0; + u16 sinc3_div = 0; + int i, j, k, ret; for (i = 0; i < indio_dev->num_channels; i++) { - if (test_bit(i, scan_mask)) + const struct ad7173_channel_config *cfg = &st->channels[i].cfg; + + if (test_bit(i, scan_mask)) { + if (cfg->filter_type == AD7173_FILTER_SINC3) { + sinc3_count++; + + if (sinc3_div == 0) { + sinc3_div = cfg->sinc3_odr_div; + } else if (sinc3_div != cfg->sinc3_odr_div) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have the same sampling_frequency for sinc3 filter_type\n"); + return -EINVAL; + } + } + ret = ad7173_set_channel(&st->sd, i); - else + } else { ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0); + } if (ret < 0) return ret; } + if (sinc3_count && sinc3_count < bitmap_weight(scan_mask, indio_dev->num_channels)) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have sinc3 filter_type\n"); + return -EINVAL; + } + + /* + * On some chips, there are more channels that setups, so if there were + * more unique setups requested than the number of available slots, + * ad7173_set_channel() will have written over some of the slots. We + * can detect this by making sure each assigned cfg_slot matches the + * requested configuration. If it doesn't, we know that the slot was + * overwritten by a different channel. + */ + for_each_set_bit(i, scan_mask, indio_dev->num_channels) { + const struct ad7173_channel_config *cfg1, *cfg2; + + cfg1 = &st->channels[i].cfg; + + for_each_set_bit(j, scan_mask, indio_dev->num_channels) { + cfg2 = &st->channels[j].cfg; + + /* + * Only compare configs that are assigned to the same + * SETUP_SEL slot and don't compare channel to itself. + */ + if (i == j || cfg1->cfg_slot != cfg2->cfg_slot) + continue; + + /* + * If we find two different configs trying to use the + * same SETUP_SEL slot, then we know that the that we + * have too many unique configurations requested for + * the available slots and at least one was overwritten. + */ + if (!ad7173_is_setup_equal(cfg1, cfg2)) { + /* + * At this point, there isn't a way to tell + * which setups are actually programmed in the + * ADC anymore, so we could read them back to + * see, but it is simpler to just turn off all + * of the live flags so that everything gets + * reprogramed on the next attempt read a sample. + */ + for (k = 0; k < st->num_channels; k++) + st->channels[k].cfg.live = false; + + dev_err(&st->sd.spi->dev, + "Too many unique channel configurations requested for scan\n"); + return -EINVAL; + } + } + } + return 0; } @@ -783,12 +1495,64 @@ static int ad7173_debug_reg_access(struct iio_dev *indio_dev, unsigned int reg, return ad_sd_write_reg(&st->sd, reg, reg_size, writeval); } +static int ad7173_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + + switch (type) { + case IIO_EV_TYPE_FAULT: + adchan->openwire_det_en = state; + return 0; + default: + return -EINVAL; + } +} + +static int ad7173_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + + switch (type) { + case IIO_EV_TYPE_FAULT: + return adchan->openwire_det_en; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec ad4111_events[] = { + { + .type = IIO_EV_TYPE_FAULT, + .dir = IIO_EV_DIR_FAULT_OPENWIRE, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_info ad7173_info = { .read_raw = &ad7173_read_raw, .write_raw = &ad7173_write_raw, .debugfs_reg_access = &ad7173_debug_reg_access, .validate_trigger = ad_sd_validate_trigger, .update_scan_mode = ad7173_update_scan_mode, + .write_event_config = ad7173_write_event_config, + .read_event_config = ad7173_read_event_config, +}; + +static const struct iio_scan_type ad4113_scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, }; static const struct iio_chan_spec ad7173_channel_template = { @@ -802,6 +1566,7 @@ static const struct iio_chan_spec ad7173_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, + .ext_info = ad7173_chan_spec_ext_info, }; static const struct iio_chan_spec ad7173_temp_iio_channel_template = { @@ -817,6 +1582,7 @@ static const struct iio_chan_spec ad7173_temp_iio_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, + .ext_info = ad7173_temp_chan_spec_ext_info, }; static void ad7173_disable_regulators(void *data) @@ -826,11 +1592,6 @@ static void ad7173_disable_regulators(void *data) regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators); } -static void ad7173_clk_disable_unprepare(void *clk) -{ - clk_disable_unprepare(clk); -} - static unsigned long ad7173_sel_clk(struct ad7173_state *st, unsigned int clk_sel) { @@ -904,14 +1665,141 @@ static int ad7173_register_clk_provider(struct iio_dev *indio_dev) &st->int_clk_hw); } +static int ad4111_validate_current_ain(struct ad7173_state *st, + const unsigned int ain[AD7173_NO_AINS_PER_CHANNEL]) +{ + struct device *dev = &st->sd.spi->dev; + + if (!st->info->has_current_inputs) + return dev_err_probe(dev, -EINVAL, + "Model %s does not support current channels\n", + st->info->name); + + if (ain[0] >= ARRAY_SIZE(ad4111_current_channel_config)) + return dev_err_probe(dev, -EINVAL, + "For current channels single-channel must be <[0-3]>\n"); + + return 0; +} + +static int ad7173_validate_voltage_ain_inputs(struct ad7173_state *st, + unsigned int ain0, unsigned int ain1) +{ + struct device *dev = &st->sd.spi->dev; + bool special_input0, special_input1; + + /* (AVDD1-AVSS)/5 power supply monitoring */ + if (ain0 == AD7173_AIN_POW_MON_POS && ain1 == AD7173_AIN_POW_MON_NEG && + st->info->has_pow_supply_monitoring) + return 0; + + special_input0 = AD7173_IS_REF_INPUT(ain0) || + (ain0 == AD4111_VINCOM_INPUT && st->info->has_vincom_input); + special_input1 = AD7173_IS_REF_INPUT(ain1) || + (ain1 == AD4111_VINCOM_INPUT && st->info->has_vincom_input); + + if ((ain0 >= st->info->num_voltage_in && !special_input0) || + (ain1 >= st->info->num_voltage_in && !special_input1)) { + if (ain0 == AD4111_VINCOM_INPUT || ain1 == AD4111_VINCOM_INPUT) + return dev_err_probe(dev, -EINVAL, + "VINCOM not supported for %s\n", st->info->name); + + return dev_err_probe(dev, -EINVAL, + "Input pin number out of range for pair (%d %d).\n", + ain0, ain1); + } + + if (AD4111_IS_VINCOM_MISMATCH(ain0, ain1) || + AD4111_IS_VINCOM_MISMATCH(ain1, ain0)) + return dev_err_probe(dev, -EINVAL, + "VINCOM must be paired with inputs having divider.\n"); + + if (!special_input0 && !special_input1 && + ((ain0 >= st->info->num_voltage_in_div) != + (ain1 >= st->info->num_voltage_in_div))) + return dev_err_probe(dev, -EINVAL, + "Both inputs must either have a voltage divider or not have: (%d %d).\n", + ain0, ain1); + + return 0; +} + +static int ad7173_validate_reference(struct ad7173_state *st, int ref_sel) +{ + struct device *dev = &st->sd.spi->dev; + int ret; + + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF && !st->info->has_int_ref) + return dev_err_probe(dev, -EINVAL, + "Internal reference is not available on current model.\n"); + + if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) + return dev_err_probe(dev, -EINVAL, + "External reference 2 is not available on current model.\n"); + + ret = ad7173_get_ref_voltage_milli(st, ref_sel); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot use reference %u\n", + ref_sel); + + return 0; +} + +static int ad7173_validate_openwire_ain_inputs(struct ad7173_state *st, + bool differential, + unsigned int ain0, + unsigned int ain1) +{ + /* + * If the channel is configured as differential, + * the ad4111 requires specific ains to be used together + */ + if (differential) + return (ain0 % 2) ? (ain0 - 1) == ain1 : (ain0 + 1) == ain1; + + return ain1 == AD4111_VINCOM_INPUT; +} + +static unsigned int ad7173_calc_openwire_thrsh_raw(struct ad7173_state *st, + struct iio_chan_spec *chan, + struct ad7173_channel *chan_st_priv, + unsigned int thrsh_mv) { + unsigned int thrsh_raw; + + thrsh_raw = + BIT(chan->scan_type.realbits - !!(chan_st_priv->cfg.bipolar)) + * thrsh_mv + / ad7173_get_ref_voltage_milli(st, chan_st_priv->cfg.ref_sel); + if (chan->channel < st->info->num_voltage_in_div) + thrsh_raw /= AD4111_DIVIDER_RATIO; + + return thrsh_raw; +} + static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) { struct ad7173_channel *chans_st_arr, *chan_st_priv; struct ad7173_state *st = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; struct iio_chan_spec *chan_arr, *chan; - unsigned int ain[2], chan_index = 0; - int ref_sel, ret; + unsigned int ain[AD7173_NO_AINS_PER_CHANNEL], chan_index = 0; + int ref_sel, ret, num_channels; + + num_channels = device_get_child_node_count(dev); + + if (st->info->has_temp) + num_channels++; + + if (num_channels == 0) + return dev_err_probe(dev, -ENODATA, "No channels specified\n"); + + if (num_channels > st->info->num_channels) + return dev_err_probe(dev, -EINVAL, + "Too many channels specified. Maximum is %d, not including temperature channel if supported.\n", + st->info->num_channels); + + indio_dev->num_channels = num_channels; + st->num_channels = num_channels; chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), st->num_channels, GFP_KERNEL); @@ -935,24 +1823,60 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan_st_priv->cfg.bipolar = false; chan_st_priv->cfg.input_buf = st->info->has_input_buf; chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; + chan_st_priv->cfg.openwire_comp_chan = -1; st->adc_mode |= AD7173_ADC_MODE_REF_EN; + if (st->info->data_reg_only_16bit) + chan_arr[chan_index].scan_type = ad4113_scan_type; + + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } chan_index++; } device_for_each_child_node_scoped(dev, child) { + bool is_current_chan = false; + chan = &chan_arr[chan_index]; + *chan = ad7173_channel_template; chan_st_priv = &chans_st_arr[chan_index]; ret = fwnode_property_read_u32_array(child, "diff-channels", ain, ARRAY_SIZE(ain)); - if (ret) - return ret; + if (ret) { + ret = fwnode_property_read_u32(child, "single-channel", + ain); + if (ret) + return dev_err_probe(dev, ret, + "Channel must define one of diff-channels or single-channel.\n"); - if (ain[0] >= st->info->num_inputs || - ain[1] >= st->info->num_inputs) - return dev_err_probe(dev, -EINVAL, - "Input pin number out of range for pair (%d %d).\n", - ain[0], ain[1]); + is_current_chan = fwnode_property_read_bool(child, "adi,current-channel"); + } else { + chan->differential = true; + } + + if (is_current_chan) { + ret = ad4111_validate_current_ain(st, ain); + if (ret) + return ret; + } else { + if (!chan->differential) { + ret = fwnode_property_read_u32(child, + "common-mode-channel", ain + 1); + if (ret) + return dev_err_probe(dev, ret, + "common-mode-channel must be defined for single-ended channels.\n"); + } + ret = ad7173_validate_voltage_ain_inputs(st, ain[0], ain[1]); + if (ret) + return ret; + } ret = fwnode_property_match_property_string(child, "adi,reference-select", @@ -963,40 +1887,57 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) else ref_sel = ret; - if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF && - !st->info->has_int_ref) - return dev_err_probe(dev, -EINVAL, - "Internal reference is not available on current model.\n"); - - if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) - return dev_err_probe(dev, -EINVAL, - "External reference 2 is not available on current model.\n"); - - ret = ad7173_get_ref_voltage_milli(st, ref_sel); - if (ret < 0) - return dev_err_probe(dev, ret, - "Cannot use reference %u\n", ref_sel); + ret = ad7173_validate_reference(st, ref_sel); + if (ret) + return ret; if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF) st->adc_mode |= AD7173_ADC_MODE_REF_EN; chan_st_priv->cfg.ref_sel = ref_sel; - *chan = ad7173_channel_template; chan->address = chan_index; chan->scan_index = chan_index; chan->channel = ain[0]; - chan->channel2 = ain[1]; - chan->differential = true; - - chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]); - chan_st_priv->chan_reg = chan_index; chan_st_priv->cfg.input_buf = st->info->has_input_buf; - chan_st_priv->cfg.odr = 0; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; + chan_st_priv->cfg.openwire_comp_chan = -1; chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar"); if (chan_st_priv->cfg.bipolar) chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET); + if (is_current_chan) { + chan->type = IIO_CURRENT; + chan->differential = false; + chan->channel2 = 0; + chan_st_priv->ain = ad4111_current_channel_config[ain[0]]; + } else { + chan_st_priv->cfg.input_buf = st->info->has_input_buf; + chan->channel2 = ain[1]; + chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]); + if (st->info->has_openwire_det && + ad7173_validate_openwire_ain_inputs(st, chan->differential, ain[0], ain[1])) { + chan->event_spec = ad4111_events; + chan->num_event_specs = ARRAY_SIZE(ad4111_events); + chan_st_priv->cfg.openwire_thrsh_raw = + ad7173_calc_openwire_thrsh_raw(st, chan, chan_st_priv, + AD4111_OW_DET_THRSH_MV); + } + } + + if (st->info->data_reg_only_16bit) + chan_arr[chan_index].scan_type = ad4113_scan_type; + + /* Assuming SPI offload is ad411x_ad717x HDL project. */ + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + chan_index++; } return 0; @@ -1006,7 +1947,6 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) { struct ad7173_state *st = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; - unsigned int num_channels; int ret; st->regulators[0].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF]; @@ -1030,8 +1970,7 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add regulators disable action\n"); + return ret; ret = device_property_match_property_string(dev, "clock-names", ad7173_clk_sel, @@ -1041,40 +1980,16 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) AD7173_ADC_MODE_CLOCKSEL_INT); ad7173_register_clk_provider(indio_dev); } else { + struct clk *clk; + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, AD7173_ADC_MODE_CLOCKSEL_EXT + ret); - st->ext_clk = devm_clk_get(dev, ad7173_clk_sel[ret]); - if (IS_ERR(st->ext_clk)) - return dev_err_probe(dev, PTR_ERR(st->ext_clk), + clk = devm_clk_get_enabled(dev, ad7173_clk_sel[ret]); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get external clock\n"); - - ret = clk_prepare_enable(st->ext_clk); - if (ret) - return dev_err_probe(dev, ret, - "Failed to enable external clock\n"); - - ret = devm_add_action_or_reset(dev, ad7173_clk_disable_unprepare, - st->ext_clk); - if (ret) - return ret; } - ret = fwnode_irq_get_byname(dev_fwnode(dev), "rdy"); - if (ret < 0) - return dev_err_probe(dev, ret, "Interrupt 'rdy' is required\n"); - - ad7173_sigma_delta_info.irq_line = ret; - - num_channels = device_get_child_node_count(dev); - - if (st->info->has_temp) - num_channels++; - - if (num_channels == 0) - return dev_err_probe(dev, -ENODATA, "No channels specified\n"); - indio_dev->num_channels = num_channels; - st->num_channels = num_channels; - return ad7173_fw_parse_channel_config(indio_dev); } @@ -1104,10 +2019,11 @@ static int ad7173_probe(struct spi_device *spi) indio_dev->info = &ad7173_info; spi->mode = SPI_MODE_3; - spi_setup(spi); + ret = spi_setup(spi); + if (ret) + return ret; - ad7173_sigma_delta_info.num_slots = st->info->num_configs; - ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7173_sigma_delta_info); + ret = ad_sd_init(&st->sd, indio_dev, spi, st->info->sd_info); if (ret) return ret; @@ -1127,39 +2043,41 @@ static int ad7173_probe(struct spi_device *spi) if (ret) return ret; - if (IS_ENABLED(CONFIG_GPIOLIB)) - return ad7173_gpio_init(st); - - return 0; + return ad7173_gpio_init(st); } static const struct of_device_id ad7173_of_match[] = { - { .compatible = "adi,ad7172-2", - .data = &ad7173_device_info[ID_AD7172_2]}, - { .compatible = "adi,ad7172-4", - .data = &ad7173_device_info[ID_AD7172_4]}, - { .compatible = "adi,ad7173-8", - .data = &ad7173_device_info[ID_AD7173_8]}, - { .compatible = "adi,ad7175-2", - .data = &ad7173_device_info[ID_AD7175_2]}, - { .compatible = "adi,ad7175-8", - .data = &ad7173_device_info[ID_AD7175_8]}, - { .compatible = "adi,ad7176-2", - .data = &ad7173_device_info[ID_AD7176_2]}, - { .compatible = "adi,ad7177-2", - .data = &ad7173_device_info[ID_AD7177_2]}, + { .compatible = "adi,ad4111", .data = &ad4111_device_info }, + { .compatible = "adi,ad4112", .data = &ad4112_device_info }, + { .compatible = "adi,ad4113", .data = &ad4113_device_info }, + { .compatible = "adi,ad4114", .data = &ad4114_device_info }, + { .compatible = "adi,ad4115", .data = &ad4115_device_info }, + { .compatible = "adi,ad4116", .data = &ad4116_device_info }, + { .compatible = "adi,ad7172-2", .data = &ad7172_2_device_info }, + { .compatible = "adi,ad7172-4", .data = &ad7172_4_device_info }, + { .compatible = "adi,ad7173-8", .data = &ad7173_8_device_info }, + { .compatible = "adi,ad7175-2", .data = &ad7175_2_device_info }, + { .compatible = "adi,ad7175-8", .data = &ad7175_8_device_info }, + { .compatible = "adi,ad7176-2", .data = &ad7176_2_device_info }, + { .compatible = "adi,ad7177-2", .data = &ad7177_2_device_info }, { } }; MODULE_DEVICE_TABLE(of, ad7173_of_match); static const struct spi_device_id ad7173_id_table[] = { - { "ad7172-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_2]}, - { "ad7172-4", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_4]}, - { "ad7173-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7173_8]}, - { "ad7175-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_2]}, - { "ad7175-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_8]}, - { "ad7176-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7176_2]}, - { "ad7177-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7177_2]}, + { "ad4111", (kernel_ulong_t)&ad4111_device_info }, + { "ad4112", (kernel_ulong_t)&ad4112_device_info }, + { "ad4113", (kernel_ulong_t)&ad4113_device_info }, + { "ad4114", (kernel_ulong_t)&ad4114_device_info }, + { "ad4115", (kernel_ulong_t)&ad4115_device_info }, + { "ad4116", (kernel_ulong_t)&ad4116_device_info }, + { "ad7172-2", (kernel_ulong_t)&ad7172_2_device_info }, + { "ad7172-4", (kernel_ulong_t)&ad7172_4_device_info }, + { "ad7173-8", (kernel_ulong_t)&ad7173_8_device_info }, + { "ad7175-2", (kernel_ulong_t)&ad7175_2_device_info }, + { "ad7175-8", (kernel_ulong_t)&ad7175_8_device_info }, + { "ad7176-2", (kernel_ulong_t)&ad7176_2_device_info }, + { "ad7177-2", (kernel_ulong_t)&ad7177_2_device_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7173_id_table); @@ -1174,8 +2092,8 @@ static struct spi_driver ad7173_driver = { }; module_spi_driver(ad7173_driver); -MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafo.de>"); MODULE_AUTHOR("Dumitru Ceclan <dumitru.ceclan@analog.com>"); -MODULE_DESCRIPTION("Analog Devices AD7172/AD7173/AD7175/AD7176 ADC driver"); +MODULE_DESCRIPTION("Analog Devices AD7173 and similar ADC driver"); MODULE_LICENSE("GPL"); |
