* [PATCH v5 0/3] Add support for AD7191
@ 2025-02-26 11:53 Alisa-Dariana Roman
2025-02-26 11:53 ` [PATCH v5 1/3] dt-bindings: iio: adc: add AD7191 Alisa-Dariana Roman
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Alisa-Dariana Roman @ 2025-02-26 11:53 UTC (permalink / raw)
To: Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron,
Ramona Gradinariu, David Lechner, linux-iio, devicetree,
linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet
Thank you all for your feedback! Here is the updated series of patches!
Kind regards,
Alisa-Dariana Roman.
---
v4: https://lore.kernel.org/all/20250203133254.313106-1-alisa.roman@analog.com/
v4 -> v5:
- use static arrays in the ad7191_config_setup function, instead of keeping
them in the state structure
- added error checking for devicetree parsing of pga-value and odr-value
- for now, it doesn't return error when the index corresponding to pga-value
or odr-value doesn't match, since index is initialized to 0, so it will use the
first value in this case (the bindings constrain the possbile values for these
2 properties, so I thought it's ok like this)
- use gpiod_multi_set_value_cansleep()
- move sampling frequency attribute to mask separate (the avail unmodified)
- removed unused argument form ad7191_setup()
- removed 2 redundant sections from docs, and renamed one to Devicetree
- add ad7191.rst to MAINTAINERS
v3: https://lore.kernel.org/all/20250129143054.225322-1-alisa.roman@analog.com/
v3 -> v4:
- addressed all replies for v3
- refactored the scale and sampling frequencies configurations to use 2
different arrays for gpio case vs pinstrap case
v2: https://lore.kernel.org/all/20250122132821.126600-1-alisa.roman@analog.com/
v2 -> v3:
- correct binding title
- remove clksel_state and clksel_gpio, assume the clksel pin is always
pinstrapped
- rephrase clocks description accordingly
- simplify binding constraints
- specify in binding description that PDOWN must be connected to SPI's
controller's CS
- add minItems for gpios in bindings
- make scope explicit for mutex guard
- remove spi irq check
- add id_table to spi_driver struct
- changed comments as suggested
- use spi_message_init_with_transfers()
- default returns an error in ad7191_set_mode()
- replace hard-coded 2 with st->pga_gpios->ndescs
- use gpiod_set_array_value_cansleep()
- change .storagebits to 32
- check return value for ad_sd_init()
- change to adi,odr-value and adi,pga-value, which now accepts the value as
suggested
- modify variables names and refactor the setup of odr and pga gpios,
indexes and available arrays into ad7191_config_setup(), since they are all
related
- add ad7191.rst
v1: https://lore.kernel.org/all/20241221155926.81954-1-alisa.roman@analog.com/
v1 -> v2:
- removed patch adding function in ad_sigma_delta.h/.c
- added a function set_cs() for asserting/deasserting the cs
- handle pinstrapping cases
- refactored all clock handling
- updated bindings: corrected and added new things
- -> address of the channels is used in set_channel()
- addressed all the other changes
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v5 1/3] dt-bindings: iio: adc: add AD7191 2025-02-26 11:53 [PATCH v5 0/3] Add support for AD7191 Alisa-Dariana Roman @ 2025-02-26 11:53 ` Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 2/3] iio: adc: ad7191: " Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 3/3] docs: iio: " Alisa-Dariana Roman 2 siblings, 0 replies; 7+ messages in thread From: Alisa-Dariana Roman @ 2025-02-26 11:53 UTC (permalink / raw) To: Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, David Lechner, linux-iio, devicetree, linux-kernel, linux-doc Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet AD7191 is a pin-programmable, ultra-low noise 24-bit sigma-delta ADC designed for precision bridge sensor measurements. It features two differential analog input channels, selectable output rates, programmable gain, internal temperature sensor and simultaneous 50Hz/60Hz rejection. Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> Reviewed-by: Rob Herring (Arm) <robh@kernel.org> --- .../bindings/iio/adc/adi,ad7191.yaml | 149 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 156 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml new file mode 100644 index 000000000000..801ed319ee82 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad7191.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD7191 ADC + +maintainers: + - Alisa-Dariana Roman <alisa.roman@analog.com> + +description: | + Bindings for the Analog Devices AD7191 ADC device. Datasheet can be + found here: + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7191.pdf + The device's PDOWN pin must be connected to the SPI controller's chip select + pin. + +properties: + compatible: + enum: + - adi,ad7191 + + reg: + maxItems: 1 + + spi-cpol: true + + spi-cpha: true + + clocks: + maxItems: 1 + description: + Must be present when CLKSEL pin is tied HIGH to select external clock + source (either a crystal between MCLK1 and MCLK2 pins, or a + CMOS-compatible clock driving MCLK2 pin). Must be absent when CLKSEL pin + is tied LOW to use the internal 4.92MHz clock. + + interrupts: + maxItems: 1 + + avdd-supply: + description: AVdd voltage supply + + dvdd-supply: + description: DVdd voltage supply + + vref-supply: + description: Vref voltage supply + + odr-gpios: + description: + ODR1 and ODR2 pins for output data rate selection. Should be defined if + adi,odr-value is absent. + minItems: 2 + maxItems: 2 + + adi,odr-value: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Should be present if ODR pins are pin-strapped. Possible values: + 120 Hz (ODR1=0, ODR2=0) + 60 Hz (ODR1=0, ODR2=1) + 50 Hz (ODR1=1, ODR2=0) + 10 Hz (ODR1=1, ODR2=1) + If defined, odr-gpios must be absent. + enum: [120, 60, 50, 10] + + pga-gpios: + description: + PGA1 and PGA2 pins for gain selection. Should be defined if adi,pga-value + is absent. + minItems: 2 + maxItems: 2 + + adi,pga-value: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Should be present if PGA pins are pin-strapped. Possible values: + Gain 1 (PGA1=0, PGA2=0) + Gain 8 (PGA1=0, PGA2=1) + Gain 64 (PGA1=1, PGA2=0) + Gain 128 (PGA1=1, PGA2=1) + If defined, pga-gpios must be absent. + enum: [1, 8, 64, 128] + + temp-gpios: + description: TEMP pin for temperature sensor enable. + maxItems: 1 + + chan-gpios: + description: CHAN pin for input channel selection. + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - avdd-supply + - dvdd-supply + - vref-supply + - spi-cpol + - spi-cpha + - temp-gpios + - chan-gpios + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + - oneOf: + - required: + - adi,odr-value + - required: + - odr-gpios + - oneOf: + - required: + - adi,pga-value + - required: + - pga-gpios + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad7191"; + reg = <0>; + spi-max-frequency = <1000000>; + spi-cpol; + spi-cpha; + clocks = <&ad7191_mclk>; + interrupts = <25 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpio>; + avdd-supply = <&avdd>; + dvdd-supply = <&dvdd>; + vref-supply = <&vref>; + adi,pga-value = <1>; + odr-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>, <&gpio 24 GPIO_ACTIVE_HIGH>; + temp-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>; + chan-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index bd04375ab4a2..ac1f61256932 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1346,6 +1346,13 @@ W: http://ez.analog.com/community/linux-device-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7091r* F: drivers/iio/adc/ad7091r* +ANALOG DEVICES INC AD7191 DRIVER +M: Alisa-Dariana Roman <alisa.roman@analog.com> +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml + ANALOG DEVICES INC AD7192 DRIVER M: Alisa-Dariana Roman <alisa.roman@analog.com> L: linux-iio@vger.kernel.org -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 2/3] iio: adc: ad7191: add AD7191 2025-02-26 11:53 [PATCH v5 0/3] Add support for AD7191 Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 1/3] dt-bindings: iio: adc: add AD7191 Alisa-Dariana Roman @ 2025-02-26 11:53 ` Alisa-Dariana Roman 2025-02-26 21:05 ` David Lechner 2025-02-27 23:34 ` kernel test robot 2025-02-26 11:53 ` [PATCH v5 3/3] docs: iio: " Alisa-Dariana Roman 2 siblings, 2 replies; 7+ messages in thread From: Alisa-Dariana Roman @ 2025-02-26 11:53 UTC (permalink / raw) To: Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, David Lechner, linux-iio, devicetree, linux-kernel, linux-doc Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet AD7191 is a pin-programmable, ultra-low noise 24-bit sigma-delta ADC designed for precision bridge sensor measurements. It features two differential analog input channels, selectable output rates, programmable gain, internal temperature sensor and simultaneous 50Hz/60Hz rejection. Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad7191.c | 553 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 565 insertions(+) create mode 100644 drivers/iio/adc/ad7191.c diff --git a/MAINTAINERS b/MAINTAINERS index ac1f61256932..87c491975ced 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1352,6 +1352,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml +F: drivers/iio/adc/ad7191.c ANALOG DEVICES INC AD7192 DRIVER M: Alisa-Dariana Roman <alisa.roman@analog.com> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 27413516216c..b7ae6e0ae0df 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -142,6 +142,16 @@ config AD7173 To compile this driver as a module, choose M here: the module will be called ad7173. +config AD7191 + tristate "Analog Devices AD7191 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7191. + + To compile this driver as a module, choose M here: the + module will be called ad7191. + config AD7192 tristate "Analog Devices AD7192 and similar ADC driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 9f26d5eca822..3e918c3eec69 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_AD7091R5) += ad7091r5.o obj-$(CONFIG_AD7091R8) += ad7091r8.o obj-$(CONFIG_AD7124) += ad7124.o obj-$(CONFIG_AD7173) += ad7173.o +obj-$(CONFIG_AD7191) += ad7191.o obj-$(CONFIG_AD7192) += ad7192.o obj-$(CONFIG_AD7266) += ad7266.o obj-$(CONFIG_AD7280) += ad7280a.o diff --git a/drivers/iio/adc/ad7191.c b/drivers/iio/adc/ad7191.c new file mode 100644 index 000000000000..f1eff7d095df --- /dev/null +++ b/drivers/iio/adc/ad7191.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7191 ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/adc/ad_sigma_delta.h> +#include <linux/iio/iio.h> + +#define ad_sigma_delta_to_ad7191(sigmad) \ + container_of((sigmad), struct ad7191_state, sd) + +#define AD7191_TEMP_CODES_PER_DEGREE 2815 + +#define AD7191_EXT_CLK_ENABLE 0 +#define AD7191_INT_CLK_ENABLE 1 + +#define AD7191_CHAN_MASK BIT(0) +#define AD7191_TEMP_MASK BIT(1) + +enum ad7191_channel { + AD7191_CH_AIN1_AIN2, + AD7191_CH_AIN3_AIN4, + AD7191_CH_TEMP, +}; + +/* + * NOTE: + * The AD7191 features a dual-use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use SPI bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt-capable GPIO. + * + * The SPI controller's chip select must be connected to the PDOWN pin + * of the ADC. When CS (PDOWN) is high, it powers down the device and + * resets the internal circuitry. + */ + +struct ad7191_state { + struct ad_sigma_delta sd; + struct mutex lock; /* Protect device state */ + + struct gpio_descs *odr_gpios; + struct gpio_descs *pga_gpios; + struct gpio_desc *temp_gpio; + struct gpio_desc *chan_gpio; + + u16 int_vref_mv; + const u32 (*scale_avail)[2]; + size_t scale_avail_size; + u32 scale_index; + const u32 *samp_freq_avail; + size_t samp_freq_avail_size; + u32 samp_freq_index; + + struct clk *mclk; +}; + +static int ad7191_set_channel(struct ad_sigma_delta *sd, unsigned int address) +{ + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); + u8 temp_gpio_val, chan_gpio_val; + + if (!FIELD_FIT(AD7191_CHAN_MASK | AD7191_TEMP_MASK, address)) + return -EINVAL; + + chan_gpio_val = FIELD_GET(AD7191_CHAN_MASK, address); + temp_gpio_val = FIELD_GET(AD7191_TEMP_MASK, address); + + gpiod_set_value(st->chan_gpio, chan_gpio_val); + gpiod_set_value(st->temp_gpio, temp_gpio_val); + + return 0; +} + +static int ad7191_set_cs(struct ad_sigma_delta *sigma_delta, int assert) +{ + struct spi_transfer t = { + .len = 0, + .cs_change = assert, + }; + struct spi_message m; + + spi_message_init_with_transfers(&m, &t, 1); + + return spi_sync_locked(sigma_delta->spi, &m); +} + +static int ad7191_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); + + switch (mode) { + case AD_SD_MODE_CONTINUOUS: + case AD_SD_MODE_SINGLE: + return ad7191_set_cs(&st->sd, 1); + case AD_SD_MODE_IDLE: + return ad7191_set_cs(&st->sd, 0); + default: + return -EINVAL; + } +} + +static const struct ad_sigma_delta_info ad7191_sigma_delta_info = { + .set_channel = ad7191_set_channel, + .set_mode = ad7191_set_mode, + .has_registers = false, +}; + +static int ad7191_init_regulators(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable specified AVdd supply\n"); + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable specified DVdd supply\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get Vref voltage\n"); + + st->int_vref_mv = ret / 1000; + + return 0; +} + +static int ad7191_config_setup(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + /* Sampling frequencies in Hz, see Table 5 */ + static const u32 samp_freq[4] = { 120, 60, 50, 10 }; + /* Gain options, see Table 7 */ + const u32 gain[4] = { 1, 8, 64, 128 }; + static u32 scale_buffer[4][2]; + int odr_value, odr_index, pga_value, pga_index, i, ret; + u64 scale_uv; + + st->samp_freq_index = 0; + st->scale_index = 0; + + ret = device_property_read_u32(dev, "adi,odr-value", &odr_value); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, "Failed to get odr value.\n"); + + if (ret == -EINVAL) { + st->odr_gpios = devm_gpiod_get_array(dev, "odr", GPIOD_OUT_LOW); + if (IS_ERR(st->odr_gpios)) + return dev_err_probe(dev, PTR_ERR(st->odr_gpios), + "Failed to get odr gpios.\n"); + + st->samp_freq_avail = samp_freq; + st->samp_freq_avail_size = ARRAY_SIZE(samp_freq); + } else { + for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { + if (odr_value != samp_freq[i]) + continue; + odr_index = i; + break; + } + + st->samp_freq_avail = &samp_freq[odr_index]; + st->samp_freq_avail_size = 1; + + st->odr_gpios = NULL; + } + + mutex_lock(&st->lock); + + for (i = 0; i < ARRAY_SIZE(scale_buffer); i++) { + scale_uv = ((u64)st->int_vref_mv * NANO) >> + (indio_dev->channels[0].scan_type.realbits - 1); + do_div(scale_uv, gain[i]); + scale_buffer[i][1] = do_div(scale_uv, NANO); + scale_buffer[i][0] = scale_uv; + } + + mutex_unlock(&st->lock); + + ret = device_property_read_u32(dev, "adi,pga-value", &pga_value); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, "Failed to get pga value.\n"); + + if (ret == -EINVAL) { + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get pga gpios.\n"); + + st->scale_avail = scale_buffer; + st->scale_avail_size = ARRAY_SIZE(scale_buffer); + } else { + for (i = 0; i < ARRAY_SIZE(gain); i++) { + if (pga_value != gain[i]) + continue; + pga_index = i; + break; + } + + st->scale_avail = &scale_buffer[pga_index]; + st->scale_avail_size = 1; + + st->pga_gpios = NULL; + } + + st->temp_gpio = devm_gpiod_get(dev, "temp", GPIOD_OUT_LOW); + if (IS_ERR(st->temp_gpio)) + return dev_err_probe(dev, PTR_ERR(st->temp_gpio), + "Failed to get temp gpio.\n"); + + st->chan_gpio = devm_gpiod_get(dev, "chan", GPIOD_OUT_LOW); + if (IS_ERR(st->chan_gpio)) + return dev_err_probe(dev, PTR_ERR(st->chan_gpio), + "Failed to get chan gpio.\n"); + + return 0; +} + +static int ad7191_clock_setup(struct ad7191_state *st) +{ + struct device *dev = &st->sd.spi->dev; + + st->mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get mclk.\n"); + + return 0; +} + +static int ad7191_setup(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7191_init_regulators(indio_dev); + if (ret) + return ret; + + ret = ad7191_config_setup(indio_dev); + if (ret) + return ret; + + return ad7191_clock_setup(st); +} + +static int ad7191_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct ad7191_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: { + guard(mutex)(&st->lock); + *val = st->scale_avail[st->scale_index][0]; + *val2 = st->scale_avail[st->scale_index][1]; + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_TEMP: + *val = 0; + *val2 = NANO / AD7191_TEMP_CODES_PER_DEGREE; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << (chan->scan_type.realbits - 1)); + switch (chan->type) { + case IIO_VOLTAGE: + return IIO_VAL_INT; + case IIO_TEMP: + *val -= 273 * AD7191_TEMP_CODES_PER_DEGREE; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->samp_freq_avail[st->samp_freq_index]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7191_set_gain(struct ad7191_state *st, int gain_index) +{ + unsigned long value = gain_index; + + st->scale_index = gain_index; + + return gpiod_multi_set_value_cansleep(st->pga_gpios, &value); +} + +static int ad7191_set_samp_freq(struct ad7191_state *st, int samp_freq_index) +{ + unsigned long value = samp_freq_index; + + st->samp_freq_index = samp_freq_index; + + return gpiod_multi_set_value_cansleep(st->odr_gpios, &value); +} + +static int __ad7191_write_raw(struct ad7191_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (!st->pga_gpios) + return -EPERM; + guard(mutex)(&st->lock); + for (i = 0; i < st->scale_avail_size; i++) { + if (val2 != st->scale_avail[i][1]) + continue; + return ad7191_set_gain(st, i); + } + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + if (!st->odr_gpios) + return -EPERM; + guard(mutex)(&st->lock); + for (i = 0; i < st->samp_freq_avail_size; i++) { + if (val != st->samp_freq_avail[i]) + continue; + return ad7191_set_samp_freq(st, i); + } + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad7191_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ad7191_state *st = iio_priv(indio_dev); + int ret; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = __ad7191_write_raw(st, chan, val, val2, mask); + + iio_device_release_direct_mode(indio_dev); + + return ret; +} + +static int ad7191_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7191_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct ad7191_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_avail; + *type = IIO_VAL_INT_PLUS_NANO; + *length = st->scale_avail_size * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)st->samp_freq_avail; + *type = IIO_VAL_INT; + *length = st->samp_freq_avail_size; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static const struct iio_info ad7191_info = { + .read_raw = ad7191_read_raw, + .write_raw = ad7191_write_raw, + .write_raw_get_fmt = ad7191_write_raw_get_fmt, + .read_avail = ad7191_read_avail, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_chan_spec ad7191_channels[] = { + { + .type = IIO_TEMP, + .address = AD7191_CH_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 2, + .address = AD7191_CH_AIN1_AIN2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .address = AD7191_CH_AIN3_AIN4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int ad7191_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7191_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); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->name = "ad7191"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7191_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7191_channels); + indio_dev->info = &ad7191_info; + + ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7191_sigma_delta_info); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7191_setup(indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7191_of_match[] = { + { + .compatible = "adi,ad7191", + }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7191_of_match); + +static const struct spi_device_id ad7191_id_table[] = { + { "ad7191" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7191_id_table); + +static struct spi_driver ad7191_driver = { + .driver = { + .name = "ad7191", + .of_match_table = ad7191_of_match, + }, + .probe = ad7191_probe, + .id_table = ad7191_id_table, +}; +module_spi_driver(ad7191_driver); + +MODULE_AUTHOR("Alisa-Dariana Roman <alisa.roman@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD7191 ADC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA); -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/3] iio: adc: ad7191: add AD7191 2025-02-26 11:53 ` [PATCH v5 2/3] iio: adc: ad7191: " Alisa-Dariana Roman @ 2025-02-26 21:05 ` David Lechner 2025-02-27 23:34 ` kernel test robot 1 sibling, 0 replies; 7+ messages in thread From: David Lechner @ 2025-02-26 21:05 UTC (permalink / raw) To: Alisa-Dariana Roman, Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, linux-iio, devicetree, linux-kernel, linux-doc Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet On 2/26/25 5:53 AM, Alisa-Dariana Roman wrote: > AD7191 is a pin-programmable, ultra-low noise 24-bit sigma-delta ADC > designed for precision bridge sensor measurements. It features two > differential analog input channels, selectable output rates, > programmable gain, internal temperature sensor and simultaneous > 50Hz/60Hz rejection. > > Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> > --- > MAINTAINERS | 1 + > drivers/iio/adc/Kconfig | 10 + > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/ad7191.c | 553 +++++++++++++++++++++++++++++++++++++++ > 4 files changed, 565 insertions(+) > create mode 100644 drivers/iio/adc/ad7191.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index ac1f61256932..87c491975ced 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -1352,6 +1352,7 @@ L: linux-iio@vger.kernel.org > S: Supported > W: https://ez.analog.com/linux-software-drivers > F: Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml > +F: drivers/iio/adc/ad7191.c > > ANALOG DEVICES INC AD7192 DRIVER > M: Alisa-Dariana Roman <alisa.roman@analog.com> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 27413516216c..b7ae6e0ae0df 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -142,6 +142,16 @@ config AD7173 > To compile this driver as a module, choose M here: the module will be > called ad7173. > > +config AD7191 > + tristate "Analog Devices AD7191 ADC driver" > + depends on SPI > + select AD_SIGMA_DELTA > + help > + Say yes here to build support for Analog Devices AD7191. > + > + To compile this driver as a module, choose M here: the > + module will be called ad7191. > + > config AD7192 > tristate "Analog Devices AD7192 and similar ADC driver" > depends on SPI > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index 9f26d5eca822..3e918c3eec69 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -16,6 +16,7 @@ obj-$(CONFIG_AD7091R5) += ad7091r5.o > obj-$(CONFIG_AD7091R8) += ad7091r8.o > obj-$(CONFIG_AD7124) += ad7124.o > obj-$(CONFIG_AD7173) += ad7173.o > +obj-$(CONFIG_AD7191) += ad7191.o > obj-$(CONFIG_AD7192) += ad7192.o > obj-$(CONFIG_AD7266) += ad7266.o > obj-$(CONFIG_AD7280) += ad7280a.o > diff --git a/drivers/iio/adc/ad7191.c b/drivers/iio/adc/ad7191.c > new file mode 100644 > index 000000000000..f1eff7d095df > --- /dev/null > +++ b/drivers/iio/adc/ad7191.c > @@ -0,0 +1,553 @@ > +// SPDX-License-Identifier: GPL-2.0 Prefer GPL-2.0-only or GPL-2.0-or-later depending on your intention. > +/* > + * AD7191 ADC driver > + * > + * Copyright 2025 Analog Devices Inc. > + */ > + > +#include <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/gpio/consumer.h> > +#include <linux/interrupt.h> > +#include <linux/kernel.h> I think we tend to avoid including kernel.h since it includes way more than we actually need. > +#include <linux/mod_devicetable.h> > +#include <linux/mutex.h> > +#include <linux/property.h> > +#include <linux/regulator/consumer.h> > +#include <linux/spi/spi.h> > +#include <linux/types.h> > +#include <linux/units.h> > + > +#include <linux/iio/adc/ad_sigma_delta.h> > +#include <linux/iio/iio.h> > + > +#define ad_sigma_delta_to_ad7191(sigmad) \ > + container_of((sigmad), struct ad7191_state, sd) > + > +#define AD7191_TEMP_CODES_PER_DEGREE 2815 > + > +#define AD7191_EXT_CLK_ENABLE 0 > +#define AD7191_INT_CLK_ENABLE 1 Unused macros? > + > +#define AD7191_CHAN_MASK BIT(0) > +#define AD7191_TEMP_MASK BIT(1) > + > +enum ad7191_channel { > + AD7191_CH_AIN1_AIN2, > + AD7191_CH_AIN3_AIN4, > + AD7191_CH_TEMP, > +}; > + > +/* > + * NOTE: > + * The AD7191 features a dual-use data out ready DOUT/RDY output. > + * In order to avoid contentions on the SPI bus, it's therefore necessary > + * to use SPI bus locking. > + * > + * The DOUT/RDY output must also be wired to an interrupt-capable GPIO. > + * > + * The SPI controller's chip select must be connected to the PDOWN pin > + * of the ADC. When CS (PDOWN) is high, it powers down the device and > + * resets the internal circuitry. > + */ > + > +struct ad7191_state { > + struct ad_sigma_delta sd; > + struct mutex lock; /* Protect device state */ > + > + struct gpio_descs *odr_gpios; > + struct gpio_descs *pga_gpios; > + struct gpio_desc *temp_gpio; > + struct gpio_desc *chan_gpio; > + > + u16 int_vref_mv; > + const u32 (*scale_avail)[2]; > + size_t scale_avail_size; > + u32 scale_index; > + const u32 *samp_freq_avail; > + size_t samp_freq_avail_size; > + u32 samp_freq_index; > + > + struct clk *mclk; > +}; > + > +static int ad7191_set_channel(struct ad_sigma_delta *sd, unsigned int address) > +{ > + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); > + u8 temp_gpio_val, chan_gpio_val; > + > + if (!FIELD_FIT(AD7191_CHAN_MASK | AD7191_TEMP_MASK, address)) > + return -EINVAL; > + > + chan_gpio_val = FIELD_GET(AD7191_CHAN_MASK, address); > + temp_gpio_val = FIELD_GET(AD7191_TEMP_MASK, address); > + > + gpiod_set_value(st->chan_gpio, chan_gpio_val); > + gpiod_set_value(st->temp_gpio, temp_gpio_val); > + > + return 0; > +} > + > +static int ad7191_set_cs(struct ad_sigma_delta *sigma_delta, int assert) > +{ > + struct spi_transfer t = { > + .len = 0, > + .cs_change = assert, > + }; > + struct spi_message m; > + > + spi_message_init_with_transfers(&m, &t, 1); > + > + return spi_sync_locked(sigma_delta->spi, &m); > +} > + > +static int ad7191_set_mode(struct ad_sigma_delta *sd, > + enum ad_sigma_delta_mode mode) > +{ > + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); > + > + switch (mode) { > + case AD_SD_MODE_CONTINUOUS: > + case AD_SD_MODE_SINGLE: > + return ad7191_set_cs(&st->sd, 1); > + case AD_SD_MODE_IDLE: > + return ad7191_set_cs(&st->sd, 0); > + default: > + return -EINVAL; > + } > +} > + > +static const struct ad_sigma_delta_info ad7191_sigma_delta_info = { > + .set_channel = ad7191_set_channel, > + .set_mode = ad7191_set_mode, > + .has_registers = false, > +}; > + > +static int ad7191_init_regulators(struct iio_dev *indio_dev) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + struct device *dev = &st->sd.spi->dev; > + int ret; > + > + ret = devm_regulator_get_enable(dev, "avdd"); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to enable specified AVdd supply\n"); > + > + ret = devm_regulator_get_enable(dev, "dvdd"); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to enable specified DVdd supply\n"); > + > + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); > + if (ret < 0) > + return dev_err_probe(dev, ret, "Failed to get Vref voltage\n"); > + > + st->int_vref_mv = ret / 1000; > + > + return 0; > +} > + > +static int ad7191_config_setup(struct iio_dev *indio_dev) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + struct device *dev = &st->sd.spi->dev; > + /* Sampling frequencies in Hz, see Table 5 */ > + static const u32 samp_freq[4] = { 120, 60, 50, 10 }; > + /* Gain options, see Table 7 */ > + const u32 gain[4] = { 1, 8, 64, 128 }; > + static u32 scale_buffer[4][2]; > + int odr_value, odr_index, pga_value, pga_index, i, ret; > + u64 scale_uv; > + > + st->samp_freq_index = 0; > + st->scale_index = 0; > + > + ret = device_property_read_u32(dev, "adi,odr-value", &odr_value); > + if (ret && ret != -EINVAL) > + return dev_err_probe(dev, ret, "Failed to get odr value.\n"); > + > + if (ret == -EINVAL) { > + st->odr_gpios = devm_gpiod_get_array(dev, "odr", GPIOD_OUT_LOW); > + if (IS_ERR(st->odr_gpios)) > + return dev_err_probe(dev, PTR_ERR(st->odr_gpios), > + "Failed to get odr gpios.\n"); Might also be worthwhile to check st->odr_gpios->ndescs to make sure we have the right number. > + > + st->samp_freq_avail = samp_freq; > + st->samp_freq_avail_size = ARRAY_SIZE(samp_freq); > + } else { > + for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { > + if (odr_value != samp_freq[i]) > + continue; > + odr_index = i; > + break; > + } > + > + st->samp_freq_avail = &samp_freq[odr_index]; > + st->samp_freq_avail_size = 1; > + > + st->odr_gpios = NULL; > + } > + > + mutex_lock(&st->lock); > + > + for (i = 0; i < ARRAY_SIZE(scale_buffer); i++) { > + scale_uv = ((u64)st->int_vref_mv * NANO) >> > + (indio_dev->channels[0].scan_type.realbits - 1); > + do_div(scale_uv, gain[i]); > + scale_buffer[i][1] = do_div(scale_uv, NANO); > + scale_buffer[i][0] = scale_uv; Just asthetics, but setting [0] before [1] seems more logical. > + } > + > + mutex_unlock(&st->lock); > + > + ret = device_property_read_u32(dev, "adi,pga-value", &pga_value); > + if (ret && ret != -EINVAL) > + return dev_err_probe(dev, ret, "Failed to get pga value.\n"); > + > + if (ret == -EINVAL) { > + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); > + if (IS_ERR(st->pga_gpios)) > + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), > + "Failed to get pga gpios.\n"); Same comment about checking ndescs here. > + > + st->scale_avail = scale_buffer; > + st->scale_avail_size = ARRAY_SIZE(scale_buffer); > + } else { > + for (i = 0; i < ARRAY_SIZE(gain); i++) { > + if (pga_value != gain[i]) > + continue; > + pga_index = i; > + break; > + } > + > + st->scale_avail = &scale_buffer[pga_index]; > + st->scale_avail_size = 1; > + > + st->pga_gpios = NULL; > + } > + > + st->temp_gpio = devm_gpiod_get(dev, "temp", GPIOD_OUT_LOW); > + if (IS_ERR(st->temp_gpio)) > + return dev_err_probe(dev, PTR_ERR(st->temp_gpio), > + "Failed to get temp gpio.\n"); > + > + st->chan_gpio = devm_gpiod_get(dev, "chan", GPIOD_OUT_LOW); > + if (IS_ERR(st->chan_gpio)) > + return dev_err_probe(dev, PTR_ERR(st->chan_gpio), > + "Failed to get chan gpio.\n"); > + > + return 0; > +} > + > +static int ad7191_clock_setup(struct ad7191_state *st) > +{ > + struct device *dev = &st->sd.spi->dev; > + > + st->mclk = devm_clk_get_optional_enabled(dev, "mclk"); > + if (IS_ERR(st->mclk)) > + return dev_err_probe(dev, PTR_ERR(st->mclk), > + "Failed to get mclk.\n"); > + > + return 0; > +} > + > +static int ad7191_setup(struct iio_dev *indio_dev) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + int ret; > + > + ret = ad7191_init_regulators(indio_dev); > + if (ret) > + return ret; > + > + ret = ad7191_config_setup(indio_dev); > + if (ret) > + return ret; > + > + return ad7191_clock_setup(st); > +} > + > +static int ad7191_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long m) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + > + switch (m) { > + case IIO_CHAN_INFO_RAW: > + return ad_sigma_delta_single_conversion(indio_dev, chan, val); > + case IIO_CHAN_INFO_SCALE: > + switch (chan->type) { > + case IIO_VOLTAGE: { > + guard(mutex)(&st->lock); I don't think the mutex helps here. The caller receives pointers to these arrays and uses them _after_ this function returns so the mutex will no longer be held while the arrays are actually being used. As long as st->scale_avail is filled in during probe and never changes after probe, we should not need to worry about any locking, so I think it is OK to just drop the mutex from the driver. > + *val = st->scale_avail[st->scale_index][0]; > + *val2 = st->scale_avail[st->scale_index][1]; > + return IIO_VAL_INT_PLUS_NANO; > + } > + case IIO_TEMP: > + *val = 0; > + *val2 = NANO / AD7191_TEMP_CODES_PER_DEGREE; > + return IIO_VAL_INT_PLUS_NANO; > + default: > + return -EINVAL; > + } > + case IIO_CHAN_INFO_OFFSET: > + *val = -(1 << (chan->scan_type.realbits - 1)); > + switch (chan->type) { > + case IIO_VOLTAGE: > + return IIO_VAL_INT; > + case IIO_TEMP: > + *val -= 273 * AD7191_TEMP_CODES_PER_DEGREE; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > + case IIO_CHAN_INFO_SAMP_FREQ: > + *val = st->samp_freq_avail[st->samp_freq_index]; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > +} > + > +static int ad7191_set_gain(struct ad7191_state *st, int gain_index) > +{ > + unsigned long value = gain_index; Maybe a bit better style to not rely on implementation detail of bitmaps. This can be replaced with: DECLARE_BITMAP(bitmap, 2) = { }; bitmap_write(bitmap, gain_index, 0, 2); > + > + st->scale_index = gain_index; > + > + return gpiod_multi_set_value_cansleep(st->pga_gpios, &value); return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); > +} > + > +static int ad7191_set_samp_freq(struct ad7191_state *st, int samp_freq_index) > +{ > + unsigned long value = samp_freq_index; ditto > + > + st->samp_freq_index = samp_freq_index; > + > + return gpiod_multi_set_value_cansleep(st->odr_gpios, &value); > +} > + > +static int __ad7191_write_raw(struct ad7191_state *st, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + int i; > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: { > + if (!st->pga_gpios) > + return -EPERM; > + guard(mutex)(&st->lock); > + for (i = 0; i < st->scale_avail_size; i++) { > + if (val2 != st->scale_avail[i][1]) > + continue; > + return ad7191_set_gain(st, i); Could save a line by inverting the if and dropping the continue. > + } > + return -EINVAL; > + } > + case IIO_CHAN_INFO_SAMP_FREQ: { > + if (!st->odr_gpios) > + return -EPERM; > + guard(mutex)(&st->lock); > + for (i = 0; i < st->samp_freq_avail_size; i++) { > + if (val != st->samp_freq_avail[i]) > + continue; > + return ad7191_set_samp_freq(st, i); > + } > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > +} > + > +static int ad7191_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int val, int val2, > + long mask) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + int ret; > + > + ret = iio_device_claim_direct_mode(indio_dev); > + if (ret) > + return ret; Jonathan's mass changes are in the togreg branch now, so we can do the "new" way that works better with static checkers: if (!iio_device_claim_direct(indio_dev)) return -EBUSY; > + > + ret = __ad7191_write_raw(st, chan, val, val2, mask); > + > + iio_device_release_direct_mode(indio_dev); iio_device_release_direct(indio_dev); > + > + return ret; > +} > + > +static int ad7191_write_raw_get_fmt(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, long mask) > +{ > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + return IIO_VAL_INT_PLUS_NANO; > + case IIO_CHAN_INFO_SAMP_FREQ: > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > +} > + > +static int ad7191_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, const int **vals, > + int *type, int *length, long mask) > +{ > + struct ad7191_state *st = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + *vals = (int *)st->scale_avail; > + *type = IIO_VAL_INT_PLUS_NANO; > + *length = st->scale_avail_size * 2; > + return IIO_AVAIL_LIST; > + case IIO_CHAN_INFO_SAMP_FREQ: > + *vals = (int *)st->samp_freq_avail; > + *type = IIO_VAL_INT; > + *length = st->samp_freq_avail_size; > + return IIO_AVAIL_LIST; > + } > + > + return -EINVAL; > +} > + > +static const struct iio_info ad7191_info = { > + .read_raw = ad7191_read_raw, > + .write_raw = ad7191_write_raw, > + .write_raw_get_fmt = ad7191_write_raw_get_fmt, > + .read_avail = ad7191_read_avail, > + .validate_trigger = ad_sd_validate_trigger, > +}; > + > +static const struct iio_chan_spec ad7191_channels[] = { > + { > + .type = IIO_TEMP, > + .address = AD7191_CH_TEMP, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_OFFSET) | > + BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), > + .scan_type = { > + .sign = 'u', > + .realbits = 24, > + .storagebits = 32, > + .endianness = IIO_BE, > + }, > + }, > + { > + .type = IIO_VOLTAGE, > + .differential = 1, > + .indexed = 1, > + .channel = 1, > + .channel2 = 2, > + .address = AD7191_CH_AIN1_AIN2, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_OFFSET) | > + BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), > + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), > + .scan_index = 1, > + .scan_type = { > + .sign = 'u', > + .realbits = 24, > + .storagebits = 32, > + .endianness = IIO_BE, > + }, > + }, > + { > + .type = IIO_VOLTAGE, > + .differential = 1, > + .indexed = 1, > + .channel = 3, > + .channel2 = 4, > + .address = AD7191_CH_AIN3_AIN4, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_OFFSET) | > + BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), > + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), > + .scan_index = 2, > + .scan_type = { > + .sign = 'u', > + .realbits = 24, > + .storagebits = 32, > + .endianness = IIO_BE, > + }, > + }, > + IIO_CHAN_SOFT_TIMESTAMP(3), > +}; > + > +static int ad7191_probe(struct spi_device *spi) > +{ > + struct device *dev = &spi->dev; > + struct ad7191_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); > + > + ret = devm_mutex_init(dev, &st->lock); > + if (ret) > + return ret; > + > + indio_dev->name = "ad7191"; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = ad7191_channels; > + indio_dev->num_channels = ARRAY_SIZE(ad7191_channels); > + indio_dev->info = &ad7191_info; > + > + ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7191_sigma_delta_info); > + if (ret) > + return ret; > + > + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); > + if (ret) > + return ret; > + > + ret = ad7191_setup(indio_dev); > + if (ret) > + return ret; > + > + return devm_iio_device_register(dev, indio_dev); > +} > + > +static const struct of_device_id ad7191_of_match[] = { > + { > + .compatible = "adi,ad7191", > + }, This could fit on one line. > + { } > +}; > +MODULE_DEVICE_TABLE(of, ad7191_of_match); > + > +static const struct spi_device_id ad7191_id_table[] = { > + { "ad7191" }, > + { } > +}; > +MODULE_DEVICE_TABLE(spi, ad7191_id_table); > + > +static struct spi_driver ad7191_driver = { > + .driver = { > + .name = "ad7191", > + .of_match_table = ad7191_of_match, > + }, > + .probe = ad7191_probe, > + .id_table = ad7191_id_table, > +}; > +module_spi_driver(ad7191_driver); > + > +MODULE_AUTHOR("Alisa-Dariana Roman <alisa.roman@analog.com>"); > +MODULE_DESCRIPTION("Analog Devices AD7191 ADC"); > +MODULE_LICENSE("GPL"); > +MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA); ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/3] iio: adc: ad7191: add AD7191 2025-02-26 11:53 ` [PATCH v5 2/3] iio: adc: ad7191: " Alisa-Dariana Roman 2025-02-26 21:05 ` David Lechner @ 2025-02-27 23:34 ` kernel test robot 1 sibling, 0 replies; 7+ messages in thread From: kernel test robot @ 2025-02-27 23:34 UTC (permalink / raw) To: Alisa-Dariana Roman, Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, David Lechner, linux-iio, devicetree, linux-kernel, linux-doc Cc: llvm, oe-kbuild-all, Lars-Peter Clausen, Michael Hennerich, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet Hi Alisa-Dariana, kernel test robot noticed the following build warnings: [auto build test WARNING on jic23-iio/togreg] [also build test WARNING on robh/for-next linus/master v6.14-rc4 next-20250227] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Alisa-Dariana-Roman/dt-bindings-iio-adc-add-AD7191/20250226-195853 base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg patch link: https://lore.kernel.org/r/20250226115451.249361-3-alisa.roman%40analog.com patch subject: [PATCH v5 2/3] iio: adc: ad7191: add AD7191 config: hexagon-allyesconfig (https://download.01.org/0day-ci/archive/20250228/202502280702.31rbuGw8-lkp@intel.com/config) compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250228/202502280702.31rbuGw8-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202502280702.31rbuGw8-lkp@intel.com/ All warnings (new ones prefixed by >>): >> drivers/iio/adc/ad7191.c:217:15: warning: variable 'pga_index' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized] 217 | for (i = 0; i < ARRAY_SIZE(gain); i++) { | ^~~~~~~~~~~~~~~~~~~~ drivers/iio/adc/ad7191.c:224:35: note: uninitialized use occurs here 224 | st->scale_avail = &scale_buffer[pga_index]; | ^~~~~~~~~ drivers/iio/adc/ad7191.c:217:15: note: remove the condition if it is always true 217 | for (i = 0; i < ARRAY_SIZE(gain); i++) { | ^~~~~~~~~~~~~~~~~~~~ drivers/iio/adc/ad7191.c:160:48: note: initialize the variable 'pga_index' to silence this warning 160 | int odr_value, odr_index, pga_value, pga_index, i, ret; | ^ | = 0 >> drivers/iio/adc/ad7191.c:179:15: warning: variable 'odr_index' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized] 179 | for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/adc/ad7191.c:186:36: note: uninitialized use occurs here 186 | st->samp_freq_avail = &samp_freq[odr_index]; | ^~~~~~~~~ drivers/iio/adc/ad7191.c:179:15: note: remove the condition if it is always true 179 | for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/adc/ad7191.c:160:26: note: initialize the variable 'odr_index' to silence this warning 160 | int odr_value, odr_index, pga_value, pga_index, i, ret; | ^ | = 0 drivers/iio/adc/ad7191.c:553:18: error: expected ';' after top level declarator 553 | MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA); | ^ 2 warnings and 1 error generated. vim +217 drivers/iio/adc/ad7191.c 150 151 static int ad7191_config_setup(struct iio_dev *indio_dev) 152 { 153 struct ad7191_state *st = iio_priv(indio_dev); 154 struct device *dev = &st->sd.spi->dev; 155 /* Sampling frequencies in Hz, see Table 5 */ 156 static const u32 samp_freq[4] = { 120, 60, 50, 10 }; 157 /* Gain options, see Table 7 */ 158 const u32 gain[4] = { 1, 8, 64, 128 }; 159 static u32 scale_buffer[4][2]; 160 int odr_value, odr_index, pga_value, pga_index, i, ret; 161 u64 scale_uv; 162 163 st->samp_freq_index = 0; 164 st->scale_index = 0; 165 166 ret = device_property_read_u32(dev, "adi,odr-value", &odr_value); 167 if (ret && ret != -EINVAL) 168 return dev_err_probe(dev, ret, "Failed to get odr value.\n"); 169 170 if (ret == -EINVAL) { 171 st->odr_gpios = devm_gpiod_get_array(dev, "odr", GPIOD_OUT_LOW); 172 if (IS_ERR(st->odr_gpios)) 173 return dev_err_probe(dev, PTR_ERR(st->odr_gpios), 174 "Failed to get odr gpios.\n"); 175 176 st->samp_freq_avail = samp_freq; 177 st->samp_freq_avail_size = ARRAY_SIZE(samp_freq); 178 } else { > 179 for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { 180 if (odr_value != samp_freq[i]) 181 continue; 182 odr_index = i; 183 break; 184 } 185 186 st->samp_freq_avail = &samp_freq[odr_index]; 187 st->samp_freq_avail_size = 1; 188 189 st->odr_gpios = NULL; 190 } 191 192 mutex_lock(&st->lock); 193 194 for (i = 0; i < ARRAY_SIZE(scale_buffer); i++) { 195 scale_uv = ((u64)st->int_vref_mv * NANO) >> 196 (indio_dev->channels[0].scan_type.realbits - 1); 197 do_div(scale_uv, gain[i]); 198 scale_buffer[i][1] = do_div(scale_uv, NANO); 199 scale_buffer[i][0] = scale_uv; 200 } 201 202 mutex_unlock(&st->lock); 203 204 ret = device_property_read_u32(dev, "adi,pga-value", &pga_value); 205 if (ret && ret != -EINVAL) 206 return dev_err_probe(dev, ret, "Failed to get pga value.\n"); 207 208 if (ret == -EINVAL) { 209 st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); 210 if (IS_ERR(st->pga_gpios)) 211 return dev_err_probe(dev, PTR_ERR(st->pga_gpios), 212 "Failed to get pga gpios.\n"); 213 214 st->scale_avail = scale_buffer; 215 st->scale_avail_size = ARRAY_SIZE(scale_buffer); 216 } else { > 217 for (i = 0; i < ARRAY_SIZE(gain); i++) { 218 if (pga_value != gain[i]) 219 continue; 220 pga_index = i; 221 break; 222 } 223 224 st->scale_avail = &scale_buffer[pga_index]; 225 st->scale_avail_size = 1; 226 227 st->pga_gpios = NULL; 228 } 229 230 st->temp_gpio = devm_gpiod_get(dev, "temp", GPIOD_OUT_LOW); 231 if (IS_ERR(st->temp_gpio)) 232 return dev_err_probe(dev, PTR_ERR(st->temp_gpio), 233 "Failed to get temp gpio.\n"); 234 235 st->chan_gpio = devm_gpiod_get(dev, "chan", GPIOD_OUT_LOW); 236 if (IS_ERR(st->chan_gpio)) 237 return dev_err_probe(dev, PTR_ERR(st->chan_gpio), 238 "Failed to get chan gpio.\n"); 239 240 return 0; 241 } 242 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 3/3] docs: iio: add AD7191 2025-02-26 11:53 [PATCH v5 0/3] Add support for AD7191 Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 1/3] dt-bindings: iio: adc: add AD7191 Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 2/3] iio: adc: ad7191: " Alisa-Dariana Roman @ 2025-02-26 11:53 ` Alisa-Dariana Roman 2025-02-26 21:07 ` David Lechner 2 siblings, 1 reply; 7+ messages in thread From: Alisa-Dariana Roman @ 2025-02-26 11:53 UTC (permalink / raw) To: Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, David Lechner, linux-iio, devicetree, linux-kernel, linux-doc Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet Add documentation for AD7191 driver. Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> --- Documentation/iio/ad7191.rst | 129 +++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 131 insertions(+) create mode 100644 Documentation/iio/ad7191.rst diff --git a/Documentation/iio/ad7191.rst b/Documentation/iio/ad7191.rst new file mode 100644 index 000000000000..6ab615ea457b --- /dev/null +++ b/Documentation/iio/ad7191.rst @@ -0,0 +1,129 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD7191 driver +============= + +Device driver for Analog Devices AD7191 ADC. + +================= +Supported devices +================= + +* `AD7191 <https://www.analog.com/AD7191>`_ + +The AD7191 is a high precision, low noise, 24-bit Σ-Δ ADC with integrated PGA. +It features two differential input channels, an internal temperature sensor, and +configurable sampling rates. + +========== +Devicetree +========== + +----------------- +Pin Configuration +----------------- + +The driver supports both pin-strapped and GPIO-controlled configurations for ODR +(Output Data Rate) and PGA (Programmable Gain Amplifier) settings. These +configurations are mutually exclusive - you must use either pin-strapped or GPIO +control for each setting, not both. + +^^^^^^^^^^^^^^^^^ +ODR Configuration +^^^^^^^^^^^^^^^^^ + +The ODR can be configured either through GPIO control or pin-strapping: + +- When using GPIO control, specify the "odr-gpios" property in the device tree +- For pin-strapped configuration, specify the "adi,odr-value" property in the + device tree + +Available ODR settings: + + - 120 Hz (ODR1=0, ODR2=0) + - 60 Hz (ODR1=0, ODR2=1) + - 50 Hz (ODR1=1, ODR2=0) + - 10 Hz (ODR1=1, ODR2=1) + +^^^^^^^^^^^^^^^^^ +PGA Configuration +^^^^^^^^^^^^^^^^^ + +The PGA can be configured either through GPIO control or pin-strapping: + +- When using GPIO control, specify the "pga-gpios" property in the device tree +- For pin-strapped configuration, specify the "adi,pga-value" property in the + device tree + +Available PGA gain settings: + + - 1x (PGA1=0, PGA2=0) + - 8x (PGA1=0, PGA2=1) + - 64x (PGA1=1, PGA2=0) + - 128x (PGA1=1, PGA2=1) + +------------------- +Clock Configuration +------------------- + +The AD7191 supports both internal and external clock sources: + +- When CLKSEL pin is tied LOW: Uses internal 4.92MHz clock (no clock property + needed) +- When CLKSEL pin is tied HIGH: Requires external clock source + - Can be a crystal between MCLK1 and MCLK2 pins + - Or a CMOS-compatible clock driving MCLK2 pin + - Must specify the "clocks" property in device tree when using external clock + +-------------------------- +SPI Interface Requirements +-------------------------- + +The AD7191 has specific SPI interface requirements: + +- The DOUT/RDY output is dual-purpose and requires SPI bus locking +- DOUT/RDY must be connected to an interrupt-capable GPIO +- The SPI controller's chip select must be connected to the PDOWN pin of the ADC +- When CS (PDOWN) is high, the device powers down and resets internal circuitry +- SPI mode 3 operation (CPOL=1, CPHA=1) is required + +------------------------- +Power Supply Requirements +------------------------- + +The device requires the following power supplies: + +- AVdd: Analog power supply +- DVdd: Digital power supply +- Vref: Reference voltage supply (external) + +All power supplies must be specified in the device tree. + +===================== +Channel Configuration +===================== + +The device provides three channels: + +1. Temperature Sensor + - 24-bit unsigned + - Internal temperature measurement + - Temperature in millidegrees Celsius + +2. Differential Input (AIN1-AIN2) + - 24-bit unsigned + - Differential voltage measurement + - Configurable gain via PGA + +3. Differential Input (AIN3-AIN4) + - 24-bit unsigned + - Differential voltage measurement + - Configurable gain via PGA + +============== +Buffer Support +============== + +This driver supports IIO triggered buffers. See Documentation/iio/iio_devbuf.rst +for more information about IIO triggered buffers. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 2d334be2b7f2..edc984a38b3b 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -21,6 +21,7 @@ Industrial I/O Kernel Drivers ad4000 ad4030 ad4695 + ad7191 ad7380 ad7606 ad7625 diff --git a/MAINTAINERS b/MAINTAINERS index 87c491975ced..0547a3bb528c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1352,6 +1352,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml +F: Documentation/iio/ad7191.rst F: drivers/iio/adc/ad7191.c ANALOG DEVICES INC AD7192 DRIVER -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v5 3/3] docs: iio: add AD7191 2025-02-26 11:53 ` [PATCH v5 3/3] docs: iio: " Alisa-Dariana Roman @ 2025-02-26 21:07 ` David Lechner 0 siblings, 0 replies; 7+ messages in thread From: David Lechner @ 2025-02-26 21:07 UTC (permalink / raw) To: Alisa-Dariana Roman, Alisa-Dariana Roman, Rob Herring (Arm), Jonathan Cameron, Ramona Gradinariu, linux-iio, devicetree, linux-kernel, linux-doc Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet On 2/26/25 5:53 AM, Alisa-Dariana Roman wrote: > Add documentation for AD7191 driver. > > Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> > --- > Documentation/iio/ad7191.rst | 129 +++++++++++++++++++++++++++++++++++ > Documentation/iio/index.rst | 1 + > MAINTAINERS | 1 + > 3 files changed, 131 insertions(+) > create mode 100644 Documentation/iio/ad7191.rst > > diff --git a/Documentation/iio/ad7191.rst b/Documentation/iio/ad7191.rst > new file mode 100644 > index 000000000000..6ab615ea457b > --- /dev/null > +++ b/Documentation/iio/ad7191.rst > @@ -0,0 +1,129 @@ > +.. SPDX-License-Identifier: GPL-2.0-only > + > +============= > +AD7191 driver > +============= In .rst, the page title really should be a different heading level from the rest of the section headings on the page. > + > +Device driver for Analog Devices AD7191 ADC. > + > +================= > +Supported devices > +================= > + ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-02-27 23:35 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-02-26 11:53 [PATCH v5 0/3] Add support for AD7191 Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 1/3] dt-bindings: iio: adc: add AD7191 Alisa-Dariana Roman 2025-02-26 11:53 ` [PATCH v5 2/3] iio: adc: ad7191: " Alisa-Dariana Roman 2025-02-26 21:05 ` David Lechner 2025-02-27 23:34 ` kernel test robot 2025-02-26 11:53 ` [PATCH v5 3/3] docs: iio: " Alisa-Dariana Roman 2025-02-26 21:07 ` David Lechner
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox