* [PATCH v3 00/10] Add support for AD4170 series of ADCs
@ 2025-05-13 12:32 Marcelo Schmitt
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
` (9 more replies)
0 siblings, 10 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:32 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Hello,
Thank you to all reviewers of v1 and v2.
Hope I've been able to sort out all issues raised and incorporate all
improvement suggestions in v3. Despite splitting some parts out of the base
driver, that's still not a small patch. I understand that it may take more than
usual to receive reviews for this series. Thank you.
There is actually one thing left that I'd like to improve which is the support
for negative/bipolar voltage references. Though, that will lead to changes in
the regulator subsystem/framework which would bloat this series even more.
If possible, I'd like to do that on a separate patch set.
This patch set adds support for Analog Devices AD4170 and similar sigma-delta ADCs.
Patch 1 adds device tree documentation for the parts.
Patch 2 adds basic device support.
Patch 3 adds support for calibration scale.
Patch 4 adds support for calibration bias.
Patch 5 adds support for sample frequency along with filter type configuration.
Patch 6 adds support for buffered ADC reading.
Patch 7 adds clock provider support
Patch 8 adds GPIO controller support.
Patch 9 adds internal temperature sensor support.
Patch 10 adds support for external RTD and bridge circuit sensors.
Change log v2 -> v3
[device tree changes]
- Removed unneeded allOf.
- Removed occurences of adi,sensor-type type re-declaration.
- Created type for the AD4170 channels, allowing to avoid dt doc repetition.
[General IIO driver changes]
- Fixed issues reported by 0-DAY kernel test robot.
- Used pahole to reorder ad4170_state fields for better cache line alignment.
- Replaced all occurrences of `int i` by `unsigned int i`.
- Replaced all `if (ret < 0)` by `if (ret)` whenever appropriate.
[Basic driver patch]
- Updated Copyright year.
- Separated handling of channel setup cases for better understanding of code flow.
- Now comparing setups field by field instead of using memcmp().
- Disable channel on ad4170_read_sample() error path.
- Reinit completion before entering single conversion mode.
- Organized ad4170_sinc3_filt_fs_tbl.
- Used clamp to simplify configuration value checking.
- Returned earlier whenever possible.
- Used HZ_PER_KHZ/MHZ.
- Declared internal voltage reference constant AD4170_INT_REF_2_5V
- Many other minor code style and readability improvements.
[New patch - Add support for calibration scale/gain]
[New patch - Add support for calibration bian/offset]
[New patch - Add support for adjustable sample frequency]
[Buffered capture patch]
- Made validate_scan_mask() return false if no channel enabled (bitmap_weight() == 0).
- Use local variable device pointer to simplify trigger setup and better wrap lines.
- Tidy up buffer declaration, function returns, comments, and other minor things.
- Fixed data handling in trigger handler which I hadn't noticed to became buggy
after xfter buffer type was changed from __be32 to u8 array.
[CLOCK provider patch]
- Applied minor changes according to v2 review.
- Avoid unmet dependencies by depending on COMMON_CLK rather than selecting that.
- Brought early external clock defines from early patches to this one.
- Picked up a review tag from v1 review that I failed to include in v2.
[GPIO controller patch]
- Defined masks for updating GPIO mode register.
- Replaced regmap_clear/set_bits() by regmap_update_bits() to set GPIO direction.
- Removed GPIO direction check before setting GPIO output values.
- Made use of regmap_assign_bits() to set GPIO output reg bits.
- Made value to be set as GPIO output state be either 0 or 1.
- No longer locking on state mutex on GPIO set since GPIO output should not
conflict with other direct mode functionality (e.g. single-shot read).
[Internal temperature sensor patch]
- Wrapped line according to logical approach.
[External sensor patch]
- Added trailing comma to ad4170_iout_current_ua_tbl array.
- Simplified AD4170_CURRENT_SRC_REG constants with macros.
- Used temporary variable to keep logical line wrapping of vbias calculation.
- Dropped ad4170_find_table_index() after open coding both uses of that.
- Extracted pin validation to reduce indentation.
- Inverted ad4170_setup_bridge() logic to reduce indentation.
- Used GPIO register masks to convey reg write meanings in ad4170_setup_bridge().
- Reworked ad4170_setup_current_src() to make it readable.
Link to v2: https://lore.kernel.org/linux-iio/cover.1745841276.git.marcelo.schmitt@analog.com/
Change log v1 -> v2
[IIO driver changes]
- Call gpio_set() at begining of gpio_direction_output() instead of at the end of it.
- Return -EPERM if try to set a GPIO configured for input.
- Now locking on state mutex before setting output GPIO values.
- Used gpiochio init_valid_mask() to only init available GPIOs.
- Replaced 3 regmap configs by regmap with custom reg_read/write implementation.
- Improved to support more than one extarnal sensor connected.
- A few other minor improvements.
[device tree changes]
- Referenced adc.yaml from sensor-node.
- Merged property descriptions to reduce doc duplication.
- Every child node type is now in the example.
- Better described sensor-type property with a list of possible types.
- Updated adi,excitation-pins description to cover a use case I had overlooked.
- Added default to interrupt-names and to clock-names.
- Dropped '|' from descriptions when not needed.
- Added extra example
Link to v1: https://lore.kernel.org/linux-iio/cover.1744200264.git.marcelo.schmitt@analog.com/
Thanks,
Marcelo
Ana-Maria Cusco (1):
iio: adc: Add basic support for AD4170
Marcelo Schmitt (9):
dt-bindings: iio: adc: Add AD4170
iio: adc: ad4170: Add support for calibration gain
iio: adc: ad4170: Add support for calibration bias
iio: adc: ad4170: Add digital filter and sample frequency config
support
iio: adc: ad4170: Add support for buffered data capture
iio: adc: ad4170: Add clock provider support
iio: adc: ad4170: Add GPIO controller support
iio: adc: ad4170: Add support for internal temperature sensor
iio: adc: ad4170: Add support for weigh scale and RTD sensors
.../bindings/iio/adc/adi,ad4170.yaml | 544 +++
MAINTAINERS | 8 +
drivers/iio/adc/Kconfig | 16 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4170.c | 2921 +++++++++++++++++
5 files changed, 3490 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
create mode 100644 drivers/iio/adc/ad4170.c
base-commit: d820bea88e277acc38b011fde9a8dc9f5310a6b9
--
2.47.2
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
@ 2025-05-13 12:33 ` Marcelo Schmitt
2025-05-13 15:47 ` David Lechner
` (2 more replies)
2025-05-13 12:34 ` [PATCH v3 02/10] iio: adc: Add basic support for AD4170 Marcelo Schmitt
` (8 subsequent siblings)
9 siblings, 3 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:33 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Add device tree documentation for AD4170 and similar sigma-delta ADCs.
The AD4170 is a 24-bit, multichannel, sigma-delta ADC.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
[device tree changes]
- Removed unneeded allOf.
- Removed occurences of adi,sensor-type type re-declaration.
- Created type for the AD4170 channels, allowing to avoid dt doc repetition.
.../bindings/iio/adc/adi,ad4170.yaml | 544 ++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 551 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
new file mode 100644
index 000000000000..0a06258b6631
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
@@ -0,0 +1,544 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad4170.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD4170 and similar Analog to Digital Converters
+
+maintainers:
+ - Marcelo Schmitt <marcelo.schmitt@analog.com>
+
+description: |
+ Analog Devices AD4170 series of Sigma-delta Analog to Digital Converters.
+ Specifications can be found at:
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ad4190-4.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ad4195-4.pdf
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+$defs:
+ ad4170-channel:
+ type: object
+ $ref: /schemas/iio/adc/adc.yaml#
+ description:
+ Common properties for configuring AD4170 channels.
+
+ properties:
+ adi,reference-select:
+ description: |
+ Selects the reference source to use when converting on the specific
+ channel. Valid values are:
+ 0: Differential reference voltage REFIN+ - REFIN−.
+ 1: Differential reference voltage REFIN2+ - REFIN2−.
+ 2: Internal 2.5V referece (REFOUT) relative to AVSS.
+ 3: Analog supply voltage (AVDD) relative AVSS.
+ $ref: /schemas/types.yaml#/definitions/uint8
+ enum: [0, 1, 2, 3]
+
+
+ sensor-node:
+ type: object
+ $ref: '#/$defs/ad4170-channel'
+ description:
+ The AD4170 and similar designs have features to aid interfacing with weigh
+ scale, RTD, and thermocouple sensors. Each of those sensor types requires
+ either distinct wiring configuration or external circuitry for proper
+ sensor operation and can use different AD4170 functionality on their
+ setups. A key characteristic of those external sensors is that they must
+ be excited either by voltage supply or by AD4170 excitation signals. The
+ sensor can then be read through a pair of analog inputs. These properties
+ describe external sensor circuitry connected to the ADC.
+
+ properties:
+ reg:
+ description:
+ Channel number. Connects the sensor to the channel with this number
+ of the device.
+ minimum: 1
+ maximum: 16
+
+ diff-channels:
+ description:
+ Defines the ADC input pins used to read sensor data. Only regular
+ analog input pins can be used.
+ items:
+ enum: [0, 1, 2, 3, 4, 5, 6, 7, 8]
+
+ bipolar: true
+
+ adi,sensor-type:
+ description: |
+ Type of sensor connected to the device. Depending on the sensor type
+ (weigh scale, RTD, or thermocouple) the values of sensor-node
+ properties have slightly different constraints. This property
+ specifies which particular external sensor is connected to the ADC so
+ the sensor-node properties can be properly parsed and verified. The
+ possible sensor types are:
+ 0: weigh scale;
+ 1: RTD;
+ 2: thermocouple.
+ $ref: /schemas/types.yaml#/definitions/uint8
+
+ adi,excitation-ac:
+ type: boolean
+ description:
+ Whether the external sensor has to be AC or DC excited.
+
+ adi,excitation-pins:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description:
+ Pins used to excite the sensor or external circuit that contains the
+ sensor. Thermocouples and RTD sensors are excited either with one
+ current source or with a pair of current sources to minimize the
+ excitation current mismatch and the excitation current drift matching
+ on the ADC. E.g. <0>; <1>; <0 1>. Load cell weigh scales may be
+ excited with one current source, a pair of excitation currents, or two
+ pairs of excitation currents. When four pins are defined, the first
+ two values specify the first pair and the last ones specify the second
+ pair of excitation currents. E.g. <0>; <0 1>; <0 1 2 3>.
+ items:
+ minimum: 0
+ maximum: 20
+
+ adi,excitation-current-microamp:
+ description:
+ Excitation current in microamperes to be output to each excitation pin
+ specified by adi,excitation-pins property. If not provided and
+ adi,excitation-ac is true, use predefined ACX1, ACX1 negated, ACX2,
+ and ACX2 negated signals to AC excite the bridge circuit. Those
+ singals are output on GPIO2, GPIO0, GPIO3, and GPIO1, respectively.
+ enum: [0, 10, 50, 100, 250, 500, 1000, 1500]
+ default: 0
+
+ adi,power-down-switch-pin:
+ description:
+ Number of the GPIO used as power-down switch for the bridge circuit.
+ $ref: /schemas/types.yaml#/definitions/uint8
+ enum: [0, 1]
+
+ adi,vbias:
+ type: boolean
+ description:
+ For unbiased thermocouple applications, the voltage generated by the
+ thermocouple must be biased around some DC voltage. When present, this
+ property specifies a bias voltage of (AVDD + AVSS)/2 to be applied as
+ common-mode voltage for the sensor.
+
+ required:
+ - reg
+ - diff-channels
+ - bipolar
+ - adi,sensor-type
+ - adi,reference-select
+
+
+properties:
+ compatible:
+ enum:
+ - adi,ad4170
+ - adi,ad4190
+ - adi,ad4195
+
+ avss-supply:
+ description:
+ Referece voltage supply for AVSS. If provided, describes the magnitude
+ (absolute value) of the negative voltage supplied to the AVSS pin. Since
+ AVSS must be −2.625V minimum and 0V maximum, the declared supply voltage
+ must be between 0 and 2.65V. If not provided, AVSS is assumed to be at
+ system ground (0V).
+
+ avdd-supply:
+ description:
+ A supply of 4.75V to 5.25V relative to AVSS that powers the chip (AVDD).
+
+ iovdd-supply:
+ description: 1.7V to 5.25V reference supply to the serial interface (IOVDD).
+
+ refin1p-supply:
+ description: REFIN+ supply that can be used as reference for conversion.
+
+ refin1n-supply:
+ description: REFIN- supply that can be used as reference for conversion. If
+ provided, describes the magnitude (absolute value) of the negative voltage
+ supplied to the REFIN- pin.
+
+ refin2p-supply:
+ description: REFIN2+ supply that can be used as reference for conversion.
+
+ refin2n-supply:
+ description: REFIN2- supply that can be used as reference for conversion. If
+ provided, describes the magnitude (absolute value) of the negative voltage
+ supplied to the REFIN2- pin.
+
+ spi-cpol: true
+
+ spi-cpha: true
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-names:
+ description:
+ Specify which pin should be configured as Data Ready interrupt.
+ enum:
+ - sdo
+ - dig_aux1
+ default: sdo
+
+ clocks:
+ maxItems: 1
+ description:
+ Optional external clock source. Can specify either an external clock or
+ external crystal.
+
+ clock-names:
+ enum:
+ - ext-clk
+ - xtal
+ default: ext-clk
+
+ '#clock-cells':
+ const: 0
+
+ clock-output-names:
+ maxItems: 1
+
+ gpio-controller: true
+
+ "#gpio-cells":
+ const: 2
+ description: |
+ The first cell is for the GPIO number: 0 to 3.
+ The second cell takes standard GPIO flags.
+
+ ldac-gpios:
+ description:
+ GPIO connected to DIG_AUX2 pin to be used as LDAC toggle to control the
+ transfer of data from the DAC_INPUT_A register to the DAC.
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ "^channel@[0-9a-f]$":
+ $ref: '#/$defs/ad4170-channel'
+ unevaluatedProperties: false
+ description:
+ Represents the external channels which are connected to the ADC.
+
+ properties:
+ reg:
+ description:
+ The channel number.
+ minimum: 0
+ maximum: 15
+
+ diff-channels:
+ description: |
+ This property is used for defining the inputs of a differential
+ voltage channel. The first value is the positive input and the second
+ value is the negative input of the channel.
+
+ Besides the analog input pins AIN0 to AIN8, there are special inputs
+ that can be selected with the following values:
+ 17: Internal temperature sensor
+ 18: (AVDD-AVSS)/5
+ 19: (IOVDD-DGND)/5
+ 20: DAC output
+ 21: ALDO
+ 22: DLDO
+ 23: AVSS
+ 24: DGND
+ 25: REFIN+
+ 26: REFIN-
+ 27: REFIN2+
+ 28: REFIN2-
+ 29: REFOUT
+ For the internal temperature sensor, use the input number for both
+ inputs (i.e. diff-channels = <17 17>).
+ items:
+ enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29]
+
+ single-channel: true
+
+ common-mode-channel: true
+
+ bipolar: true
+
+ adi,buffered-positive:
+ description: |
+ Enable precharge buffer, full buffer, or skip reference buffering of
+ the positive voltage reference. Because the output impedance of the
+ source driving the voltage reference inputs may be dynamic, RC
+ combinations of those inputs can cause DC gain errors if the reference
+ inputs go unbuffered into the ADC. Enable reference buffering if the
+ provided reference source has dynamic high impedance output. Note the
+ absolute voltage allowed on positive reference inputs (REFIN+,
+ REFIN2+) is from AVSS − 50 mV to AVDD + 50 mV when the reference
+ buffers are disabled but narrows to AVSS to AVDD when reference
+ buffering is enabled or in precharge mode.
+ 0: Reference precharge buffer.
+ 1: Full Buffer.
+ 2: Bypass reference buffers (buffering disabled).
+ $ref: /schemas/types.yaml#/definitions/uint8
+ enum: [0, 1, 2]
+ default: 1
+
+ adi,buffered-negative:
+ description: |
+ Enable precharge buffer, full buffer, or skip reference buffering of
+ the negative voltage reference. Because the output impedance of the
+ source driving the voltage reference inputs may be dynamic, RC
+ combinations of those inputs can cause DC gain errors if the reference
+ inputs go unbuffered into the ADC. Enable reference buffering if the
+ provided reference source has dynamic high impedance output. Note the
+ absolute voltage allowed on negative reference inputs (REFIN-,
+ REFIN2-) is from AVSS − 50 mV to AVDD + 50 mV when the reference
+ buffers are disabled but narrows to AVSS to AVDD when reference
+ buffering is enabled or in precharge mode.
+ 0: Reference precharge buffer.
+ 1: Full Buffer.
+ 2: Bypass reference buffers (buffering disabled).
+ $ref: /schemas/types.yaml#/definitions/uint8
+ enum: [0, 1, 2]
+ default: 1
+
+ required:
+ - reg
+
+ oneOf:
+ - required: [single-channel]
+ properties:
+ diff-channels: false
+ - required: [diff-channels]
+ properties:
+ single-channel: false
+ common-mode-channel: false
+
+ "^weighscale@":
+ $ref: '#/$defs/sensor-node'
+ unevaluatedProperties: false
+
+ properties:
+ diff-channels: true
+ bipolar: true
+
+ adi,sensor-type:
+ description: Weigh scale sensor.
+ const: 0
+
+ adi,excitation-pins: true
+
+ "^rtd@":
+ $ref: '#/$defs/sensor-node'
+ unevaluatedProperties: false
+
+ properties:
+ diff-channels: true
+ bipolar: true
+
+ adi,sensor-type:
+ description: RTD sensor.
+ const: 1
+
+ adi,excitation-pins: true
+
+ adi,excitation-current-microamp: true
+
+ required:
+ - adi,excitation-pins
+ - adi,excitation-current-microamp
+
+ "^thermocouple@":
+ $ref: '#/$defs/sensor-node'
+ unevaluatedProperties: false
+
+ properties:
+ diff-channels: true
+ bipolar: true
+
+ adi,sensor-type:
+ description: Thermocouple sensor.
+ const: 2
+
+ required:
+ - adi,excitation-pins
+ - adi,excitation-current-microamp
+
+required:
+ - compatible
+ - reg
+ - avdd-supply
+ - iovdd-supply
+ - spi-cpol
+ - spi-cpha
+
+allOf:
+ # Some devices don't have integrated DAC
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - adi,ad4190
+ - adi,ad4195
+ then:
+ properties:
+ ldac-gpios: false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4170";
+ reg = <0>;
+ spi-max-frequency = <20000000>;
+ spi-cpol;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ iovdd-supply = <&iovdd>;
+ interrupt-parent = <&gpio_in>;
+ interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ // Sample AIN0 with respect to DGND throughout AVDD/DGND input range
+ // Pseudo-differential unipolar
+ channel@0 {
+ reg = <0>;
+ single-channel = <0>;
+ common-mode-channel = <24>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Weigh scale sensor
+ weighscale@1 {
+ reg = <1>;
+ bipolar;
+ diff-channels = <1 2>;
+ adi,sensor-type = /bits/ 8 <0>;
+ adi,reference-select = /bits/ 8 <0>;
+ adi,excitation-ac;
+ adi,excitation-pins = <19 20>;
+ adi,power-down-switch-pin = /bits/ 8 <0>;
+ };
+ // RTD sensor
+ rtd@2 {
+ reg = <2>;
+ bipolar;
+ diff-channels = <3 4>;
+ adi,sensor-type = /bits/ 8 <1>;
+ adi,reference-select = /bits/ 8 <0>;
+ adi,excitation-ac;
+ adi,excitation-pins = <5 6>;
+ adi,excitation-current-microamp = <500>;
+ };
+ // Thermocouple sensor
+ thermocouple@3 {
+ reg = <3>;
+ bipolar;
+ diff-channels = <7 8>;
+ adi,sensor-type = /bits/ 8 <2>;
+ adi,reference-select = /bits/ 8 <0>;
+ adi,excitation-pins = <18>;
+ adi,excitation-current-microamp = <500>;
+ adi,vbias;
+ };
+ };
+ };
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4170";
+ reg = <0>;
+ spi-max-frequency = <20000000>;
+ spi-cpol;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ iovdd-supply = <&iovdd>;
+ interrupt-parent = <&gpio_in>;
+ interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input range
+ // Differential bipolar. If AVSS < 0V, differential true bipolar
+ channel@0 {
+ reg = <0>;
+ bipolar;
+ diff-channels = <0 1>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Sample AIN2 with respect to DGND throughout AVDD/DGND input range
+ // Pseudo-differential unipolar
+ channel@1 {
+ reg = <1>;
+ single-channel = <2>;
+ common-mode-channel = <24>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Sample AIN3 with respect to 2.5V throughout AVDD/AVSS input range
+ // Pseudo-differential bipolar
+ channel@2 {
+ reg = <2>;
+ bipolar;
+ single-channel = <3>;
+ common-mode-channel = <29>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Sample AIN4 with respect to DGND throughout AVDD/AVSS input range
+ // Pseudo-differential bipolar
+ channel@3 {
+ reg = <3>;
+ bipolar;
+ single-channel = <4>;
+ common-mode-channel = <24>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Sample AIN5 with respect to 2.5V throughout AVDD/AVSS input range
+ // Pseudo-differential unipolar (AD4170 datasheet page 46 example)
+ channel@4 {
+ reg = <4>;
+ single-channel = <5>;
+ common-mode-channel = <29>;
+ adi,reference-select = /bits/ 8 <3>;
+ };
+ // Sample AIN6 with respect to 2.5V throughout REFIN+/REFIN- input range
+ // Pseudo-differential bipolar
+ channel@5 {
+ reg = <5>;
+ bipolar;
+ single-channel = <6>;
+ common-mode-channel = <29>;
+ adi,reference-select = /bits/ 8 <0>;
+ };
+ // Weigh scale sensor
+ weighscale@6 {
+ reg = <6>;
+ bipolar;
+ diff-channels = <7 8>;
+ adi,reference-select = /bits/ 8 <0>;
+ adi,sensor-type = /bits/ 8 <0>;
+ adi,excitation-ac;
+ adi,excitation-pins = <17 18 19 20>;
+ };
+ };
+ };
+...
+
diff --git a/MAINTAINERS b/MAINTAINERS
index cc9582b14ced..0a8f2c7a139c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1346,6 +1346,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-ad4130
F: Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml
F: drivers/iio/adc/ad4130.c
+ANALOG DEVICES INC AD4170 DRIVER
+M: Marcelo Schmitt <marcelo.schmitt@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,ad4170.yaml
+
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 02/10] iio: adc: Add basic support for AD4170
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
@ 2025-05-13 12:34 ` Marcelo Schmitt
2025-05-25 10:36 ` Jonathan Cameron
2025-05-26 10:21 ` Nuno Sá
2025-05-13 12:34 ` [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain Marcelo Schmitt
` (7 subsequent siblings)
9 siblings, 2 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:34 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: Ana-Maria Cusco, jic23, lars, Michael.Hennerich, dlechner,
nuno.sa, andy, robh, krzk+dt, conor+dt, linus.walleij, brgl,
marcelo.schmitt1
From: Ana-Maria Cusco <ana-maria.cusco@analog.com>
The AD4170 is a multichannel, low noise, 24-bit precision sigma-delta
analog to digital converter. The AD4170 design offers a flexible data
aquisition solution with crosspoint multiplexed analog inputs, configurable
ADC voltage reference inputs, ultra-low noise integrated PGA, digital
filtering, wide range of configurable output data rates, internal
oscillator and temperature sensor, four GPIOs, and integrated features for
interfacing with load cell weigh scales, RTD, and thermocouple sensors.
Add basic support for the AD4170 ADC with the following features:
- Single-shot read.
- Analog front end PGA configuration.
- Differential and pseudo-differential input configuration.
Signed-off-by: Ana-Maria Cusco <ana-maria.cusco@analog.com>
Co-developed-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Updated Copyright year.
- Separated handling of channel setup cases for better understanding of code flow.
- Now comparing setups field by field instead of using memcmp().
- Disable channel on ad4170_read_sample() error path.
- Reinit completion before entering single conversion mode.
- Organized ad4170_sinc3_filt_fs_tbl.
- Used clamp to simplify configuration value checking.
- Returned earlier whenever possible.
- Used HZ_PER_KHZ/MHZ.
- Declared internal voltage reference constant AD4170_INT_REF_2_5V
- Many other minor code style and readability improvements.
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4170.c | 1553 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1567 insertions(+)
create mode 100644 drivers/iio/adc/ad4170.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 0a8f2c7a139c..541e37ed304e 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,ad4170.yaml
+F: drivers/iio/adc/ad4170.c
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 0fe6601e59ed..594b9f55ec0a 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -70,6 +70,18 @@ config AD4130
To compile this driver as a module, choose M here: the module will be
called ad4130.
+
+config AD4170
+ tristate "Analog Device AD4170 ADC Driver"
+ depends on SPI
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices AD4170 SPI analog
+ to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad4170.
+
config AD4695
tristate "Analog Device AD4695 ADC Driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 07d4b832c42e..d3a1376d1f96 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
obj-$(CONFIG_AD4000) += ad4000.o
obj-$(CONFIG_AD4030) += ad4030.o
obj-$(CONFIG_AD4130) += ad4130.o
+obj-$(CONFIG_AD4170) += ad4170.o
obj-$(CONFIG_AD4695) += ad4695.o
obj-$(CONFIG_AD4851) += ad4851.o
obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
new file mode 100644
index 000000000000..bf19b31095ee
--- /dev/null
+++ b/drivers/iio/adc/ad4170.c
@@ -0,0 +1,1553 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Analog Devices, Inc.
+ * Author: Ana-Maria Cusco <ana-maria.cusco@analog.com>
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+/*
+ * AD4170 registers
+ * Multibyte register addresses point to the most significant byte which is the
+ * address to use to get the most significant byte first (address accessed is
+ * decremented by one for each data byte)
+ *
+ * Each register address define follows the AD4170_<REG_NAME>_REG form.
+ * Each mask follows the AD4170_<REG_NAME>_<FIELD_NAME> form.
+ * E.g. AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK is for accessing DIG_AUX1_CTRL field
+ * of PIN_MUXING_REG.
+ * Each constant follows the AD4170_<REG_NAME>_<FIELD_NAME>_<FUNCTION> form.
+ * E.g. AD4170_PIN_MUXING_DIG_AUX1_DISABLED is the value written to
+ * DIG_AUX1_CTRL field of PIN_MUXING register to disable DIG_AUX1 pin.
+ * Some register names and register field names are shortened versions of
+ * their datasheet counterpart names to provide better code readability.
+ */
+#define AD4170_CONFIG_A_REG 0x00
+#define AD4170_DATA_24B_REG 0x1E
+#define AD4170_PIN_MUXING_REG 0x69
+#define AD4170_ADC_CTRL_REG 0x71
+#define AD4170_CHAN_EN_REG 0x79
+#define AD4170_CHAN_SETUP_REG(x) (0x81 + 4 * (x))
+#define AD4170_CHAN_MAP_REG(x) (0x83 + 4 * (x))
+#define AD4170_MISC_REG(x) (0xC1 + 14 * (x))
+#define AD4170_AFE_REG(x) (0xC3 + 14 * (x))
+#define AD4170_FILTER_REG(x) (0xC5 + 14 * (x))
+#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x))
+#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x))
+#define AD4170_GAIN_REG(x) (0xCD + 14 * (x))
+
+#define AD4170_REG_READ_MASK BIT(14)
+
+/* AD4170_CONFIG_A_REG - INTERFACE_CONFIG_A REGISTER */
+#define AD4170_SW_RESET_MSK (BIT(7) | BIT(0))
+
+/* AD4170_PIN_MUXING_REG */
+#define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK GENMASK(5, 4)
+
+/* AD4170_ADC_CTRL_REG */
+#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7)
+#define AD4170_ADC_CTRL_MODE_MSK GENMASK(3, 0)
+
+/* AD4170_CHAN_EN_REG */
+#define AD4170_CHAN_EN(ch) BIT(ch)
+
+/* AD4170_CHAN_SETUP_REG */
+#define AD4170_CHAN_SETUP_SETUP_MSK GENMASK(2, 0)
+
+/* AD4170_CHAN_MAP_REG */
+#define AD4170_CHAN_MAP_AINP_MSK GENMASK(12, 8)
+#define AD4170_CHAN_MAP_AINM_MSK GENMASK(4, 0)
+
+/* AD4170_AFE_REG */
+#define AD4170_AFE_REF_BUF_M_MSK GENMASK(11, 10)
+#define AD4170_AFE_REF_BUF_P_MSK GENMASK(9, 8)
+#define AD4170_AFE_REF_SELECT_MSK GENMASK(6, 5)
+#define AD4170_AFE_BIPOLAR_MSK BIT(4)
+#define AD4170_AFE_PGA_GAIN_MSK GENMASK(3, 0)
+
+/* AD4170 register constants */
+
+/* AD4170_CHAN_MAP_REG constants */
+#define AD4170_CHAN_MAP_AIN(x) (x)
+#define AD4170_CHAN_MAP_TEMP_SENSOR 17
+#define AD4170_CHAN_MAP_AVDD_AVSS_P 18
+#define AD4170_CHAN_MAP_AVDD_AVSS_N 18
+#define AD4170_CHAN_MAP_IOVDD_DGND_P 19
+#define AD4170_CHAN_MAP_IOVDD_DGND_N 19
+#define AD4170_CHAN_MAP_AVSS 23
+#define AD4170_CHAN_MAP_DGND 24
+#define AD4170_CHAN_MAP_REFIN1_P 25
+#define AD4170_CHAN_MAP_REFIN1_N 26
+#define AD4170_CHAN_MAP_REFIN2_P 27
+#define AD4170_CHAN_MAP_REFIN2_N 28
+#define AD4170_CHAN_MAP_REFOUT 29
+
+/* AD4170_PIN_MUXING_REG constants */
+#define AD4170_PIN_MUXING_DIG_AUX1_DISABLED 0x0
+#define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1
+
+/* AD4170_ADC_CTRL_REG constants */
+#define AD4170_ADC_CTRL_MODE_SINGLE 0x4
+#define AD4170_ADC_CTRL_MODE_IDLE 0x7
+
+/* Device properties and auxiliary constants */
+
+#define AD4170_NUM_ANALOG_PINS 9
+#define AD4170_MAX_CHANNELS 16
+#define AD4170_MAX_ANALOG_PINS 8
+#define AD4170_MAX_SETUPS 8
+#define AD4170_INVALID_SETUP 9
+#define AD4170_SPI_MAX_XFER_LEN 6
+
+#define AD4170_INT_REF_2_5V 2500000
+
+/* Internal and external clock properties */
+#define AD4170_INT_CLOCK_16MHZ (16 * HZ_PER_MHZ)
+
+#define AD4170_NUM_PGA_OPTIONS 10
+
+#define AD4170_GAIN_REG_DEFAULT 0x555555
+
+/* Analog pin functions */
+#define AD4170_PIN_UNASIGNED 0x00
+
+static const unsigned int ad4170_reg_size[] = {
+ [AD4170_CONFIG_A_REG] = 1,
+ [AD4170_DATA_24B_REG] = 3,
+ [AD4170_PIN_MUXING_REG] = 2,
+ [AD4170_ADC_CTRL_REG] = 2,
+ [AD4170_CHAN_EN_REG] = 2,
+ /*
+ * CHANNEL_SETUP and CHANNEL_MAP register are all 2 byte size each and
+ * their addresses are interleaved such that we have CHANNEL_SETUP0
+ * address followed by CHANNEL_MAP0 address, followed by CHANNEL_SETUP1,
+ * and so on until CHANNEL_MAP15.
+ * Thus, initialize the register size for them only once.
+ */
+ [AD4170_CHAN_SETUP_REG(0) ... AD4170_CHAN_MAP_REG(AD4170_MAX_CHANNELS - 1)] = 2,
+ /*
+ * MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN register addresses are
+ * also interleaved but MISC, AFE, FILTER, FILTER_FS, OFFSET are 16-bit
+ * while OFFSET, GAIN are 24-bit registers so we can't init them all to
+ * the same size.
+ */
+ [AD4170_MISC_REG(0) ... AD4170_FILTER_FS_REG(0)] = 2,
+ [AD4170_MISC_REG(1) ... AD4170_FILTER_FS_REG(1)] = 2,
+ [AD4170_MISC_REG(2) ... AD4170_FILTER_FS_REG(2)] = 2,
+ [AD4170_MISC_REG(3) ... AD4170_FILTER_FS_REG(3)] = 2,
+ [AD4170_MISC_REG(4) ... AD4170_FILTER_FS_REG(4)] = 2,
+ [AD4170_MISC_REG(5) ... AD4170_FILTER_FS_REG(5)] = 2,
+ [AD4170_MISC_REG(6) ... AD4170_FILTER_FS_REG(6)] = 2,
+ [AD4170_MISC_REG(7) ... AD4170_FILTER_FS_REG(7)] = 2,
+ [AD4170_OFFSET_REG(0) ... AD4170_GAIN_REG(0)] = 3,
+ [AD4170_OFFSET_REG(1) ... AD4170_GAIN_REG(1)] = 3,
+ [AD4170_OFFSET_REG(2) ... AD4170_GAIN_REG(2)] = 3,
+ [AD4170_OFFSET_REG(3) ... AD4170_GAIN_REG(3)] = 3,
+ [AD4170_OFFSET_REG(4) ... AD4170_GAIN_REG(4)] = 3,
+ [AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3,
+ [AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3,
+ [AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3,
+};
+
+enum ad4170_ref_buf {
+ AD4170_REF_BUF_PRE, /* Pre-charge referrence buffer */
+ AD4170_REF_BUF_FULL, /* Full referrence buffering */
+ AD4170_REF_BUF_BYPASS /* Bypass referrence buffering */
+};
+
+enum ad4170_ref_select {
+ AD4170_REF_REFIN1,
+ AD4170_REF_REFIN2,
+ AD4170_REF_REFOUT,
+ AD4170_REF_AVDD
+};
+
+enum ad4170_regulator {
+ AD4170_AVDD_SUP,
+ AD4170_AVSS_SUP,
+ AD4170_IOVDD_SUP,
+ AD4170_REFIN1P_SUP,
+ AD4170_REFIN1N_SUP,
+ AD4170_REFIN2P_SUP,
+ AD4170_REFIN2N_SUP,
+ AD4170_MAX_SUP
+};
+
+enum ad4170_int_pin_sel {
+ AD4170_INT_PIN_SDO,
+ AD4170_INT_PIN_DIG_AUX1,
+};
+
+static const char * const ad4170_int_pin_names[] = {
+ [AD4170_INT_PIN_SDO] = "sdo",
+ [AD4170_INT_PIN_DIG_AUX1] = "dig_aux1",
+};
+
+struct ad4170_chip_info {
+ const char *name;
+};
+
+static const struct ad4170_chip_info ad4170_chip_info = {
+ .name = "ad4170",
+};
+
+static const struct ad4170_chip_info ad4190_chip_info = {
+ .name = "ad4190",
+};
+
+static const struct ad4170_chip_info ad4195_chip_info = {
+ .name = "ad4195",
+};
+
+/*
+ * There are 8 of each MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN
+ * configuration registers. That is, there are 8 miscellaneous registers, MISC0
+ * to MISC7. Each MISC register is associated with a setup; MISCN is associated
+ * with setup number N. The other 5 above mentioned types of registers have
+ * analogous structure. A setup is a set of those registers. For example,
+ * setup 1 comprises of MISC1, AFE1, FILTER1, FILTER_FS1, OFFSET1, and GAIN1
+ * registers. Also, there are 16 CHANNEL_SETUP registers (CHANNEL_SETUP0 to
+ * CHANNEL_SETUP15). Each channel setup is associated with one of the 8 possible
+ * setups. Thus, AD4170 can support up to 16 channels but, since there are only
+ * 8 available setups, channels must share settings if more than 8 channels are
+ * configured.
+ *
+ * If this struct is modified, ad4170_setup_eq() will probably need to be
+ * updated too.
+ */
+struct ad4170_setup {
+ u16 misc;
+ u16 afe;
+ u16 filter;
+ u16 filter_fs;
+ u32 offset; /* For calibration purposes */
+ u32 gain; /* For calibration purposes */
+};
+
+struct ad4170_setup_info {
+ struct ad4170_setup setup;
+ unsigned int enabled_channels;
+ unsigned int channels;
+};
+
+struct ad4170_chan_info {
+ unsigned int input_range_uv;
+ unsigned int setup_num; /* Index to access state setup_infos array */
+ struct ad4170_setup setup; /* cached setup */
+ int offset_tbl[10];
+ u32 scale_tbl[10][2];
+ bool initialized;
+ bool enabled;
+};
+
+struct ad4170_state {
+ struct mutex lock; /* Protect read-modify-write and multi write sequences */
+ int vrefs_uv[AD4170_MAX_SUP];
+ u32 mclk_hz;
+ struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS];
+ struct iio_chan_spec chans[AD4170_MAX_CHANNELS];
+ struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS];
+ struct spi_device *spi;
+ struct regmap *regmap;
+ int pins_fn[AD4170_NUM_ANALOG_PINS];
+ u32 int_pin_sel;
+ struct completion completion;
+ /*
+ * DMA (thus cache coherency maintenance) requires the transfer buffers
+ * to live in their own cache lines.
+ */
+ u8 tx_buf[AD4170_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN);
+ u8 rx_buf[4];
+};
+
+static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+ else
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int ad4170_get_reg_size(struct ad4170_state *st, unsigned int reg,
+ unsigned int *size)
+{
+ if (reg >= ARRAY_SIZE(ad4170_reg_size))
+ return -EINVAL;
+
+ *size = ad4170_reg_size[reg];
+
+ return 0;
+}
+
+static int ad4170_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct ad4170_state *st = context;
+ unsigned int size;
+ int ret;
+
+ ret = ad4170_get_reg_size(st, reg, &size);
+ if (ret)
+ return ret;
+
+ put_unaligned_be16(reg, st->tx_buf);
+ switch (size) {
+ case 3:
+ put_unaligned_be24(val, &st->tx_buf[2]);
+ break;
+ case 2:
+ put_unaligned_be16(val, &st->tx_buf[2]);
+ break;
+ case 1:
+ st->tx_buf[2] = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return spi_write(st->spi, st->tx_buf, size + 2);
+}
+
+static int ad4170_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct ad4170_state *st = context;
+ struct spi_transfer t[] = {
+ {
+ .tx_buf = st->tx_buf,
+ .len = 2,
+ },
+ {
+ .rx_buf = st->rx_buf,
+ },
+ };
+ unsigned int size;
+ int ret;
+
+ ret = ad4170_get_reg_size(st, reg, &size);
+ if (ret)
+ return ret;
+
+ put_unaligned_be16(AD4170_REG_READ_MASK | reg, st->tx_buf);
+ t[1].len = size;
+
+ ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
+ if (ret)
+ return ret;
+
+ switch (size) {
+ case 3:
+ *val = get_unaligned_be24(st->rx_buf);
+ return 0;
+ case 2:
+ *val = get_unaligned_be16(st->rx_buf);
+ return 0;
+ case 1:
+ *val = st->rx_buf[0];
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct regmap_config ad4170_regmap_config = {
+ .reg_read = ad4170_reg_read,
+ .reg_write = ad4170_reg_write,
+};
+
+static bool ad4170_setup_eq(struct ad4170_setup *a, struct ad4170_setup *b)
+{
+ /*
+ * The use of static_assert() here is to make sure that the comparison
+ * is adapted whenever struct ad4170_setup is changed.
+ */
+ static_assert(sizeof(*a) ==
+ sizeof(struct {
+ u16 misc;
+ u16 afe;
+ u16 filter;
+ u16 filter_fs;
+ u32 offset;
+ u32 gain;
+ }));
+
+ if (a->misc != b->misc ||
+ a->afe != b->afe ||
+ a->filter != b->filter ||
+ a->filter_fs != b->filter_fs ||
+ a->offset != b->offset ||
+ a->gain != b->gain)
+ return false;
+
+ return true;
+}
+
+static int ad4170_find_setup(struct ad4170_state *st,
+ struct ad4170_setup *target_setup,
+ unsigned int *setup_num, bool *overwrite)
+{
+ unsigned int i;
+
+ *setup_num = AD4170_INVALID_SETUP;
+ *overwrite = false;
+
+ for (i = 0; i < AD4170_MAX_SETUPS; i++) {
+ struct ad4170_setup_info *setup_info = &st->setup_infos[i];
+
+ /* Immediately accept a matching setup. */
+ if (ad4170_setup_eq(target_setup, &setup_info->setup)) {
+ *setup_num = i;
+ return 0;
+ }
+
+ /* Ignore all setups which are used by enabled channels. */
+ if (setup_info->enabled_channels)
+ continue;
+
+ /* Find the least used slot. */
+ if (*setup_num == AD4170_INVALID_SETUP ||
+ setup_info->channels < st->setup_infos[*setup_num].channels)
+ *setup_num = i;
+ }
+
+ if (*setup_num == AD4170_INVALID_SETUP)
+ return -EINVAL;
+
+ *overwrite = true;
+ return 0;
+}
+
+static void ad4170_unlink_channel(struct ad4170_state *st, unsigned int channel)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[channel];
+ struct ad4170_setup_info *setup_info = &st->setup_infos[chan_info->setup_num];
+
+ chan_info->setup_num = AD4170_INVALID_SETUP;
+ setup_info->channels--;
+}
+
+static int ad4170_unlink_setup(struct ad4170_state *st, unsigned int setup_num)
+{
+ unsigned int i;
+
+ for (i = 0; i < AD4170_MAX_CHANNELS; i++) {
+ struct ad4170_chan_info *chan_info = &st->chan_infos[i];
+
+ if (!chan_info->initialized || chan_info->setup_num != setup_num)
+ continue;
+
+ ad4170_unlink_channel(st, i);
+ }
+ return 0;
+}
+
+static int ad4170_link_channel_setup(struct ad4170_state *st,
+ unsigned int chan_addr,
+ unsigned int setup_num)
+{
+ struct ad4170_setup_info *setup_info = &st->setup_infos[setup_num];
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr];
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, AD4170_CHAN_SETUP_REG(chan_addr),
+ AD4170_CHAN_SETUP_SETUP_MSK,
+ FIELD_PREP(AD4170_CHAN_SETUP_SETUP_MSK, setup_num));
+ if (ret)
+ return ret;
+
+ chan_info->setup_num = setup_num;
+ setup_info->channels++;
+ return 0;
+}
+
+/*
+ * Sets the ADC operating mode. Supported modes are
+ * - Single conversion mode
+ * - Idle mode
+ */
+static int ad4170_set_mode(struct ad4170_state *st, unsigned int mode)
+{
+ return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+ AD4170_ADC_CTRL_MODE_MSK,
+ FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, mode));
+}
+
+static int ad4170_write_setup(struct ad4170_state *st, unsigned int setup_num,
+ struct ad4170_setup *setup)
+{
+ int ret;
+
+ /*
+ * It is recommended to place the ADC in standby mode or idle mode to
+ * write to OFFSET and GAIN registers.
+ */
+ ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_MISC_REG(setup_num), setup->misc);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_AFE_REG(setup_num), setup->afe);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_FILTER_REG(setup_num),
+ setup->filter);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_FILTER_FS_REG(setup_num),
+ setup->filter_fs);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_OFFSET_REG(setup_num),
+ setup->offset);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4170_GAIN_REG(setup_num), setup->gain);
+ if (ret)
+ return ret;
+
+ memcpy(&st->setup_infos[setup_num].setup, setup, sizeof(*setup));
+ return 0;
+}
+
+static int ad4170_write_channel_setup(struct ad4170_state *st,
+ unsigned int chan_addr, bool on_enable)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr];
+ bool overwrite;
+ int setup_num;
+ int ret;
+
+ /*
+ * Similar to AD4130 driver, the following cases need to be handled.
+ *
+ * 1. Enabled and linked channel with setup changes:
+ * - Find a setup. If not possible, return error.
+ * - Unlink channel from current setup.
+ * - If the setup found has only disabled channels linked to it,
+ * unlink all channels, and write the new setup to it.
+ * - Link channel to new setup.
+ *
+ * 2. Soon to be enabled and unlinked channel:
+ * - Find a setup. If not possible, return error.
+ * - If the setup found has only disabled channels linked to it,
+ * unlink all channels, and write the new setup to it.
+ * - Link channel to the setup.
+ *
+ * 3. Disabled and linked channel with setup changes:
+ * - Unlink channel from current setup.
+ *
+ * 4. Soon to be enabled and linked channel:
+ * 5. Disabled and unlinked channel with setup changes:
+ * - Do nothing.
+ */
+
+ /* Cases 3, 4, and 5 */
+ if (chan_info->setup_num != AD4170_INVALID_SETUP) {
+ /* Case 4 */
+ if (on_enable)
+ return 0;
+
+ /* Case 3 */
+ if (!chan_info->enabled) {
+ ad4170_unlink_channel(st, chan_addr);
+ return 0;
+ }
+ } else if (!on_enable && !chan_info->enabled) {
+ /* Case 5 */
+ return 0;
+ }
+
+ /* Cases 1 & 2 */
+ ret = ad4170_find_setup(st, &chan_info->setup, &setup_num, &overwrite);
+ if (ret)
+ return ret;
+
+ if (chan_info->setup_num != AD4170_INVALID_SETUP)
+ /* Case 1 */
+ ad4170_unlink_channel(st, chan_addr);
+
+ if (overwrite) {
+ ret = ad4170_unlink_setup(st, setup_num);
+ if (ret)
+ return ret;
+
+ ret = ad4170_write_setup(st, setup_num, &chan_info->setup);
+ if (ret)
+ return ret;
+ }
+
+ return ad4170_link_channel_setup(st, chan_addr, setup_num);
+}
+
+static int ad4170_set_channel_enable(struct ad4170_state *st,
+ unsigned int chan_addr, bool status)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr];
+ struct ad4170_setup_info *setup_info;
+ int ret;
+
+ if (chan_info->enabled == status)
+ return 0;
+
+ if (status) {
+ ret = ad4170_write_channel_setup(st, chan_addr, true);
+ if (ret)
+ return ret;
+ }
+
+ setup_info = &st->setup_infos[chan_info->setup_num];
+
+ ret = regmap_update_bits(st->regmap, AD4170_CHAN_EN_REG,
+ AD4170_CHAN_EN(chan_addr),
+ status ? AD4170_CHAN_EN(chan_addr) : 0);
+ if (ret)
+ return ret;
+
+ setup_info->enabled_channels += status ? 1 : -1;
+ chan_info->enabled = status;
+ return 0;
+}
+
+static const struct iio_chan_spec ad4170_channel_template = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .differential = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ .scan_type = {
+ .realbits = 24,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+};
+
+/*
+ * Receives the number of a multiplexed AD4170 input (ain_n), and stores the
+ * voltage (in µV) of the specified input into ain_voltage. If the input number
+ * is a ordinary analog input (AIN0 to AIN8), stores zero into ain_voltage.
+ * If a voltage regulator required by a special input is unavailable, return
+ * error code. Return 0 on success.
+ */
+static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n,
+ int *ain_voltage)
+{
+ struct device *dev = &st->spi->dev;
+ int v_diff;
+
+ *ain_voltage = 0;
+ if (ain_n <= AD4170_CHAN_MAP_TEMP_SENSOR)
+ return 0;
+
+ switch (ain_n) {
+ case AD4170_CHAN_MAP_AVDD_AVSS_N:
+ v_diff = st->vrefs_uv[AD4170_AVDD_SUP] - st->vrefs_uv[AD4170_AVSS_SUP];
+ *ain_voltage = v_diff / 5;
+ return 0;
+ case AD4170_CHAN_MAP_IOVDD_DGND_N:
+ *ain_voltage = st->vrefs_uv[AD4170_IOVDD_SUP] / 5;
+ return 0;
+ case AD4170_CHAN_MAP_AVSS:
+ *ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP];
+ return 0;
+ case AD4170_CHAN_MAP_DGND:
+ *ain_voltage = 0;
+ return 0;
+ case AD4170_CHAN_MAP_REFIN1_P:
+ if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "input set to REFIN+ but ref not provided\n");
+
+ *ain_voltage = st->vrefs_uv[AD4170_REFIN1P_SUP];
+ return 0;
+ case AD4170_CHAN_MAP_REFIN1_N:
+ if (st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "input set to REFIN- but ref not provided\n");
+
+ *ain_voltage = st->vrefs_uv[AD4170_REFIN1N_SUP];
+ return 0;
+ case AD4170_CHAN_MAP_REFIN2_P:
+ if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "input set to REFIN2+ but ref not provided\n");
+
+ *ain_voltage = st->vrefs_uv[AD4170_REFIN2P_SUP];
+ return 0;
+ case AD4170_CHAN_MAP_REFIN2_N:
+ if (st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "input set to REFIN2- but ref not provided\n");
+
+ *ain_voltage = st->vrefs_uv[AD4170_REFIN2N_SUP];
+ return 0;
+ case AD4170_CHAN_MAP_REFOUT:
+ /* REFOUT is 2.5V relative to AVSS so take that into account */
+ *ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool com)
+{
+ /* Check common-mode input pin is mapped to a special input. */
+ if (com && (pin < AD4170_CHAN_MAP_AVDD_AVSS_P || pin > AD4170_CHAN_MAP_REFOUT))
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Invalid common-mode input pin number. %d\n",
+ pin);
+
+ /* Check differential input pin is mapped to a analog input pin. */
+ if (!com && pin > AD4170_MAX_ANALOG_PINS)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Invalid analog input pin number. %d\n",
+ pin);
+
+ return 0;
+}
+
+/*
+ * Verifies whether the channel input configuration is valid by checking the
+ * input numbers.
+ * Returns 0 on valid channel input configuration. -EINVAL otherwise.
+ */
+static int ad4170_validate_channel(struct ad4170_state *st,
+ struct iio_chan_spec const *chan)
+{
+ int ret;
+
+ ret = ad4170_validate_channel_input(st, chan->channel, false);
+ if (ret)
+ return ret;
+
+ return ad4170_validate_channel_input(st, chan->channel2,
+ !chan->differential);
+}
+
+/*
+ * Verifies whether the channel configuration is valid by checking the provided
+ * input type, polarity, and voltage references result in a sane input range.
+ * Returns negative error code on failure.
+ */
+static int ad4170_get_input_range(struct ad4170_state *st,
+ struct iio_chan_spec const *chan,
+ unsigned int ch_reg, unsigned int ref_sel)
+{
+ bool bipolar = chan->scan_type.sign == 's';
+ struct device *dev = &st->spi->dev;
+ int refp, refn, ain_voltage, ret;
+
+ switch (ref_sel) {
+ case AD4170_REF_REFIN1:
+ if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV ||
+ st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "REFIN± selected but not provided\n");
+
+ refp = st->vrefs_uv[AD4170_REFIN1P_SUP];
+ refn = st->vrefs_uv[AD4170_REFIN1N_SUP];
+ break;
+ case AD4170_REF_REFIN2:
+ if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV ||
+ st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV)
+ return dev_err_probe(dev, -ENODEV,
+ "REFIN2± selected but not provided\n");
+
+ refp = st->vrefs_uv[AD4170_REFIN2P_SUP];
+ refn = st->vrefs_uv[AD4170_REFIN2N_SUP];
+ break;
+ case AD4170_REF_AVDD:
+ refp = st->vrefs_uv[AD4170_AVDD_SUP];
+ refn = st->vrefs_uv[AD4170_AVSS_SUP];
+ break;
+ case AD4170_REF_REFOUT:
+ /* REFOUT is 2.5 V relative to AVSS */
+ refp = st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V;
+ refn = st->vrefs_uv[AD4170_AVSS_SUP];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Find out the analog input range from the channel type, polarity, and
+ * voltage reference selection.
+ * AD4170 channels are either differential or pseudo-differential.
+ * Diff input voltage range: −VREF/gain to +VREF/gain (datasheet page 6)
+ * Pseudo-diff input voltage range: 0 to VREF/gain (datasheet page 6)
+ */
+ if (chan->differential) {
+ if (!bipolar)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Channel %u differential unipolar\n",
+ ch_reg);
+
+ /*
+ * Differential bipolar channel.
+ * avss-supply is never above 0V.
+ * Assuming refin1n-supply not above 0V.
+ * Assuming refin2n-supply not above 0V.
+ */
+ return refp + abs(refn);
+ }
+ /*
+ * Some configurations can lead to invalid setups.
+ * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS),
+ * and pseudo-diff channel configuration set, then the input range
+ * should go from 0V to +VREF (single-ended - datasheet pg 10), but
+ * REFOUT/AVSS range would be -2.5V to 0V.
+ * Check the positive reference is higher than 0V for pseudo-diff
+ * channels.
+ */
+ if (refp <= 0)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "REF+ <= GND for pseudo-diff chan %u\n",
+ ch_reg);
+
+ if (bipolar)
+ return refp;
+
+ /*
+ * Pseudo-differential unipolar channel.
+ * Input expected to swing from IN- to +VREF.
+ */
+ ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ain_voltage);
+ if (ret)
+ return ret;
+
+ if (refp - ain_voltage <= 0)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Negative input >= REF+ for pseudo-diff chan %u\n",
+ ch_reg);
+
+ return refp - ain_voltage;
+}
+
+static int __ad4170_read_sample(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned long settling_time_ms;
+ int ret;
+
+ reinit_completion(&st->completion);
+ ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_SINGLE);
+ if (ret)
+ return ret;
+
+ /*
+ * When a channel is manually selected by the user, the ADC needs an
+ * extra time to provide the first stable conversion. The ADC settling
+ * time depends on the filter type, filter frequency, and ADC clock
+ * frequency (see datasheet page 53). The maximum settling time among
+ * all filter configurations is 6291164 / fCLK. Use that formula to wait
+ * for sufficient time whatever the filter configuration may be.
+ */
+ settling_time_ms = DIV_ROUND_UP(6291164 * MILLI, st->mclk_hz);
+ ret = wait_for_completion_timeout(&st->completion,
+ msecs_to_jiffies(settling_time_ms));
+ if (!ret)
+ dev_dbg(&st->spi->dev,
+ "No Data Ready signal. Reading after delay.\n");
+
+ ret = regmap_read(st->regmap, AD4170_DATA_24B_REG, val);
+ if (ret)
+ return ret;
+
+ if (chan->scan_type.sign == 's')
+ *val = sign_extend32(*val, chan->scan_type.realbits - 1);
+
+ return 0;
+}
+
+static int ad4170_read_sample(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int ret, ret2;
+
+ guard(mutex)(&st->lock);
+ /*
+ * The ADC sequences through all enabled channels. That can lead to
+ * incorrect channel being sampled if a previous read would have left a
+ * different channel enabled. Thus, always enable and disable the
+ * channel on single-shot read.
+ */
+ ret = ad4170_set_channel_enable(st, chan->address, true);
+ if (ret)
+ return ret;
+
+ ret = __ad4170_read_sample(indio_dev, chan, val);
+ if (ret) {
+ dev_err(dev, "failed to read sample: %d\n", ret);
+
+ ret2 = ad4170_set_channel_enable(st, chan->address, false);
+ if (ret2)
+ dev_err(dev, "failed to disable channel: %d\n", ret2);
+
+ return ret;
+ }
+
+ ret = ad4170_set_channel_enable(st, chan->address, false);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int ad4170_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ unsigned int pga;
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = ad4170_read_sample(indio_dev, chan, val);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+ *val = chan_info->scale_tbl[pga][0];
+ *val2 = chan_info->scale_tbl[pga][1];
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_OFFSET:
+ pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+ *val = chan_info->offset_tbl[pga];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct device *dev = &st->spi->dev;
+ int bipolar = chan->scan_type.sign == 's' ? 1 : 0;
+ int precision_bits = chan->scan_type.realbits;
+ int pga, ainm_voltage, ret;
+ unsigned long long offset;
+
+ ainm_voltage = 0;
+ ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ainm_voltage);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to fill scale table\n");
+
+ for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) {
+ u64 nv;
+ unsigned int lshift, rshift;
+
+ /*
+ * The PGA options are numbered from 0 to 9, with option 0 being
+ * a gain of 2^0 (no actual gain), and 7 meaning a gain of 2^7.
+ * Option 8, though, sets a gain of 0.5, so the input signal can
+ * be attenuated by 2 rather than amplified. Option 9, allows
+ * the signal to bypass the PGA circuitry (no gain).
+ *
+ * The scale factor to get ADC output codes to values in mV
+ * units is given by:
+ * _scale = (input_range / gain) / 2^precision
+ * AD4170 gain is a power of 2 so the above can be written as
+ * _scale = input_range / 2^(precision + gain)
+ * Keep the input range in µV to avoid truncating the less
+ * significan bits when right shifting it so to preserve scale
+ * precision.
+ */
+ nv = (u64)chan_info->input_range_uv * NANO;
+ lshift = !!(pga & BIT(3)); /* handle options 8 and 9 */
+ rshift = precision_bits - bipolar + (pga & GENMASK(2, 0)) - lshift;
+ chan_info->scale_tbl[pga][0] = 0;
+ chan_info->scale_tbl[pga][1] = div_u64(nv >> rshift, MILLI);
+
+ /*
+ * If the negative input is not at GND, the conversion result
+ * (which is relative to IN-) will be offset by the level at IN-.
+ * Use the scale factor the other way around to go from a known
+ * voltage to the corresponding ADC output code.
+ * With that, we are able to get to what would be the output
+ * code for the voltage at the negative input.
+ * If the negative input is not fixed, there is no offset.
+ */
+ offset = ((unsigned long long)abs(ainm_voltage)) * MICRO;
+ offset = DIV_ROUND_CLOSEST_ULL(offset, chan_info->scale_tbl[pga][1]);
+
+ /*
+ * After divided by the scale, offset will always fit into 31
+ * bits. For _raw + _offset to be relative to GND, the value
+ * provided as _offset is of opposite sign than the real offset.
+ */
+ if (ainm_voltage > 0)
+ chan_info->offset_tbl[pga] = -(int)(offset);
+ else
+ chan_info->offset_tbl[pga] = (int)(offset);
+ }
+ return 0;
+}
+
+static int ad4170_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long info)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (int *)chan_info->scale_tbl;
+ *length = ARRAY_SIZE(chan_info->scale_tbl) * 2;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4170_set_pga(struct ad4170_state *st,
+ struct iio_chan_spec const *chan, int val, int val2)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ unsigned int old_pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+ unsigned int pga;
+ int ret;
+
+ for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) {
+ if (val == chan_info->scale_tbl[pga][0] &&
+ val2 == chan_info->scale_tbl[pga][1])
+ break;
+ }
+
+ if (pga == AD4170_NUM_PGA_OPTIONS)
+ return -EINVAL;
+
+ if (pga == old_pga)
+ return 0;
+
+ guard(mutex)(&st->lock);
+ setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+ setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, pga);
+
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret) {
+ setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+ setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, old_pga);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __ad4170_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long info)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ return ad4170_set_pga(st, chan, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4170_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long info)
+{
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = __ad4170_write_raw(indio_dev, chan, val, val2, info);
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ad4170_info = {
+ .read_raw = ad4170_read_raw,
+ .read_avail = ad4170_read_avail,
+ .write_raw = ad4170_write_raw,
+ .write_raw_get_fmt = ad4170_write_raw_get_fmt,
+ .debugfs_reg_access = ad4170_debugfs_reg_access,
+};
+
+static int ad4170_soft_reset(struct ad4170_state *st)
+{
+ int ret;
+
+ ret = regmap_write(st->regmap, AD4170_CONFIG_A_REG,
+ AD4170_SW_RESET_MSK);
+ if (ret)
+ return ret;
+
+ /* AD4170-4 requires 1 ms between reset and any register access. */
+ fsleep(MILLI);
+
+ return 0;
+}
+
+static int ad4170_parse_reference(struct ad4170_state *st,
+ struct fwnode_handle *child,
+ struct ad4170_setup *setup)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+ u8 aux;
+
+ /* Optional positive reference buffering, if omitted we use the default */
+ aux = AD4170_REF_BUF_FULL; /* Default to full precharge buffer enabled. */
+ ret = fwnode_property_read_u8(child, "adi,buffered-positive", &aux);
+ if (!ret) {
+ if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid adi,buffered-positive: %u\n",
+ aux);
+ }
+ setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, aux);
+
+ /* Optional negative reference buffering, if omitted we use the default */
+ aux = AD4170_REF_BUF_FULL; /* Default to full precharge buffer enabled. */
+ ret = fwnode_property_read_u8(child, "adi,buffered-negative", &aux);
+ if (!ret) {
+ if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid adi,buffered-negative: %u\n",
+ aux);
+ }
+ setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_M_MSK, aux);
+
+ /* Optional voltage reference selection, if omitted we use the default */
+ aux = AD4170_REF_REFOUT; /* Default reference selection. */
+ ret = fwnode_property_read_u8(child, "adi,reference-select", &aux);
+ if (!ret) {
+ if (aux > AD4170_REF_AVDD)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid reference selected %u\n",
+ aux);
+ }
+ setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, aux);
+
+ return 0;
+}
+
+static int ad4170_parse_adc_channel_type(struct device *dev,
+ struct fwnode_handle *child,
+ struct iio_chan_spec *chan)
+{
+ u32 pins[2];
+ int ret, ret2;
+
+ /* Parse pseudo-differential channel configuration */
+ ret = fwnode_property_read_u32(child, "single-channel", &pins[0]);
+ ret2 = fwnode_property_read_u32(child, "common-mode-channel", &pins[1]);
+ if (!ret && ret2)
+ return dev_err_probe(dev, ret,
+ "single-ended channels must define common-mode-channel\n");
+ if (!ret) {
+ chan->differential = false;
+ chan->channel = pins[0];
+ chan->channel2 = pins[1];
+ return 0;
+ }
+
+ /* Parse differential channel configuration */
+ ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
+ ARRAY_SIZE(pins));
+ if (!ret) {
+ chan->differential = true;
+ chan->channel = pins[0];
+ chan->channel2 = pins[1];
+ return 0;
+ }
+ return dev_err_probe(dev, ret,
+ "Channel must define one of diff-channels or single-channel.\n");
+}
+
+static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
+ struct fwnode_handle *child,
+ unsigned int chan_num)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct ad4170_chan_info *chan_info;
+ struct ad4170_setup *setup;
+ struct iio_chan_spec *chan;
+ unsigned int ch_reg;
+ u8 ref_select;
+ bool bipolar;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "reg", &ch_reg);
+ if (ret)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to read channel reg\n");
+
+ if (ch_reg >= AD4170_MAX_CHANNELS)
+ return dev_err_probe(dev, -EINVAL,
+ "Channel idx greater than no of channels\n");
+
+ chan = &st->chans[chan_num];
+ *chan = ad4170_channel_template;
+
+ chan->address = ch_reg;
+ chan->scan_index = ch_reg;
+ chan_info = &st->chan_infos[chan->address];
+
+ chan_info->setup_num = AD4170_INVALID_SETUP;
+ chan_info->initialized = true;
+
+ setup = &chan_info->setup;
+ ret = ad4170_parse_reference(st, child, setup);
+ if (ret)
+ return ret;
+
+ ret = ad4170_parse_adc_channel_type(dev, child, chan);
+ if (ret < 0)
+ return ret;
+
+ bipolar = fwnode_property_read_bool(child, "bipolar");
+ setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar);
+ if (bipolar)
+ chan->scan_type.sign = 's';
+ else
+ chan->scan_type.sign = 'u';
+
+ ref_select = FIELD_GET(AD4170_AFE_REF_SELECT_MSK, setup->afe);
+ ret = ad4170_validate_channel(st, chan);
+ if (ret < 0)
+ return ret;
+
+ ret = ad4170_get_input_range(st, chan, ch_reg, ref_select);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Invalid input config\n");
+
+ chan_info->input_range_uv = ret;
+ return 0;
+}
+
+static int ad4170_parse_channels(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ unsigned int num_channels;
+ unsigned int chan_num = 0;
+ int ret;
+
+ num_channels = device_get_child_node_count(dev);
+
+ if (num_channels > AD4170_MAX_CHANNELS)
+ return dev_err_probe(dev, -EINVAL, "Too many channels\n");
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = ad4170_parse_channel_node(indio_dev, child, chan_num++);
+ if (ret)
+ return ret;
+ }
+
+ indio_dev->num_channels = num_channels;
+ indio_dev->channels = st->chans;
+ return 0;
+}
+
+static int ad4170_parse_firmware(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int reg_data, ret;
+ unsigned int i;
+
+ st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
+
+ for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
+ st->pins_fn[i] = AD4170_PIN_UNASIGNED;
+
+ /* On power on, device defaults to using SDO pin for data ready signal */
+ st->int_pin_sel = AD4170_INT_PIN_SDO;
+ ret = device_property_match_property_string(dev, "interrupt-names",
+ ad4170_int_pin_names,
+ ARRAY_SIZE(ad4170_int_pin_names));
+ if (ret >= 0)
+ st->int_pin_sel = ret;
+
+ reg_data = FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK,
+ st->int_pin_sel == AD4170_INT_PIN_DIG_AUX1 ?
+ AD4170_PIN_MUXING_DIG_AUX1_RDY :
+ AD4170_PIN_MUXING_DIG_AUX1_DISABLED);
+
+ ret = regmap_update_bits(st->regmap, AD4170_PIN_MUXING_REG,
+ AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, reg_data);
+ if (ret)
+ return ret;
+
+ return ad4170_parse_channels(indio_dev);
+}
+
+static int ad4170_initial_config(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ unsigned int i;
+ int ret;
+
+ ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set ADC mode to idle\n");
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ struct ad4170_chan_info *chan_info;
+ struct iio_chan_spec const *chan;
+ struct ad4170_setup *setup;
+ unsigned int val;
+
+ chan = &indio_dev->channels[i];
+ chan_info = &st->chan_infos[chan->address];
+
+ setup = &chan_info->setup;
+ setup->gain = AD4170_GAIN_REG_DEFAULT;
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to write channel setup\n");
+
+ val = FIELD_PREP(AD4170_CHAN_MAP_AINP_MSK, chan->channel) |
+ FIELD_PREP(AD4170_CHAN_MAP_AINM_MSK, chan->channel2);
+
+ ret = regmap_write(st->regmap, AD4170_CHAN_MAP_REG(i), val);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to write CHAN_MAP_REG\n");
+
+ ret = ad4170_fill_scale_tbl(indio_dev, chan);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to fill scale tbl\n");
+ }
+
+ /* Disable all channels to avoid reading from unexpected channel */
+ ret = regmap_write(st->regmap, AD4170_CHAN_EN_REG, 0);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to disable channels\n");
+
+ /*
+ * Configure channels to share the same data output register, i.e. data
+ * can be read from the same register address regardless of channel
+ * number.
+ */
+ return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+ AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK,
+ AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK);
+}
+
+static irqreturn_t ad4170_irq_handler(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct ad4170_state *st = iio_priv(indio_dev);
+
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+};
+
+static int ad4170_regulator_setup(struct ad4170_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ /* Required regulators */
+ ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get AVDD voltage.\n");
+
+ st->vrefs_uv[AD4170_AVDD_SUP] = ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "iovdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get IOVDD voltage.\n");
+
+ st->vrefs_uv[AD4170_IOVDD_SUP] = ret;
+
+ /* Optional regulators */
+ ret = devm_regulator_get_enable_read_voltage(dev, "avss");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "Failed to get AVSS voltage.\n");
+
+ /* Assume AVSS at GND (0V) if not provided */
+ st->vrefs_uv[AD4170_AVSS_SUP] = ret == -ENODEV ? 0 : -ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "refin1p");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "Failed to get REFIN+ voltage.\n");
+
+ st->vrefs_uv[AD4170_REFIN1P_SUP] = ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "refin1n");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "Failed to get REFIN- voltage.\n");
+
+ /* Negative supplies are assumed to provide negative voltage */
+ st->vrefs_uv[AD4170_REFIN1N_SUP] = ret == -ENODEV ? -ENODEV : -ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "refin2p");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "Failed to get REFIN2+ voltage.\n");
+
+ st->vrefs_uv[AD4170_REFIN2P_SUP] = ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "refin2n");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "Failed to get REFIN2- voltage.\n");
+
+ /* Negative supplies are assumed to provide negative voltage */
+ st->vrefs_uv[AD4170_REFIN2N_SUP] = ret == -ENODEV ? -ENODEV : -ret;
+
+ return 0;
+}
+
+static int ad4170_probe(struct spi_device *spi)
+{
+ const struct ad4170_chip_info *chip;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad4170_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ chip = spi_get_device_match_data(spi);
+ if (!chip)
+ return -EINVAL;
+
+ indio_dev->name = chip->name;
+ indio_dev->info = &ad4170_info;
+
+ st->spi = spi;
+
+ st->regmap = devm_regmap_init(dev, NULL, st, &ad4170_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ ret = ad4170_regulator_setup(st);
+ if (ret)
+ return ret;
+
+ ret = ad4170_soft_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ad4170_parse_firmware(indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to parse firmware\n");
+
+ ret = ad4170_initial_config(indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to setup device\n");
+
+ init_completion(&st->completion);
+
+ if (spi->irq) {
+ ret = devm_request_irq(&st->spi->dev, st->spi->irq,
+ &ad4170_irq_handler, IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id ad4170_id_table[] = {
+ { "ad4170", (kernel_ulong_t)&ad4170_chip_info },
+ { "ad4190", (kernel_ulong_t)&ad4190_chip_info },
+ { "ad4195", (kernel_ulong_t)&ad4195_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad4170_id_table);
+
+static const struct of_device_id ad4170_of_match[] = {
+ { .compatible = "adi,ad4170", .data = &ad4170_chip_info },
+ { .compatible = "adi,ad4190", .data = &ad4190_chip_info },
+ { .compatible = "adi,ad4195", .data = &ad4195_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad4170_of_match);
+
+static struct spi_driver ad4170_driver = {
+ .driver = {
+ .name = "ad4170",
+ .of_match_table = ad4170_of_match,
+ },
+ .probe = ad4170_probe,
+ .id_table = ad4170_id_table,
+};
+module_spi_driver(ad4170_driver);
+
+MODULE_AUTHOR("Ana-Maria Cusco <ana-maria.cusco@analog.com>");
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4170 SPI driver");
+MODULE_LICENSE("GPL");
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
2025-05-13 12:34 ` [PATCH v3 02/10] iio: adc: Add basic support for AD4170 Marcelo Schmitt
@ 2025-05-13 12:34 ` Marcelo Schmitt
2025-05-26 10:24 ` Nuno Sá
2025-05-13 12:34 ` [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias Marcelo Schmitt
` (6 subsequent siblings)
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:34 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Add support for ADC calibration gain configuration.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- New patch spun out of the base driver patch.
drivers/iio/adc/ad4170.c | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index bf19b31095ee..1df214f7fdec 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -642,7 +642,8 @@ static const struct iio_chan_spec ad4170_channel_template = {
.differential = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
- BIT(IIO_CHAN_INFO_OFFSET),
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
.scan_type = {
.realbits = 24,
@@ -953,6 +954,9 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
*val = chan_info->offset_tbl[pga];
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ *val = setup->gain;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -1079,6 +1083,25 @@ static int ad4170_set_pga(struct ad4170_state *st,
return 0;
}
+static int ad4170_set_calib_gain(struct ad4170_state *st,
+ struct iio_chan_spec const *chan, int val)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ u32 old_gain;
+ int ret;
+
+ guard(mutex)(&st->lock);
+ old_gain = setup->gain;
+ setup->gain = val;
+
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret)
+ setup->gain = old_gain;
+
+ return ret;
+}
+
static int __ad4170_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val,
int val2, long info)
@@ -1088,6 +1111,8 @@ static int __ad4170_write_raw(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return ad4170_set_pga(st, chan, val, val2);
+ case IIO_CHAN_INFO_CALIBSCALE:
+ return ad4170_set_calib_gain(st, chan, val);
default:
return -EINVAL;
}
@@ -1114,6 +1139,8 @@ static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (2 preceding siblings ...)
2025-05-13 12:34 ` [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain Marcelo Schmitt
@ 2025-05-13 12:34 ` Marcelo Schmitt
2025-05-26 10:27 ` Nuno Sá
2025-05-13 12:35 ` [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support Marcelo Schmitt
` (5 subsequent siblings)
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:34 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Add support for ADC calibration bias/offset configuration.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- New patch spun out of the base driver patch.
drivers/iio/adc/ad4170.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index 1df214f7fdec..b02fdd25b4c8 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -643,6 +643,7 @@ static const struct iio_chan_spec ad4170_channel_template = {
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
BIT(IIO_CHAN_INFO_CALIBSCALE),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
.scan_type = {
@@ -954,6 +955,9 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
*val = chan_info->offset_tbl[pga];
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ *val = setup->offset;
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_CALIBSCALE:
*val = setup->gain;
return IIO_VAL_INT;
@@ -1083,6 +1087,25 @@ static int ad4170_set_pga(struct ad4170_state *st,
return 0;
}
+static int ad4170_set_calib_offset(struct ad4170_state *st,
+ struct iio_chan_spec const *chan, int val)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ u32 old_offset;
+ int ret;
+
+ guard(mutex)(&st->lock);
+ old_offset = setup->offset;
+ setup->offset = val;
+
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret)
+ setup->offset = old_offset;
+
+ return ret;
+}
+
static int ad4170_set_calib_gain(struct ad4170_state *st,
struct iio_chan_spec const *chan, int val)
{
@@ -1111,6 +1134,8 @@ static int __ad4170_write_raw(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return ad4170_set_pga(st, chan, val, val2);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return ad4170_set_calib_offset(st, chan, val);
case IIO_CHAN_INFO_CALIBSCALE:
return ad4170_set_calib_gain(st, chan, val);
default:
@@ -1139,6 +1164,7 @@ static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_CALIBBIAS:
case IIO_CHAN_INFO_CALIBSCALE:
return IIO_VAL_INT;
default:
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (3 preceding siblings ...)
2025-05-13 12:34 ` [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias Marcelo Schmitt
@ 2025-05-13 12:35 ` Marcelo Schmitt
2025-05-25 10:41 ` Jonathan Cameron
2025-05-13 12:35 ` [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture Marcelo Schmitt
` (4 subsequent siblings)
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:35 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Add support for sinc3, sinc5, and averaged sinc5 digital filters along with
sample frequency configuration.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- New patch spun out of the base driver patch.
drivers/iio/adc/ad4170.c | 273 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 270 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index b02fdd25b4c8..218f768042fe 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -16,6 +16,7 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
@@ -84,6 +85,9 @@
#define AD4170_AFE_BIPOLAR_MSK BIT(4)
#define AD4170_AFE_PGA_GAIN_MSK GENMASK(3, 0)
+/* AD4170_FILTER_REG */
+#define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0)
+
/* AD4170 register constants */
/* AD4170_CHAN_MAP_REG constants */
@@ -109,6 +113,11 @@
#define AD4170_ADC_CTRL_MODE_SINGLE 0x4
#define AD4170_ADC_CTRL_MODE_IDLE 0x7
+/* AD4170_FILTER_REG constants */
+#define AD4170_FILTER_FILTER_TYPE_SINC5_AVG 0x0
+#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4
+#define AD4170_FILTER_FILTER_TYPE_SINC3 0x6
+
/* Device properties and auxiliary constants */
#define AD4170_NUM_ANALOG_PINS 9
@@ -117,6 +126,7 @@
#define AD4170_MAX_SETUPS 8
#define AD4170_INVALID_SETUP 9
#define AD4170_SPI_MAX_XFER_LEN 6
+#define AD4170_DEFAULT_SAMP_RATE (125 * HZ_PER_KHZ)
#define AD4170_INT_REF_2_5V 2500000
@@ -125,6 +135,12 @@
#define AD4170_NUM_PGA_OPTIONS 10
+/* Digital filter properties */
+#define AD4170_SINC3_MIN_FS 4
+#define AD4170_SINC3_MAX_FS 65532
+#define AD4170_SINC5_MIN_FS 1
+#define AD4170_SINC5_MAX_FS 256
+
#define AD4170_GAIN_REG_DEFAULT 0x555555
/* Analog pin functions */
@@ -181,6 +197,12 @@ enum ad4170_ref_select {
AD4170_REF_AVDD
};
+enum ad4170_filter_type {
+ AD4170_SINC5_AVG,
+ AD4170_SINC5,
+ AD4170_SINC3,
+};
+
enum ad4170_regulator {
AD4170_AVDD_SUP,
AD4170_AVSS_SUP,
@@ -202,6 +224,18 @@ static const char * const ad4170_int_pin_names[] = {
[AD4170_INT_PIN_DIG_AUX1] = "dig_aux1",
};
+static const unsigned int ad4170_sinc3_filt_fs_tbl[] = {
+ 4, 8, 12, 16, 20, 40, 48, 80, /* 0 - 7 */
+ 100, 256, 500, 1000, 5000, 8332, 10000, 25000, /* 8 - 15 */
+ 50000, 65532, /* 16 - 17 */
+};
+
+#define AD4170_MAX_FS_TBL_SIZE ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl)
+
+static const unsigned int ad4170_sinc5_filt_fs_tbl[] = {
+ 1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256,
+};
+
struct ad4170_chip_info {
const char *name;
};
@@ -259,6 +293,12 @@ struct ad4170_chan_info {
bool enabled;
};
+static const char * const ad4170_filt_names[] = {
+ [AD4170_SINC5_AVG] = "sinc5+avg",
+ [AD4170_SINC5] = "sinc5",
+ [AD4170_SINC3] = "sinc3",
+};
+
struct ad4170_state {
struct mutex lock; /* Protect read-modify-write and multi write sequences */
int vrefs_uv[AD4170_MAX_SUP];
@@ -268,6 +308,7 @@ struct ad4170_state {
struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS];
struct spi_device *spi;
struct regmap *regmap;
+ int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
int pins_fn[AD4170_NUM_ANALOG_PINS];
u32 int_pin_sel;
struct completion completion;
@@ -279,6 +320,38 @@ struct ad4170_state {
u8 rx_buf[4];
};
+static void ad4170_fill_sps_tbl(struct ad4170_state *st)
+{
+ unsigned int tmp0, tmp1, i;
+
+ /*
+ * The ODR can be calculated the same way for sinc5+avg, sinc5, and
+ * sinc3 filter types with the exception that sinc5 filter has a
+ * narrowed range of allowed FILTER_FS values.
+ */
+ for (i = 0; i < ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); i++) {
+ tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc3_filt_fs_tbl[i],
+ &tmp1);
+ tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc3_filt_fs_tbl[i]);
+ /* Fill sinc5+avg filter SPS table */
+ st->sps_tbl[AD4170_SINC5_AVG][i][0] = tmp0; /* Integer part */
+ st->sps_tbl[AD4170_SINC5_AVG][i][1] = tmp1; /* Fractional part */
+
+ /* Fill sinc3 filter SPS table */
+ st->sps_tbl[AD4170_SINC3][i][0] = tmp0; /* Integer part */
+ st->sps_tbl[AD4170_SINC3][i][1] = tmp1; /* Fractional part */
+ }
+ /* Sinc5 filter ODR doesn't use all FILTER_FS bits */
+ for (i = 0; i < ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); i++) {
+ tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc5_filt_fs_tbl[i],
+ &tmp1);
+ tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc5_filt_fs_tbl[i]);
+ /* Fill sinc5 filter SPS table */
+ st->sps_tbl[AD4170_SINC5][i][0] = tmp0; /* Integer part */
+ st->sps_tbl[AD4170_SINC5][i][1] = tmp1; /* Fractional part */
+ }
+}
+
static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev,
unsigned int reg, unsigned int writeval,
unsigned int *readval)
@@ -636,6 +709,105 @@ static int ad4170_set_channel_enable(struct ad4170_state *st,
return 0;
}
+static int __ad4170_get_filter_type(unsigned int filter)
+{
+ u16 f_conf = FIELD_GET(AD4170_FILTER_FILTER_TYPE_MSK, filter);
+
+ switch (f_conf) {
+ case AD4170_FILTER_FILTER_TYPE_SINC5_AVG:
+ return AD4170_SINC5_AVG;
+ case AD4170_FILTER_FILTER_TYPE_SINC5:
+ return AD4170_SINC5;
+ case AD4170_FILTER_FILTER_TYPE_SINC3:
+ return AD4170_SINC3;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4170_set_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ unsigned int val)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ unsigned int old_filter_fs, old_filter, filter_type_conf;
+ int ret;
+
+ switch (val) {
+ case AD4170_SINC5_AVG:
+ filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5_AVG;
+ break;
+ case AD4170_SINC5:
+ filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5;
+ break;
+ case AD4170_SINC3:
+ filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ guard(mutex)(&st->lock);
+ /*
+ * The filters provide the same ODR for a given filter_fs value but
+ * there are different minimum and maximum filter_fs limits for each
+ * filter. The filter_fs value will be adjusted if the current filter_fs
+ * is out of the limits of the just requested filter. Since the
+ * filter_fs value affects the ODR (sampling_frequency), changing the
+ * filter may lead to a change in the sampling frequency.
+ */
+ old_filter = setup->filter;
+ old_filter_fs = setup->filter_fs;
+ if (val == AD4170_SINC5_AVG || val == AD4170_SINC3)
+ setup->filter_fs = clamp(val, AD4170_SINC3_MIN_FS,
+ AD4170_SINC3_MAX_FS);
+ else
+ setup->filter_fs = clamp(val, AD4170_SINC5_MIN_FS,
+ AD4170_SINC5_MAX_FS);
+
+ setup->filter &= ~AD4170_FILTER_FILTER_TYPE_MSK;
+ setup->filter |= FIELD_PREP(AD4170_FILTER_FILTER_TYPE_MSK,
+ filter_type_conf);
+
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret) {
+ setup->filter = old_filter;
+ setup->filter_fs = old_filter_fs;
+ }
+
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static int ad4170_get_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+
+ return __ad4170_get_filter_type(setup->filter);
+}
+
+static const struct iio_enum ad4170_filter_type_enum = {
+ .items = ad4170_filt_names,
+ .num_items = ARRAY_SIZE(ad4170_filt_names),
+ .get = ad4170_get_filter_type,
+ .set = ad4170_set_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad4170_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+ &ad4170_filter_type_enum),
+ { }
+};
+
static const struct iio_chan_spec ad4170_channel_template = {
.type = IIO_VOLTAGE,
.indexed = 1,
@@ -644,8 +816,11 @@ static const struct iio_chan_spec ad4170_channel_template = {
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_CALIBBIAS) |
- BIT(IIO_CHAN_INFO_CALIBSCALE),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad4170_filter_type_ext_info,
.scan_type = {
.realbits = 24,
.storagebits = 32,
@@ -935,7 +1110,8 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
struct ad4170_state *st = iio_priv(indio_dev);
struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
struct ad4170_setup *setup = &chan_info->setup;
- unsigned int pga;
+ enum ad4170_filter_type f_type;
+ unsigned int pga, fs_idx;
int ret;
switch (info) {
@@ -955,6 +1131,27 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
*val = chan_info->offset_tbl[pga];
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ f_type = __ad4170_get_filter_type(setup->filter);
+ switch (f_type) {
+ case AD4170_SINC5_AVG:
+ case AD4170_SINC3:
+ fs_idx = find_closest(setup->filter_fs,
+ ad4170_sinc3_filt_fs_tbl,
+ ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl));
+ *val = st->sps_tbl[f_type][fs_idx][0];
+ *val2 = st->sps_tbl[f_type][fs_idx][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case AD4170_SINC5:
+ fs_idx = find_closest(setup->filter_fs,
+ ad4170_sinc5_filt_fs_tbl,
+ ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl));
+ *val = st->sps_tbl[f_type][fs_idx][0];
+ *val2 = st->sps_tbl[f_type][fs_idx][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
case IIO_CHAN_INFO_CALIBBIAS:
*val = setup->offset;
return IIO_VAL_INT;
@@ -1040,6 +1237,7 @@ static int ad4170_read_avail(struct iio_dev *indio_dev,
{
struct ad4170_state *st = iio_priv(indio_dev);
struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ enum ad4170_filter_type f_type;
switch (info) {
case IIO_CHAN_INFO_SCALE:
@@ -1047,6 +1245,21 @@ static int ad4170_read_avail(struct iio_dev *indio_dev,
*length = ARRAY_SIZE(chan_info->scale_tbl) * 2;
*type = IIO_VAL_INT_PLUS_NANO;
return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ f_type = ad4170_get_filter_type(indio_dev, chan);
+ *vals = (int *)st->sps_tbl[f_type];
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ switch (f_type) {
+ case AD4170_SINC5_AVG:
+ case AD4170_SINC3:
+ *length = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ case AD4170_SINC5:
+ *length = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -1087,6 +1300,48 @@ static int ad4170_set_pga(struct ad4170_state *st,
return 0;
}
+static int ad4170_set_channel_freq(struct ad4170_state *st,
+ struct iio_chan_spec const *chan, int val,
+ int val2)
+{
+ struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+ struct ad4170_setup *setup = &chan_info->setup;
+ enum ad4170_filter_type f_type = __ad4170_get_filter_type(setup->filter);
+ unsigned int old_filter_fs, i;
+ int filt_fs_tbl_size, ret;
+
+ switch (f_type) {
+ case AD4170_SINC5_AVG:
+ case AD4170_SINC3:
+ filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl);
+ break;
+ case AD4170_SINC5:
+ filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl);
+ break;
+ }
+
+ for (i = 0; i < filt_fs_tbl_size; i++) {
+ if (st->sps_tbl[f_type][i][0] == val &&
+ st->sps_tbl[f_type][i][1] == val2)
+ break;
+ }
+ if (i == filt_fs_tbl_size)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+ old_filter_fs = setup->filter_fs;
+ if (f_type == AD4170_SINC5)
+ setup->filter_fs = ad4170_sinc5_filt_fs_tbl[i];
+ else
+ setup->filter_fs = ad4170_sinc3_filt_fs_tbl[i];
+
+ ret = ad4170_write_channel_setup(st, chan->address, false);
+ if (ret)
+ setup->filter_fs = old_filter_fs;
+
+ return ret;
+}
+
static int ad4170_set_calib_offset(struct ad4170_state *st,
struct iio_chan_spec const *chan, int val)
{
@@ -1134,6 +1389,8 @@ static int __ad4170_write_raw(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return ad4170_set_pga(st, chan, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4170_set_channel_freq(st, chan, val, val2);
case IIO_CHAN_INFO_CALIBBIAS:
return ad4170_set_calib_offset(st, chan, val);
case IIO_CHAN_INFO_CALIBSCALE:
@@ -1164,6 +1421,8 @@ static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_SCALE:
return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_CALIBBIAS:
case IIO_CHAN_INFO_CALIBSCALE:
return IIO_VAL_INT;
@@ -1398,6 +1657,8 @@ static int ad4170_initial_config(struct iio_dev *indio_dev)
unsigned int i;
int ret;
+ ad4170_fill_sps_tbl(st);
+
ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
if (ret)
return dev_err_probe(dev, ret,
@@ -1427,6 +1688,12 @@ static int ad4170_initial_config(struct iio_dev *indio_dev)
return dev_err_probe(dev, ret,
"Failed to write CHAN_MAP_REG\n");
+ ret = ad4170_set_channel_freq(st, chan,
+ AD4170_DEFAULT_SAMP_RATE, 0);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set channel freq\n");
+
ret = ad4170_fill_scale_tbl(indio_dev, chan);
if (ret)
return dev_err_probe(dev, ret,
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (4 preceding siblings ...)
2025-05-13 12:35 ` [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support Marcelo Schmitt
@ 2025-05-13 12:35 ` Marcelo Schmitt
2025-05-25 10:46 ` Jonathan Cameron
2025-05-13 12:35 ` [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support Marcelo Schmitt
` (3 subsequent siblings)
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:35 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
Extend the AD4170 driver to allow buffered data capture in continuous read
mode. In continuous read mode, the chip skips the instruction phase and
outputs just ADC sample data, enabling faster sample rates to be reached.
The internal channel sequencer always starts sampling from channel 0 and
channel 0 must be enabled if more than one channel is selected for data
capture. The scan mask validation callback checks if the aforementioned
condition is met.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Made validate_scan_mask() return false if no channel enabled (bitmap_weight() == 0).
- Use local variable device pointer to simplify trigger setup and better wrap lines.
- Tidy up buffer declaration, function returns, comments, and other minor things.
- Fixed data handling in trigger handler which I hadn't noticed to became buggy
after xfter buffer type was changed from __be32 to u8 array.
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ad4170.c | 211 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 211 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 594b9f55ec0a..0d3d3af862f8 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -75,6 +75,8 @@ config AD4170
tristate "Analog Device AD4170 ADC Driver"
depends on SPI
select REGMAP_SPI
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
help
Say yes here to build support for Analog Devices AD4170 SPI analog
to digital converters (ADC).
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index 218f768042fe..e8856c911dfd 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -12,7 +12,11 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/math64.h>
@@ -55,6 +59,7 @@
#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x))
#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x))
#define AD4170_GAIN_REG(x) (0xCD + 14 * (x))
+#define AD4170_ADC_CTRL_CONT_READ_EXIT_REG 0x200 /* virtual reg */
#define AD4170_REG_READ_MASK BIT(14)
@@ -66,6 +71,7 @@
/* AD4170_ADC_CTRL_REG */
#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7)
+#define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4)
#define AD4170_ADC_CTRL_MODE_MSK GENMASK(3, 0)
/* AD4170_CHAN_EN_REG */
@@ -110,9 +116,13 @@
#define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1
/* AD4170_ADC_CTRL_REG constants */
+#define AD4170_ADC_CTRL_MODE_CONT 0x0
#define AD4170_ADC_CTRL_MODE_SINGLE 0x4
#define AD4170_ADC_CTRL_MODE_IDLE 0x7
+#define AD4170_ADC_CTRL_CONT_READ_DISABLE 0x0
+#define AD4170_ADC_CTRL_CONT_READ_ENABLE 0x1
+
/* AD4170_FILTER_REG constants */
#define AD4170_FILTER_FILTER_TYPE_SINC5_AVG 0x0
#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4
@@ -143,6 +153,8 @@
#define AD4170_GAIN_REG_DEFAULT 0x555555
+#define AD4170_ADC_CTRL_CONT_READ_EXIT 0xA5
+
/* Analog pin functions */
#define AD4170_PIN_UNASIGNED 0x00
@@ -182,6 +194,7 @@ static const unsigned int ad4170_reg_size[] = {
[AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3,
[AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3,
[AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3,
+ [AD4170_ADC_CTRL_CONT_READ_EXIT_REG] = 0,
};
enum ad4170_ref_buf {
@@ -309,9 +322,13 @@ struct ad4170_state {
struct spi_device *spi;
struct regmap *regmap;
int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
+ __be32 bounce_buffer[AD4170_MAX_CHANNELS];
+ struct spi_message msg;
+ struct spi_transfer xfer;
+ struct iio_trigger *trig;
+ struct completion completion;
int pins_fn[AD4170_NUM_ANALOG_PINS];
u32 int_pin_sel;
- struct completion completion;
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
* to live in their own cache lines.
@@ -396,6 +413,10 @@ static int ad4170_reg_write(void *context, unsigned int reg, unsigned int val)
case 1:
st->tx_buf[2] = val;
break;
+ case 0:
+ /* Write continuous read exit code */
+ st->tx_buf[0] = AD4170_ADC_CTRL_CONT_READ_EXIT;
+ return spi_write(st->spi, st->tx_buf, 1);
default:
return -EINVAL;
}
@@ -556,6 +577,7 @@ static int ad4170_link_channel_setup(struct ad4170_state *st,
/*
* Sets the ADC operating mode. Supported modes are
+ * - Continuous conversion mode (default)
* - Single conversion mode
* - Idle mode
*/
@@ -824,6 +846,7 @@ static const struct iio_chan_spec ad4170_channel_template = {
.scan_type = {
.realbits = 24,
.storagebits = 32,
+ .shift = 8,
.endianness = IIO_BE,
},
};
@@ -1431,11 +1454,27 @@ static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
}
}
+static int ad4170_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *active_scan_mask)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned int chan_index;
+ int ret;
+
+ iio_for_each_active_channel(indio_dev, chan_index) {
+ ret = ad4170_set_channel_enable(st, chan_index, true);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
static const struct iio_info ad4170_info = {
.read_raw = ad4170_read_raw,
.read_avail = ad4170_read_avail,
.write_raw = ad4170_write_raw,
.write_raw_get_fmt = ad4170_write_raw_get_fmt,
+ .update_scan_mode = ad4170_update_scan_mode,
.debugfs_reg_access = ad4170_debugfs_reg_access,
};
@@ -1716,16 +1755,170 @@ static int ad4170_initial_config(struct iio_dev *indio_dev)
AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK);
}
+static int ad4170_prepare_spi_message(struct ad4170_state *st)
+{
+ /*
+ * Continuous data register read is enabled on buffer postenable so
+ * no instruction phase is needed meaning we don't need to send the
+ * register address to read data. Transfer only needs the read buffer.
+ */
+ st->xfer.rx_buf = &st->rx_buf;
+ st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits);
+
+ spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
+
+ return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg);
+}
+
+static int ad4170_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_CONT);
+ if (ret)
+ return ret;
+
+ /*
+ * This enables continuous read of the ADC data register. The ADC must
+ * be in continuous conversion mode.
+ */
+ return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+ AD4170_ADC_CTRL_CONT_READ_MSK,
+ FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK,
+ AD4170_ADC_CTRL_CONT_READ_ENABLE));
+}
+
+static int ad4170_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned int i;
+ int ret;
+
+ /*
+ * Use a high register address (virtual register) to request a write of
+ * 0xA5 to the ADC during the first 8 SCLKs of the ADC data read cycle,
+ * thus exiting continuous read.
+ */
+ ret = regmap_write(st->regmap, AD4170_ADC_CTRL_CONT_READ_EXIT_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+ AD4170_ADC_CTRL_CONT_READ_MSK,
+ FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK,
+ AD4170_ADC_CTRL_CONT_READ_DISABLE));
+ if (ret)
+ return ret;
+
+ ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+ if (ret)
+ return ret;
+
+ /*
+ * The ADC sequences through all the enabled channels (see datasheet
+ * page 95). That can lead to incorrect channel being read if a
+ * single-shot read (or buffered read with different active_scan_mask)
+ * is done after buffer disable. Disable all channels so only requested
+ * channels will be read.
+ */
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ ret = ad4170_set_channel_enable(st, i, false);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool ad4170_validate_scan_mask(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ unsigned int masklength = iio_get_masklength(indio_dev);
+
+ /*
+ * The channel sequencer cycles through the enabled channels in
+ * sequential order, from channel 0 to channel 15, bypassing disabled
+ * channels. When more than one channel is enabled, channel 0 must
+ * always be enabled. See datasheet channel_en register description at
+ * page 95.
+ */
+ if (bitmap_weight(scan_mask, masklength) > 1)
+ return test_bit(0, scan_mask);
+
+ return bitmap_weight(scan_mask, masklength) == 1;
+}
+
+static const struct iio_buffer_setup_ops ad4170_buffer_ops = {
+ .postenable = ad4170_buffer_postenable,
+ .predisable = ad4170_buffer_predisable,
+ .validate_scan_mask = ad4170_validate_scan_mask,
+};
+
+static irqreturn_t ad4170_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned int i;
+ int ret;
+
+ iio_for_each_active_channel(indio_dev, i) {
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ goto err_out;
+
+ memcpy(&st->bounce_buffer[i], st->rx_buf, ARRAY_SIZE(st->rx_buf));
+ }
+
+ iio_push_to_buffers(indio_dev, st->bounce_buffer);
+err_out:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops ad4170_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
static irqreturn_t ad4170_irq_handler(int irq, void *dev_id)
{
struct iio_dev *indio_dev = dev_id;
struct ad4170_state *st = iio_priv(indio_dev);
- complete(&st->completion);
+ if (iio_buffer_enabled(indio_dev))
+ iio_trigger_poll(st->trig);
+ else
+ complete(&st->completion);
return IRQ_HANDLED;
};
+static int ad4170_trigger_setup(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-trig%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad4170_trigger_ops;
+ st->trig->dev.parent = dev;
+
+ iio_trigger_set_drvdata(st->trig, indio_dev);
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register trigger\n");
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ return 0;
+}
+
static int ad4170_regulator_setup(struct ad4170_state *st)
{
struct device *dev = &st->spi->dev;
@@ -1836,8 +2029,22 @@ static int ad4170_probe(struct spi_device *spi)
indio_dev->name, indio_dev);
if (ret)
return ret;
+
+ ret = ad4170_trigger_setup(indio_dev);
+ if (ret)
+ return ret;
}
+ ret = ad4170_prepare_spi_message(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to prepare SPI message\n");
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ &ad4170_trigger_handler,
+ &ad4170_buffer_ops);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to setup read buffer\n");
+
return devm_iio_device_register(dev, indio_dev);
}
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (5 preceding siblings ...)
2025-05-13 12:35 ` [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture Marcelo Schmitt
@ 2025-05-13 12:35 ` Marcelo Schmitt
2025-05-13 16:59 ` David Lechner
2025-05-13 12:36 ` [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support Marcelo Schmitt
` (2 subsequent siblings)
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:35 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
The AD4170 chip can use an externally supplied clock at the XTAL2 pin, or
an external crystal connected to the XTAL1 and XTAL2 pins. Alternatively,
the AD4170 can provide its 16 MHz internal clock at the XTAL2 pin. Extend
the AD4170 driver so it effectively uses the provided external clock, if
any, or supplies its own clock as a clock provider.
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Applied minor changes according to v2 review.
- Avoid unmet dependencies by depending on COMMON_CLK rather than selecting that.
- Brought early external clock defines from early patches to this one.
- Picked up a review tag from v1 review that I failed to include in v2.
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4170.c | 144 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 144 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 0d3d3af862f8..6e4b14243599 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -77,6 +77,7 @@ config AD4170
select REGMAP_SPI
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
+ depends on COMMON_CLK
help
Say yes here to build support for Analog Devices AD4170 SPI analog
to digital converters (ADC).
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index e8856c911dfd..9ab6df002e5e 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -9,6 +9,8 @@
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -49,6 +51,7 @@
#define AD4170_CONFIG_A_REG 0x00
#define AD4170_DATA_24B_REG 0x1E
#define AD4170_PIN_MUXING_REG 0x69
+#define AD4170_CLOCK_CTRL_REG 0x6B
#define AD4170_ADC_CTRL_REG 0x71
#define AD4170_CHAN_EN_REG 0x79
#define AD4170_CHAN_SETUP_REG(x) (0x81 + 4 * (x))
@@ -69,6 +72,9 @@
/* AD4170_PIN_MUXING_REG */
#define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK GENMASK(5, 4)
+/* AD4170_CLOCK_CTRL_REG */
+#define AD4170_CLOCK_CTRL_CLOCKSEL_MSK GENMASK(1, 0)
+
/* AD4170_ADC_CTRL_REG */
#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7)
#define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4)
@@ -96,6 +102,12 @@
/* AD4170 register constants */
+/* AD4170_CLOCK_CTRL_REG constants */
+#define AD4170_CLOCK_CTRL_CLOCKSEL_INT 0x0
+#define AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT 0x1
+#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT 0x2
+#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT_XTAL 0x3
+
/* AD4170_CHAN_MAP_REG constants */
#define AD4170_CHAN_MAP_AIN(x) (x)
#define AD4170_CHAN_MAP_TEMP_SENSOR 17
@@ -142,6 +154,8 @@
/* Internal and external clock properties */
#define AD4170_INT_CLOCK_16MHZ (16 * HZ_PER_MHZ)
+#define AD4170_EXT_CLOCK_MHZ_MIN (1 * HZ_PER_MHZ)
+#define AD4170_EXT_CLOCK_MHZ_MAX (17 * HZ_PER_MHZ)
#define AD4170_NUM_PGA_OPTIONS 10
@@ -162,6 +176,7 @@ static const unsigned int ad4170_reg_size[] = {
[AD4170_CONFIG_A_REG] = 1,
[AD4170_DATA_24B_REG] = 3,
[AD4170_PIN_MUXING_REG] = 2,
+ [AD4170_CLOCK_CTRL_REG] = 2,
[AD4170_ADC_CTRL_REG] = 2,
[AD4170_CHAN_EN_REG] = 2,
/*
@@ -227,6 +242,10 @@ enum ad4170_regulator {
AD4170_MAX_SUP
};
+static const char *const ad4170_clk_sel[] = {
+ "ext-clk", "xtal",
+};
+
enum ad4170_int_pin_sel {
AD4170_INT_PIN_SDO,
AD4170_INT_PIN_DIG_AUX1,
@@ -329,6 +348,9 @@ struct ad4170_state {
struct completion completion;
int pins_fn[AD4170_NUM_ANALOG_PINS];
u32 int_pin_sel;
+ struct clk_hw int_clk_hw;
+ struct clk *ext_clk;
+ unsigned int clock_ctrl;
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
* to live in their own cache lines.
@@ -1656,6 +1678,120 @@ static int ad4170_parse_channels(struct iio_dev *indio_dev)
return 0;
}
+static struct ad4170_state *clk_hw_to_ad4170(struct clk_hw *hw)
+{
+ return container_of(hw, struct ad4170_state, int_clk_hw);
+}
+
+static unsigned long ad4170_sel_clk(struct ad4170_state *st,
+ unsigned int clk_sel)
+{
+ st->clock_ctrl &= ~AD4170_CLOCK_CTRL_CLOCKSEL_MSK;
+ st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, clk_sel);
+ return regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
+}
+
+static unsigned long ad4170_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return AD4170_INT_CLOCK_16MHZ;
+}
+
+static int ad4170_clk_output_is_enabled(struct clk_hw *hw)
+{
+ struct ad4170_state *st = clk_hw_to_ad4170(hw);
+ u32 clk_sel;
+
+ clk_sel = FIELD_GET(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, st->clock_ctrl);
+ return clk_sel == AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT;
+}
+
+static int ad4170_clk_output_prepare(struct clk_hw *hw)
+{
+ struct ad4170_state *st = clk_hw_to_ad4170(hw);
+
+ return ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT);
+}
+
+static void ad4170_clk_output_unprepare(struct clk_hw *hw)
+{
+ struct ad4170_state *st = clk_hw_to_ad4170(hw);
+
+ ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT);
+}
+
+static const struct clk_ops ad4170_int_clk_ops = {
+ .recalc_rate = ad4170_clk_recalc_rate,
+ .is_enabled = ad4170_clk_output_is_enabled,
+ .prepare = ad4170_clk_output_prepare,
+ .unprepare = ad4170_clk_output_unprepare,
+};
+
+static int ad4170_register_clk_provider(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ struct clk_init_data init = {};
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ if (device_property_read_string(dev, "clock-output-names", &init.name)) {
+ init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw",
+ dev_fwnode(dev));
+ if (!init.name)
+ return -ENOMEM;
+ }
+
+ init.ops = &ad4170_int_clk_ops;
+
+ st->int_clk_hw.init = &init;
+ ret = devm_clk_hw_register(dev, &st->int_clk_hw);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+ &st->int_clk_hw);
+}
+
+static int ad4170_clock_select(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
+ ret = device_property_match_property_string(dev, "clock-names",
+ ad4170_clk_sel,
+ ARRAY_SIZE(ad4170_clk_sel));
+ if (ret < 0) {
+ /* Use internal clock reference */
+ st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
+ AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT);
+ return ad4170_register_clk_provider(indio_dev);
+ }
+
+ /* Use external clock reference */
+ st->ext_clk = devm_clk_get_enabled(dev, ad4170_clk_sel[ret]);
+ if (IS_ERR(st->ext_clk))
+ return dev_err_probe(dev, PTR_ERR(st->ext_clk),
+ "Failed to get external clock\n");
+
+ st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
+ AD4170_CLOCK_CTRL_CLOCKSEL_EXT + ret);
+
+ st->mclk_hz = clk_get_rate(st->ext_clk);
+ if (st->mclk_hz < AD4170_EXT_CLOCK_MHZ_MIN ||
+ st->mclk_hz > AD4170_EXT_CLOCK_MHZ_MAX) {
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid external clock frequency %u\n",
+ st->mclk_hz);
+ }
+
+ return 0;
+}
+
static int ad4170_parse_firmware(struct iio_dev *indio_dev)
{
struct ad4170_state *st = iio_priv(indio_dev);
@@ -1663,7 +1799,13 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
int reg_data, ret;
unsigned int i;
- st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
+ ret = ad4170_clock_select(indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to setup device clock\n");
+
+ ret = regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
+ if (ret)
+ return ret;
for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
st->pins_fn[i] = AD4170_PIN_UNASIGNED;
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (6 preceding siblings ...)
2025-05-13 12:35 ` [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support Marcelo Schmitt
@ 2025-05-13 12:36 ` Marcelo Schmitt
2025-05-20 17:06 ` Bartosz Golaszewski
2025-05-13 12:36 ` [PATCH v3 09/10] iio: adc: ad4170: Add support for internal temperature sensor Marcelo Schmitt
2025-05-13 12:36 ` [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors Marcelo Schmitt
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:36 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
The AD4170 has four multifunctional pins that can be used as GPIOs. The
GPIO functionality can be accessed when the AD4170 chip is not busy
performing continuous data capture or handling any other register
read/write request. Also, the AD4170 does not provide any interrupt based
on GPIO pin states so AD4170 GPIOs can't be used as interrupt sources.
Implement gpio_chip callbacks to make AD4170 GPIO pins controllable through
the gpiochip interface.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Defined masks for updating GPIO mode register.
- Replaced regmap_clear/set_bits() by regmap_update_bits() to set GPIO direction.
- Removed GPIO direction check before setting GPIO output values.
- Made use of regmap_assign_bits() to set GPIO output reg bits.
- Made value to be set as GPIO output state be either 0 or 1.
- No longer locking on state mutex on GPIO set since GPIO output should not
conflict with other direct mode functionality (e.g. single-shot read).
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4170.c | 224 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 224 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6e4b14243599..a328f03eea34 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -78,6 +78,7 @@ config AD4170
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
depends on COMMON_CLK
+ select GPIOLIB
help
Say yes here to build support for Analog Devices AD4170 SPI analog
to digital converters (ADC).
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index 9ab6df002e5e..31f126a0f76f 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -14,6 +14,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/gpio/driver.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger.h>
@@ -62,6 +63,9 @@
#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x))
#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x))
#define AD4170_GAIN_REG(x) (0xCD + 14 * (x))
+#define AD4170_GPIO_MODE_REG 0x191
+#define AD4170_GPIO_OUTPUT_REG 0x193
+#define AD4170_GPIO_INPUT_REG 0x195
#define AD4170_ADC_CTRL_CONT_READ_EXIT_REG 0x200 /* virtual reg */
#define AD4170_REG_READ_MASK BIT(14)
@@ -100,6 +104,12 @@
/* AD4170_FILTER_REG */
#define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0)
+/* AD4170_GPIO_MODE_REG */
+#define AD4170_GPIO_MODE_GPIO0_MSK GENMASK(1, 0)
+#define AD4170_GPIO_MODE_GPIO1_MSK GENMASK(3, 2)
+#define AD4170_GPIO_MODE_GPIO2_MSK GENMASK(5, 4)
+#define AD4170_GPIO_MODE_GPIO3_MSK GENMASK(7, 6)
+
/* AD4170 register constants */
/* AD4170_CLOCK_CTRL_REG constants */
@@ -140,9 +150,14 @@
#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4
#define AD4170_FILTER_FILTER_TYPE_SINC3 0x6
+/* AD4170_GPIO_MODE_REG constants */
+#define AD4170_GPIO_MODE_GPIO_INPUT 1
+#define AD4170_GPIO_MODE_GPIO_OUTPUT 2
+
/* Device properties and auxiliary constants */
#define AD4170_NUM_ANALOG_PINS 9
+#define AD4170_NUM_GPIO_PINS 4
#define AD4170_MAX_CHANNELS 16
#define AD4170_MAX_ANALOG_PINS 8
#define AD4170_MAX_SETUPS 8
@@ -172,6 +187,9 @@
/* Analog pin functions */
#define AD4170_PIN_UNASIGNED 0x00
+/* GPIO pin functions */
+#define AD4170_GPIO_UNASIGNED 0x00
+
static const unsigned int ad4170_reg_size[] = {
[AD4170_CONFIG_A_REG] = 1,
[AD4170_DATA_24B_REG] = 3,
@@ -209,6 +227,9 @@ static const unsigned int ad4170_reg_size[] = {
[AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3,
[AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3,
[AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3,
+ [AD4170_GPIO_MODE_REG] = 2,
+ [AD4170_GPIO_OUTPUT_REG] = 2,
+ [AD4170_GPIO_INPUT_REG] = 2,
[AD4170_ADC_CTRL_CONT_READ_EXIT_REG] = 0,
};
@@ -349,8 +370,10 @@ struct ad4170_state {
int pins_fn[AD4170_NUM_ANALOG_PINS];
u32 int_pin_sel;
struct clk_hw int_clk_hw;
+ struct gpio_chip gpiochip;
struct clk *ext_clk;
unsigned int clock_ctrl;
+ int gpio_fn[AD4170_NUM_GPIO_PINS];
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
* to live in their own cache lines.
@@ -1515,6 +1538,194 @@ static int ad4170_soft_reset(struct ad4170_state *st)
return 0;
}
+static int ad4170_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val);
+ if (ret)
+ goto err_release;
+
+ /*
+ * If the GPIO is configured as an input, read the current value from
+ * AD4170_GPIO_INPUT_REG. Otherwise, read the input value from
+ * AD4170_GPIO_OUTPUT_REG.
+ */
+ if (val & BIT(offset * 2))
+ ret = regmap_read(st->regmap, AD4170_GPIO_INPUT_REG, &val);
+ else
+ ret = regmap_read(st->regmap, AD4170_GPIO_OUTPUT_REG, &val);
+ if (ret)
+ goto err_release;
+
+ ret = !!(val & BIT(offset));
+err_release:
+ iio_device_release_direct(indio_dev);
+
+ return ret;
+}
+
+static int ad4170_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ad4170_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = regmap_assign_bits(st->regmap, AD4170_GPIO_OUTPUT_REG,
+ BIT(offset), !!value);
+
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static int ad4170_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val);
+ if (ret)
+ goto err_release;
+
+ if (val & BIT(offset * 2 + 1))
+ ret = GPIO_LINE_DIRECTION_OUT;
+ else
+ ret = GPIO_LINE_DIRECTION_IN;
+
+err_release:
+ iio_device_release_direct(indio_dev);
+
+ return ret;
+}
+
+static int ad4170_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned long gpio_mask;
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ switch (offset) {
+ case 0:
+ gpio_mask = AD4170_GPIO_MODE_GPIO0_MSK;
+ break;
+ case 1:
+ gpio_mask = AD4170_GPIO_MODE_GPIO1_MSK;
+ break;
+ case 2:
+ gpio_mask = AD4170_GPIO_MODE_GPIO2_MSK;
+ break;
+ case 3:
+ gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err_release;
+ }
+ ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
+ AD4170_GPIO_MODE_GPIO_INPUT << (2 * offset));
+
+err_release:
+ iio_device_release_direct(indio_dev);
+
+ return ret;
+}
+
+static int ad4170_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ad4170_state *st = iio_priv(indio_dev);
+ unsigned long gpio_mask;
+ int ret;
+
+ ret = ad4170_gpio_set(gc, offset, value);
+ if (ret)
+ return ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ switch (offset) {
+ case 0:
+ gpio_mask = AD4170_GPIO_MODE_GPIO0_MSK;
+ break;
+ case 1:
+ gpio_mask = AD4170_GPIO_MODE_GPIO1_MSK;
+ break;
+ case 2:
+ gpio_mask = AD4170_GPIO_MODE_GPIO2_MSK;
+ break;
+ case 3:
+ gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err_release;
+ }
+ ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * offset));
+
+err_release:
+ iio_device_release_direct(indio_dev);
+
+ return ret;
+}
+
+static int ad4170_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct ad4170_state *st = gpiochip_get_data(gc);
+ unsigned int i;
+
+ /* Only expose GPIOs that were not assigned any other function. */
+ for (i = 0; i < ngpios; i++) {
+ bool valid = st->gpio_fn[i] == AD4170_GPIO_UNASIGNED;
+
+ __assign_bit(i, valid_mask, valid);
+ }
+
+ return 0;
+}
+
+static int ad4170_gpio_init(struct iio_dev *indio_dev)
+{
+ struct ad4170_state *st = iio_priv(indio_dev);
+
+ st->gpiochip.label = "ad4170_gpios";
+ st->gpiochip.base = -1;
+ st->gpiochip.ngpio = AD4170_NUM_GPIO_PINS;
+ st->gpiochip.parent = &st->spi->dev;
+ st->gpiochip.can_sleep = true;
+ st->gpiochip.init_valid_mask = ad4170_gpio_init_valid_mask;
+ st->gpiochip.get_direction = ad4170_gpio_get_direction;
+ st->gpiochip.direction_input = ad4170_gpio_direction_input;
+ st->gpiochip.direction_output = ad4170_gpio_direction_output;
+ st->gpiochip.get = ad4170_gpio_get;
+ st->gpiochip.set_rv = ad4170_gpio_set;
+ st->gpiochip.owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev);
+}
+
static int ad4170_parse_reference(struct ad4170_state *st,
struct fwnode_handle *child,
struct ad4170_setup *setup)
@@ -1828,7 +2039,18 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
if (ret)
return ret;
- return ad4170_parse_channels(indio_dev);
+ ret = ad4170_parse_channels(indio_dev);
+ if (ret)
+ return ret;
+
+ /* Only create a GPIO chip if flagged for it */
+ if (device_property_read_bool(&st->spi->dev, "gpio-controller")) {
+ ret = ad4170_gpio_init(indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static int ad4170_initial_config(struct iio_dev *indio_dev)
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 09/10] iio: adc: ad4170: Add support for internal temperature sensor
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (7 preceding siblings ...)
2025-05-13 12:36 ` [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support Marcelo Schmitt
@ 2025-05-13 12:36 ` Marcelo Schmitt
2025-05-13 12:36 ` [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors Marcelo Schmitt
9 siblings, 0 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:36 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
The AD4170 has an internal temperature sensor that can be read using the
ADC. Whenever possible, configure an IIO channel to provide the chip's
temperature.
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Wrapped line according to logical approach.
drivers/iio/adc/ad4170.c | 72 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 69 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index 31f126a0f76f..1150c2e7ca1a 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -896,6 +896,27 @@ static const struct iio_chan_spec ad4170_channel_template = {
},
};
+static const struct iio_chan_spec ad4170_temp_channel_template = {
+ .type = IIO_TEMP,
+ .indexed = 0,
+ .channel = 17,
+ .channel2 = 17,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_type = {
+ .sign = 's',
+ .realbits = 24,
+ .storagebits = 32,
+ .shift = 8,
+ .endianness = IIO_BE,
+ },
+};
+
/*
* Receives the number of a multiplexed AD4170 input (ain_n), and stores the
* voltage (in µV) of the specified input into ain_voltage. If the input number
@@ -1192,9 +1213,27 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
return ret;
case IIO_CHAN_INFO_SCALE:
pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
- *val = chan_info->scale_tbl[pga][0];
- *val2 = chan_info->scale_tbl[pga][1];
- return IIO_VAL_INT_PLUS_NANO;
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ *val = chan_info->scale_tbl[pga][0];
+ *val2 = chan_info->scale_tbl[pga][1];
+ return IIO_VAL_INT_PLUS_NANO;
+
+ case IIO_TEMP:
+ /*
+ * The scale_tbl converts output codes to mV units so
+ * multiply by MILLI to make the factor convert to µV.
+ * Then, apply the temperature sensor change sensitivity
+ * of 477 μV/K. Finally, multiply the result by MILLI
+ * again to comply with milli degrees Celsius IIO ABI.
+ */
+ *val = 0;
+ *val2 = DIV_ROUND_CLOSEST(chan_info->scale_tbl[pga][1] * MILLI, 477) *
+ MILLI;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
case IIO_CHAN_INFO_OFFSET:
pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
*val = chan_info->offset_tbl[pga];
@@ -1878,12 +1917,39 @@ static int ad4170_parse_channels(struct iio_dev *indio_dev)
if (num_channels > AD4170_MAX_CHANNELS)
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
+ /* Add one for temperature */
+ num_channels = min(num_channels + 1, AD4170_MAX_CHANNELS);
+
device_for_each_child_node_scoped(dev, child) {
ret = ad4170_parse_channel_node(indio_dev, child, chan_num++);
if (ret)
return ret;
}
+ /*
+ * Add internal temperature sensor channel if the maximum number of
+ * channels has not been reached.
+ */
+ if (num_channels < AD4170_MAX_CHANNELS) {
+ struct ad4170_setup *setup = &st->chan_infos[chan_num].setup;
+
+ st->chans[chan_num] = ad4170_temp_channel_template;
+ st->chans[chan_num].address = chan_num;
+ st->chans[chan_num].scan_index = chan_num;
+
+ st->chan_infos[chan_num].setup_num = AD4170_INVALID_SETUP;
+ st->chan_infos[chan_num].initialized = true;
+
+ setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK,
+ AD4170_REF_AVDD);
+
+ ret = ad4170_get_input_range(st, &st->chans[chan_num], chan_num,
+ AD4170_REF_AVDD);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Invalid input config\n");
+
+ st->chan_infos[chan_num].input_range_uv = ret;
+ }
indio_dev->num_channels = num_channels;
indio_dev->channels = st->chans;
return 0;
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
` (8 preceding siblings ...)
2025-05-13 12:36 ` [PATCH v3 09/10] iio: adc: ad4170: Add support for internal temperature sensor Marcelo Schmitt
@ 2025-05-13 12:36 ` Marcelo Schmitt
2025-05-25 10:57 ` Jonathan Cameron
9 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-13 12:36 UTC (permalink / raw)
To: linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
The AD4170 design has features to aid interfacing with weigh scale and RTD
sensors that are expected to be setup with external circuitry for proper
sensor operation. A key characteristic of those sensors is that the circuit
they are in must be excited with a pair of signals. The external circuit
can be excited either by voltage supply or by AD4170 excitation signals.
The sensor can then be read through a different pair of lines that are
connected to AD4170 ADC.
Configure AD4170 to handle external circuit sensors.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3
- Added trailing comma to ad4170_iout_current_ua_tbl array.
- Simplified AD4170_CURRENT_SRC_REG constants with macros.
- Used temporary variable to keep logical line wrapping of vbias calculation.
- Dropped ad4170_find_table_index() after open coding both uses of that.
- Extracted pin validation to reduce indentation.
- Inverted ad4170_setup_bridge() logic to reduce indentation.
- Used GPIO register masks to convey reg write meanings in ad4170_setup_bridge().
- Reworked ad4170_setup_current_src() to make it readable.
drivers/iio/adc/ad4170.c | 419 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 415 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
index 1150c2e7ca1a..f80fbc6b663a 100644
--- a/drivers/iio/adc/ad4170.c
+++ b/drivers/iio/adc/ad4170.c
@@ -63,6 +63,8 @@
#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x))
#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x))
#define AD4170_GAIN_REG(x) (0xCD + 14 * (x))
+#define AD4170_V_BIAS_REG 0x135
+#define AD4170_CURRENT_SRC_REG(x) (0x139 + 2 * (x))
#define AD4170_GPIO_MODE_REG 0x191
#define AD4170_GPIO_OUTPUT_REG 0x193
#define AD4170_GPIO_INPUT_REG 0x195
@@ -94,6 +96,10 @@
#define AD4170_CHAN_MAP_AINP_MSK GENMASK(12, 8)
#define AD4170_CHAN_MAP_AINM_MSK GENMASK(4, 0)
+/* AD4170_MISC_REG */
+#define AD4170_MISC_CHOP_IEXC_MSK GENMASK(15, 14)
+#define AD4170_MISC_CHOP_ADC_MSK GENMASK(9, 8)
+
/* AD4170_AFE_REG */
#define AD4170_AFE_REF_BUF_M_MSK GENMASK(11, 10)
#define AD4170_AFE_REF_BUF_P_MSK GENMASK(9, 8)
@@ -104,6 +110,10 @@
/* AD4170_FILTER_REG */
#define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0)
+/* AD4170_CURRENT_SRC_REG */
+#define AD4170_CURRENT_SRC_I_OUT_PIN_MSK GENMASK(12, 8)
+#define AD4170_CURRENT_SRC_I_OUT_VAL_MSK GENMASK(2, 0)
+
/* AD4170_GPIO_MODE_REG */
#define AD4170_GPIO_MODE_GPIO0_MSK GENMASK(1, 0)
#define AD4170_GPIO_MODE_GPIO1_MSK GENMASK(3, 2)
@@ -133,6 +143,11 @@
#define AD4170_CHAN_MAP_REFIN2_N 28
#define AD4170_CHAN_MAP_REFOUT 29
+/* AD4170_MISC_REG constants */
+#define AD4170_MISC_CHOP_IEXC_PAIR1 0x1
+#define AD4170_MISC_CHOP_IEXC_PAIR2 0x2
+#define AD4170_MISC_CHOP_IEXC_BOTH 0x3
+
/* AD4170_PIN_MUXING_REG constants */
#define AD4170_PIN_MUXING_DIG_AUX1_DISABLED 0x0
#define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1
@@ -150,6 +165,10 @@
#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4
#define AD4170_FILTER_FILTER_TYPE_SINC3 0x6
+/* AD4170_CURRENT_SRC_REG constants */
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN(x) (x)
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(x) ((x) + 17)
+
/* AD4170_GPIO_MODE_REG constants */
#define AD4170_GPIO_MODE_GPIO_INPUT 1
#define AD4170_GPIO_MODE_GPIO_OUTPUT 2
@@ -163,6 +182,7 @@
#define AD4170_MAX_SETUPS 8
#define AD4170_INVALID_SETUP 9
#define AD4170_SPI_MAX_XFER_LEN 6
+#define AD4170_NUM_CURRENT_SRC 4
#define AD4170_DEFAULT_SAMP_RATE (125 * HZ_PER_KHZ)
#define AD4170_INT_REF_2_5V 2500000
@@ -186,9 +206,17 @@
/* Analog pin functions */
#define AD4170_PIN_UNASIGNED 0x00
+#define AD4170_PIN_ANALOG_IN 0x01
+#define AD4170_PIN_CURRENT_OUT 0x02
+#define AD4170_PIN_VBIAS 0x04
/* GPIO pin functions */
#define AD4170_GPIO_UNASIGNED 0x00
+#define AD4170_GPIO_AC_EXCITATION 0x02
+#define AD4170_GPIO_OUTPUT 0x04
+
+/* Current source */
+#define AD4170_CURRENT_SRC_DISABLED 0xFF
static const unsigned int ad4170_reg_size[] = {
[AD4170_CONFIG_A_REG] = 1,
@@ -227,6 +255,8 @@ static const unsigned int ad4170_reg_size[] = {
[AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3,
[AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3,
[AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3,
+ [AD4170_V_BIAS_REG] = 2,
+ [AD4170_CURRENT_SRC_REG(0) ... AD4170_CURRENT_SRC_REG(3)] = 2,
[AD4170_GPIO_MODE_REG] = 2,
[AD4170_GPIO_OUTPUT_REG] = 2,
[AD4170_GPIO_INPUT_REG] = 2,
@@ -289,6 +319,33 @@ static const unsigned int ad4170_sinc5_filt_fs_tbl[] = {
1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256,
};
+static const unsigned int ad4170_iout_pin_tbl[] = {
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(0),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(1),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(2),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(3),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(4),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(5),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(6),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(7),
+ AD4170_CURRENT_SRC_I_OUT_PIN_AIN(8),
+ AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(0),
+ AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(1),
+ AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(2),
+ AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(3),
+};
+
+static const unsigned int ad4170_iout_current_ua_tbl[] = {
+ 0, 10, 50, 100, 250, 500, 1000, 1500,
+};
+
+enum ad4170_sensor_type {
+ AD4170_WEIGH_SCALE_SENSOR = 0,
+ AD4170_RTD_SENSOR = 1,
+ AD4170_THERMOCOUPLE_SENSOR = 2,
+ AD4170_ADC_SENSOR = 3,
+};
+
struct ad4170_chip_info {
const char *name;
};
@@ -374,6 +431,7 @@ struct ad4170_state {
struct clk *ext_clk;
unsigned int clock_ctrl;
int gpio_fn[AD4170_NUM_GPIO_PINS];
+ unsigned int cur_src_pins[AD4170_NUM_CURRENT_SRC];
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
* to live in their own cache lines.
@@ -931,6 +989,19 @@ static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n,
int v_diff;
*ain_voltage = 0;
+ /*
+ * The voltage bias (vbias) sets the common-mode voltage of the channel
+ * to (AVDD + AVSS)/2. If provided, AVSS supply provides the magnitude
+ * (absolute value) of the negative voltage supplied to the AVSS pin.
+ * So, we do AVDD - AVSS to compute the DC voltage generated by the bias
+ * voltage generator.
+ */
+ if (st->pins_fn[ain_n] & AD4170_PIN_VBIAS) {
+ int v_diff = st->vrefs_uv[AD4170_AVDD_SUP] - st->vrefs_uv[AD4170_AVSS_SUP];
+ *ain_voltage = v_diff / 2;
+ return 0;
+ }
+
if (ain_n <= AD4170_CHAN_MAP_TEMP_SENSOR)
return 0;
@@ -985,6 +1056,19 @@ static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n,
}
}
+static int ad4170_validate_analog_input(struct ad4170_state *st, int pin)
+{
+ if (pin <= AD4170_MAX_ANALOG_PINS) {
+ if (st->pins_fn[pin] & AD4170_PIN_CURRENT_OUT)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Pin %d already used with fn %u.\n",
+ pin, st->pins_fn[pin]);
+
+ st->pins_fn[pin] |= AD4170_PIN_ANALOG_IN;
+ }
+ return 0;
+}
+
static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool com)
{
/* Check common-mode input pin is mapped to a special input. */
@@ -999,7 +1083,7 @@ static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool
"Invalid analog input pin number. %d\n",
pin);
- return 0;
+ return ad4170_validate_analog_input(st, pin);
}
/*
@@ -1765,6 +1849,304 @@ static int ad4170_gpio_init(struct iio_dev *indio_dev)
return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev);
}
+static int ad4170_validate_excitation_pin(struct ad4170_state *st, u32 pin)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned int i;
+
+ /* Check the pin number is valid */
+ for (i = 0; i < ARRAY_SIZE(ad4170_iout_pin_tbl); i++)
+ if (ad4170_iout_pin_tbl[i] == pin)
+ break;
+
+ if (i == ARRAY_SIZE(ad4170_iout_pin_tbl))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid excitation pin: %u\n",
+ pin);
+
+ /* Check the pin is available */
+ if (pin <= AD4170_MAX_ANALOG_PINS) {
+ if (st->pins_fn[pin] != AD4170_PIN_UNASIGNED)
+ return dev_err_probe(dev, -EINVAL,
+ "Pin %u already used with fn %u\n",
+ pin, st->pins_fn[pin]);
+
+ st->pins_fn[pin] |= AD4170_PIN_CURRENT_OUT;
+ } else {
+ unsigned int gpio = pin - AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(0);
+
+ if (st->gpio_fn[gpio] != AD4170_GPIO_UNASIGNED)
+ return dev_err_probe(dev, -EINVAL,
+ "GPIO %u already used with fn %u\n",
+ gpio, st->gpio_fn[gpio]);
+
+ st->gpio_fn[gpio] |= AD4170_GPIO_AC_EXCITATION;
+ }
+
+ return 0;
+}
+
+static int ad4170_validate_excitation_pins(struct ad4170_state *st,
+ u32 *exc_pins, int num_exc_pins)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < num_exc_pins; i++) {
+ unsigned int pin = exc_pins[i];
+
+ ret = ad4170_validate_excitation_pin(st, pin);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int ad4170_setup_current_src(struct ad4170_state *st,
+ struct fwnode_handle *child,
+ struct ad4170_setup *setup, u32 *exc_pins,
+ int num_exc_pins, int exc_cur, bool ac_excited)
+{
+ unsigned int exc_cur_pair, i;
+ unsigned int current_src = 0;
+ int ret;
+
+ for (i = 0; i < num_exc_pins; i++) {
+ unsigned int pin = exc_pins[i];
+
+ current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin);
+ current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, exc_cur);
+
+ ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(i),
+ current_src);
+ if (ret)
+ return ret;
+ }
+
+ if (!ac_excited)
+ return 0;
+
+ if (num_exc_pins < 2)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Current chopping requested but only one pin provided: %u\n",
+ exc_pins[0]);
+
+ /*
+ * Two use cases to handle here:
+ * - 2 pairs of excitation currents;
+ * - 1 pair of excitation currents.
+ */
+ if (num_exc_pins == 4) {
+ for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++) {
+ unsigned int pin = exc_pins[i];
+
+ if (st->cur_src_pins[i] != AD4170_CURRENT_SRC_DISABLED)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Unable to use 4 exc pins\n");
+
+ st->cur_src_pins[i] = pin;
+ }
+ } else {
+ /*
+ * Excitation current chopping is configured in pairs. Current
+ * sources IOUT0 and IOUT1 form pair 1, IOUT2 and IOUT3 make up
+ * pair 2. So, if current chopping was requested, check if the
+ * first end of the first pair of excitation currents is
+ * available. Try the next pair if IOUT0 has already been
+ * configured for another channel.
+ */
+ i = st->cur_src_pins[0] == AD4170_CURRENT_SRC_DISABLED ? 0 : 2;
+
+ if (st->cur_src_pins[i] != AD4170_CURRENT_SRC_DISABLED ||
+ st->cur_src_pins[i + 1] != AD4170_CURRENT_SRC_DISABLED)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Failed to setup current chopping\n");
+
+ st->cur_src_pins[i] = exc_pins[0];
+ st->cur_src_pins[i + 1] = exc_pins[1];
+
+ if (i == 0)
+ exc_cur_pair = AD4170_MISC_CHOP_IEXC_PAIR1;
+ else
+ exc_cur_pair = AD4170_MISC_CHOP_IEXC_PAIR2;
+ }
+
+ /*
+ * Configure excitation current chopping.
+ * Chop both pairs if using four excitation pins.
+ */
+ setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK,
+ num_exc_pins == 2 ?
+ exc_cur_pair :
+ AD4170_MISC_CHOP_IEXC_BOTH);
+
+ return 0;
+}
+
+static int ad4170_setup_bridge(struct ad4170_state *st,
+ struct fwnode_handle *child,
+ struct ad4170_setup *setup, u32 *exc_pins,
+ int num_exc_pins, int exc_cur, bool ac_excited)
+{
+ unsigned long gpio_mask;
+ int ret;
+
+ /*
+ * If a specific current is provided through
+ * adi,excitation-current-microamp, set excitation pins provided through
+ * adi,excitation-pins to excite the bridge circuit.
+ */
+ if (exc_cur > 0)
+ return ad4170_setup_current_src(st, child, setup, exc_pins,
+ num_exc_pins, exc_cur,
+ ac_excited);
+
+ /*
+ * Else, use predefined ACX1, ACX1 negated, ACX2, ACX2 negated signals
+ * to AC excite the bridge. Those signals are output on GPIO2, GPIO0,
+ * GPIO3, and GPIO1, respectively. If only two pins are specified for AC
+ * excitation, use ACX1 and ACX2 (GPIO2 and GPIO3).
+ *
+ * Also, to avoid any short-circuit condition when more than one channel
+ * is enabled, set GPIO2 and GPIO0 high, and set GPIO1 and GPIO3 low to
+ * DC excite the bridge whenever a channel without AC excitation is
+ * selected. That is needed because GPIO pins are controlled by the next
+ * highest priority GPIO function when a channel doesn't enable AC
+ * excitation. See datasheet Figure 113 Weigh Scale (AC Excitation) for
+ * an example circuit diagram.
+ */
+ if (num_exc_pins == 2) {
+ setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3);
+
+ gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK;
+ ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 3) |
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 2));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap,
+ AD4170_GPIO_OUTPUT_REG,
+ BIT(3) | BIT(2));
+ if (ret)
+ return ret;
+
+ st->gpio_fn[3] |= AD4170_GPIO_OUTPUT;
+ st->gpio_fn[2] |= AD4170_GPIO_OUTPUT;
+ } else {
+ setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x2);
+
+ gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK |
+ AD4170_GPIO_MODE_GPIO1_MSK | AD4170_GPIO_MODE_GPIO0_MSK;
+ ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 3) |
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 2) |
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 1) |
+ AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 0));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap,
+ AD4170_GPIO_OUTPUT_REG,
+ BIT(3) | BIT(2) | BIT(1) | BIT(0));
+ if (ret)
+ return ret;
+
+ st->gpio_fn[3] |= AD4170_GPIO_OUTPUT;
+ st->gpio_fn[2] |= AD4170_GPIO_OUTPUT;
+ st->gpio_fn[1] |= AD4170_GPIO_OUTPUT;
+ st->gpio_fn[0] |= AD4170_GPIO_OUTPUT;
+ }
+
+ return 0;
+}
+
+static int ad4170_setup_rtd(struct ad4170_state *st,
+ struct fwnode_handle *child,
+ struct ad4170_setup *setup, u32 *exc_pins,
+ int num_exc_pins, int exc_cur, bool ac_excited)
+{
+ return ad4170_setup_current_src(st, child, setup, exc_pins,
+ num_exc_pins, exc_cur, ac_excited);
+}
+
+static int ad4170_parse_external_sensor(struct ad4170_state *st,
+ struct fwnode_handle *child,
+ struct ad4170_setup *setup,
+ struct iio_chan_spec *chan, u8 s_type)
+{
+ unsigned int num_exc_pins, exc_cur, reg_val;
+ struct device *dev = &st->spi->dev;
+ u32 pins[2], exc_pins[4];
+ bool ac_excited, vbias;
+ unsigned int i;
+ int ret;
+
+ ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
+ ARRAY_SIZE(pins));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read sensor diff-channels\n");
+
+ chan->differential = true;
+ chan->channel = pins[0];
+ chan->channel2 = pins[1];
+
+ ac_excited = fwnode_property_read_bool(child, "adi,excitation-ac");
+
+ num_exc_pins = fwnode_property_count_u32(child, "adi,excitation-pins");
+ if (num_exc_pins != 1 && num_exc_pins != 2 && num_exc_pins != 4)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid number of excitation pins\n");
+
+ ret = fwnode_property_read_u32_array(child, "adi,excitation-pins",
+ exc_pins, num_exc_pins);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read adi,excitation-pins\n");
+
+ ret = ad4170_validate_excitation_pins(st, exc_pins, num_exc_pins);
+ if (ret)
+ return ret;
+
+ exc_cur = 0;
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp",
+ &exc_cur);
+ if (ret && s_type == AD4170_RTD_SENSOR)
+ return dev_err_probe(dev, ret,
+ "Failed to read adi,excitation-current-microamp\n");
+
+ for (i = 0; i < ARRAY_SIZE(ad4170_iout_current_ua_tbl); i++)
+ if (ad4170_iout_current_ua_tbl[i] == exc_cur)
+ break;
+
+ if (i == ARRAY_SIZE(ad4170_iout_current_ua_tbl))
+ return dev_err_probe(dev, ret,
+ "Invalid excitation current: %uuA\n",
+ exc_cur);
+
+ /* Get the excitation current configuration value */
+ exc_cur = ret;
+
+ if (s_type == AD4170_THERMOCOUPLE_SENSOR) {
+ vbias = fwnode_property_read_bool(child, "adi,vbias");
+ if (vbias) {
+ st->pins_fn[chan->channel2] |= AD4170_PIN_VBIAS;
+ reg_val = BIT(chan->channel2);
+ return regmap_write(st->regmap, AD4170_V_BIAS_REG,
+ reg_val);
+ }
+ }
+ if (s_type == AD4170_WEIGH_SCALE_SENSOR) {
+ ret = ad4170_setup_bridge(st, child, setup, exc_pins,
+ num_exc_pins, exc_cur, ac_excited);
+ } else {
+ ret = ad4170_setup_rtd(st, child, setup, exc_pins, num_exc_pins,
+ exc_cur, ac_excited);
+ }
+ return ret;
+}
+
static int ad4170_parse_reference(struct ad4170_state *st,
struct fwnode_handle *child,
struct ad4170_setup *setup)
@@ -1849,6 +2231,7 @@ static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
struct ad4170_state *st = iio_priv(indio_dev);
struct device *dev = &st->spi->dev;
struct ad4170_chan_info *chan_info;
+ u8 s_type = AD4170_ADC_SENSOR;
struct ad4170_setup *setup;
struct iio_chan_spec *chan;
unsigned int ch_reg;
@@ -1880,10 +2263,32 @@ static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
if (ret)
return ret;
- ret = ad4170_parse_adc_channel_type(dev, child, chan);
- if (ret < 0)
- return ret;
+ ret = fwnode_property_read_u8(child, "adi,sensor-type", &s_type);
+ if (!ret) {
+ if (s_type > AD4170_THERMOCOUPLE_SENSOR)
+ return dev_err_probe(dev, ret,
+ "Invalid adi,sensor-type: %u\n",
+ s_type);
+ }
+ switch (s_type) {
+ case AD4170_ADC_SENSOR:
+ ret = ad4170_parse_adc_channel_type(dev, child, chan);
+ if (ret)
+ return ret;
+ break;
+ case AD4170_WEIGH_SCALE_SENSOR:
+ case AD4170_THERMOCOUPLE_SENSOR:
+ case AD4170_RTD_SENSOR:
+ ret = ad4170_parse_external_sensor(st, child, setup, chan,
+ s_type);
+ if (ret)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
bipolar = fwnode_property_read_bool(child, "bipolar");
setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar);
if (bipolar)
@@ -2087,6 +2492,12 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
st->pins_fn[i] = AD4170_PIN_UNASIGNED;
+ for (i = 0; i < AD4170_NUM_GPIO_PINS; i++)
+ st->gpio_fn[i] = AD4170_GPIO_UNASIGNED;
+
+ for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++)
+ st->cur_src_pins[i] = AD4170_CURRENT_SRC_DISABLED;
+
/* On power on, device defaults to using SDO pin for data ready signal */
st->int_pin_sel = AD4170_INT_PIN_SDO;
ret = device_property_match_property_string(dev, "interrupt-names",
--
2.47.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
@ 2025-05-13 15:47 ` David Lechner
2025-05-16 15:45 ` Marcelo Schmitt
2025-05-21 8:41 ` Krzysztof Kozlowski
2025-05-25 10:11 ` Jonathan Cameron
2 siblings, 1 reply; 30+ messages in thread
From: David Lechner @ 2025-05-13 15:47 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On 5/13/25 7:33 AM, Marcelo Schmitt wrote:
> Add device tree documentation for AD4170 and similar sigma-delta ADCs.
> The AD4170 is a 24-bit, multichannel, sigma-delta ADC.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v2 -> v3
>
> [device tree changes]
> - Removed unneeded allOf.
> - Removed occurences of adi,sensor-type type re-declaration.
> - Created type for the AD4170 channels, allowing to avoid dt doc repetition.
>
> .../bindings/iio/adc/adi,ad4170.yaml | 544 ++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 551 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> new file mode 100644
> index 000000000000..0a06258b6631
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> @@ -0,0 +1,544 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/adi,ad4170.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD4170 and similar Analog to Digital Converters
> +
> +maintainers:
> + - Marcelo Schmitt <marcelo.schmitt@analog.com>
> +
> +description: |
> + Analog Devices AD4170 series of Sigma-delta Analog to Digital Converters.
> + Specifications can be found at:
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4190-4.pdf
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4195-4.pdf
> +
> +$ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +$defs:
> + ad4170-channel:
> + type: object
> + $ref: /schemas/iio/adc/adc.yaml#
> + description:
> + Common properties for configuring AD4170 channels.
> +
> + properties:
> + adi,reference-select:
> + description: |
> + Selects the reference source to use when converting on the specific
> + channel. Valid values are:
> + 0: Differential reference voltage REFIN+ - REFIN−.
> + 1: Differential reference voltage REFIN2+ - REFIN2−.
> + 2: Internal 2.5V referece (REFOUT) relative to AVSS.
> + 3: Analog supply voltage (AVDD) relative AVSS.
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1, 2, 3]
Using strings instead of int for this and most of the other custom enums here
would make them self-documenting and easier to use.
> +
> +
> + sensor-node:
> + type: object
> + $ref: '#/$defs/ad4170-channel'
> + description:
> + The AD4170 and similar designs have features to aid interfacing with weigh
> + scale, RTD, and thermocouple sensors. Each of those sensor types requires
> + either distinct wiring configuration or external circuitry for proper
> + sensor operation and can use different AD4170 functionality on their
> + setups. A key characteristic of those external sensors is that they must
> + be excited either by voltage supply or by AD4170 excitation signals. The
> + sensor can then be read through a pair of analog inputs. These properties
> + describe external sensor circuitry connected to the ADC.
> +
> + properties:
> + reg:
> + description:
> + Channel number. Connects the sensor to the channel with this number
> + of the device.
> + minimum: 1
> + maximum: 16
> +
> + diff-channels:
> + description:
> + Defines the ADC input pins used to read sensor data. Only regular
> + analog input pins can be used.
> + items:
> + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8]
> +
> + bipolar: true
> +
> + adi,sensor-type:
> + description: |
> + Type of sensor connected to the device. Depending on the sensor type
> + (weigh scale, RTD, or thermocouple) the values of sensor-node
> + properties have slightly different constraints. This property
> + specifies which particular external sensor is connected to the ADC so
> + the sensor-node properties can be properly parsed and verified. The
> + possible sensor types are:
> + 0: weigh scale;
> + 1: RTD;
> + 2: thermocouple.
> + $ref: /schemas/types.yaml#/definitions/uint8
This property seems reduandant since it has to match the node name.
i.e. weighscale@... is is always adi,sensor-type = <0>; and so on.
> +
> + adi,excitation-ac:
> + type: boolean
> + description:
> + Whether the external sensor has to be AC or DC excited.
Description could be more clear than when omitted, it is DC excited.
> +
> + adi,excitation-pins:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + description:
> + Pins used to excite the sensor or external circuit that contains the
> + sensor. Thermocouples and RTD sensors are excited either with one
> + current source or with a pair of current sources to minimize the
> + excitation current mismatch and the excitation current drift matching
> + on the ADC. E.g. <0>; <1>; <0 1>. Load cell weigh scales may be
> + excited with one current source, a pair of excitation currents, or two
> + pairs of excitation currents. When four pins are defined, the first
> + two values specify the first pair and the last ones specify the second
> + pair of excitation currents. E.g. <0>; <0 1>; <0 1 2 3>.
> + items:
> + minimum: 0
> + maximum: 20
> +
> + adi,excitation-current-microamp:
> + description:
> + Excitation current in microamperes to be output to each excitation pin
> + specified by adi,excitation-pins property. If not provided and
> + adi,excitation-ac is true, use predefined ACX1, ACX1 negated, ACX2,
> + and ACX2 negated signals to AC excite the bridge circuit. Those
> + singals are output on GPIO2, GPIO0, GPIO3, and GPIO1, respectively.
> + enum: [0, 10, 50, 100, 250, 500, 1000, 1500]
> + default: 0
> +
> + adi,power-down-switch-pin:
> + description:
> + Number of the GPIO used as power-down switch for the bridge circuit.
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1]
This isn't required, so what is the default if omitted?
> +
> + adi,vbias:
> + type: boolean
> + description:
> + For unbiased thermocouple applications, the voltage generated by the
> + thermocouple must be biased around some DC voltage. When present, this
> + property specifies a bias voltage of (AVDD + AVSS)/2 to be applied as
> + common-mode voltage for the sensor.
> +
> + required:
> + - reg
> + - diff-channels
> + - bipolar
> + - adi,sensor-type
> + - adi,reference-select
> +
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad4170
> + - adi,ad4190
> + - adi,ad4195
> +
> + avss-supply:
> + description:
> + Referece voltage supply for AVSS. If provided, describes the magnitude
s/Referece/Reference/
> + (absolute value) of the negative voltage supplied to the AVSS pin. Since
> + AVSS must be −2.625V minimum and 0V maximum, the declared supply voltage
> + must be between 0 and 2.65V. If not provided, AVSS is assumed to be at
> + system ground (0V).
> +
> + avdd-supply:
> + description:
> + A supply of 4.75V to 5.25V relative to AVSS that powers the chip (AVDD).
> +
> + iovdd-supply:
> + description: 1.7V to 5.25V reference supply to the serial interface (IOVDD).
> +
> + refin1p-supply:
> + description: REFIN+ supply that can be used as reference for conversion.
> +
> + refin1n-supply:
> + description: REFIN- supply that can be used as reference for conversion. If
> + provided, describes the magnitude (absolute value) of the negative voltage
> + supplied to the REFIN- pin.
> +
> + refin2p-supply:
> + description: REFIN2+ supply that can be used as reference for conversion.
> +
> + refin2n-supply:
> + description: REFIN2- supply that can be used as reference for conversion. If
> + provided, describes the magnitude (absolute value) of the negative voltage
> + supplied to the REFIN2- pin.
> +
> + spi-cpol: true
> +
> + spi-cpha: true
> +
> + interrupts:
> + maxItems: 1> +
> + interrupt-names:
> + description:
> + Specify which pin should be configured as Data Ready interrupt.
> + enum:
> + - sdo
> + - dig_aux1
> + default: sdo> +
> + clocks:
> + maxItems: 1
> + description:
> + Optional external clock source. Can specify either an external clock or
> + external crystal.
> +
> + clock-names:
> + enum:
> + - ext-clk
> + - xtal
> + default: ext-clk
Shouldn't there be a depedency that if clocks is given, then clock-names is requried.
> +
> + '#clock-cells':
> + const: 0
> +
> + clock-output-names:
> + maxItems: 1
And if #clock-cells is given then clocks is forbidden.
> +
> + gpio-controller: true
> +
> + "#gpio-cells":
> + const: 2
> + description: |
> + The first cell is for the GPIO number: 0 to 3.
> + The second cell takes standard GPIO flags.
> +
> + ldac-gpios:
> + description:
> + GPIO connected to DIG_AUX2 pin to be used as LDAC toggle to control the
> + transfer of data from the DAC_INPUT_A register to the DAC.
> + maxItems: 1
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> +patternProperties:
> + "^channel@[0-9a-f]$":
> + $ref: '#/$defs/ad4170-channel'
> + unevaluatedProperties: false
> + description:
> + Represents the external channels which are connected to the ADC.
> +
> + properties:
> + reg:
> + description:
> + The channel number.
> + minimum: 0
> + maximum: 15
> +
> + diff-channels:
> + description: |
> + This property is used for defining the inputs of a differential
> + voltage channel. The first value is the positive input and the second
> + value is the negative input of the channel.
> +
> + Besides the analog input pins AIN0 to AIN8, there are special inputs
> + that can be selected with the following values:
> + 17: Internal temperature sensor
> + 18: (AVDD-AVSS)/5
> + 19: (IOVDD-DGND)/5
> + 20: DAC output
> + 21: ALDO
> + 22: DLDO
> + 23: AVSS
> + 24: DGND
> + 25: REFIN+
> + 26: REFIN-
> + 27: REFIN2+
> + 28: REFIN2-
> + 29: REFOUT
> + For the internal temperature sensor, use the input number for both
> + inputs (i.e. diff-channels = <17 17>).
> + items:
> + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> + 26, 27, 28, 29]
A Header file with macros for these would be nice since it seems like we
have to use the higher-numbered ones a lot with the common-mode-channel
properties in the examples.
> +
> + single-channel: true
> +
> + common-mode-channel: true
> +
> + bipolar: true
> +
> + adi,buffered-positive:
> + description: |
> + Enable precharge buffer, full buffer, or skip reference buffering of
> + the positive voltage reference. Because the output impedance of the
> + source driving the voltage reference inputs may be dynamic, RC
> + combinations of those inputs can cause DC gain errors if the reference
> + inputs go unbuffered into the ADC. Enable reference buffering if the
> + provided reference source has dynamic high impedance output. Note the
> + absolute voltage allowed on positive reference inputs (REFIN+,
> + REFIN2+) is from AVSS − 50 mV to AVDD + 50 mV when the reference
> + buffers are disabled but narrows to AVSS to AVDD when reference
> + buffering is enabled or in precharge mode.
> + 0: Reference precharge buffer.
> + 1: Full Buffer.
> + 2: Bypass reference buffers (buffering disabled).
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1, 2]
> + default: 1
> +
> + adi,buffered-negative:
> + description: |
> + Enable precharge buffer, full buffer, or skip reference buffering of
> + the negative voltage reference. Because the output impedance of the
> + source driving the voltage reference inputs may be dynamic, RC
> + combinations of those inputs can cause DC gain errors if the reference
> + inputs go unbuffered into the ADC. Enable reference buffering if the
> + provided reference source has dynamic high impedance output. Note the
> + absolute voltage allowed on negative reference inputs (REFIN-,
> + REFIN2-) is from AVSS − 50 mV to AVDD + 50 mV when the reference
> + buffers are disabled but narrows to AVSS to AVDD when reference
> + buffering is enabled or in precharge mode.
> + 0: Reference precharge buffer.
> + 1: Full Buffer.
> + 2: Bypass reference buffers (buffering disabled).
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1, 2]
> + default: 1
Could make a $def for these too to reduce duplication.
Also another case where string type would make more sense.
> +
> + required:
> + - reg
reg is already required by adc.yaml
> +
> + oneOf:
> + - required: [single-channel]
Is there a default for common-mode-channel if it isn't required in this case?
> + properties:
> + diff-channels: false
> + - required: [diff-channels]
> + properties:
> + single-channel: false
> + common-mode-channel: false
> +
> + "^weighscale@":
> + $ref: '#/$defs/sensor-node'
> + unevaluatedProperties: false
> +
> + properties:
> + diff-channels: true
> + bipolar: true
> +
> + adi,sensor-type:
> + description: Weigh scale sensor.
> + const: 0
> +
> + adi,excitation-pins: true
> +
> + "^rtd@":
> + $ref: '#/$defs/sensor-node'
> + unevaluatedProperties: false
> +
> + properties:
> + diff-channels: true
> + bipolar: true
> +
> + adi,sensor-type:
> + description: RTD sensor.
> + const: 1
> +
> + adi,excitation-pins: true
> +
> + adi,excitation-current-microamp: true
> +
> + required:
> + - adi,excitation-pins
> + - adi,excitation-current-microamp
> +
> + "^thermocouple@":
> + $ref: '#/$defs/sensor-node'
> + unevaluatedProperties: false
> +
> + properties:
> + diff-channels: true
> + bipolar: true
> +
> + adi,sensor-type:
> + description: Thermocouple sensor.
> + const: 2
> +
> + required:
> + - adi,excitation-pins
> + - adi,excitation-current-microamp
> +
> +required:
> + - compatible
> + - reg
> + - avdd-supply
> + - iovdd-supply
> + - spi-cpol
> + - spi-cpha
> +
> +allOf:
> + # Some devices don't have integrated DAC
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - adi,ad4190
> + - adi,ad4195
> + then:
> + properties:
> + ldac-gpios: false
> +
> +unevaluatedProperties: false
> +
The patternProperties: section is so big, it would be nice to move these
before it so the are closer to the properties they actually affect.
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support
2025-05-13 12:35 ` [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support Marcelo Schmitt
@ 2025-05-13 16:59 ` David Lechner
0 siblings, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-05-13 16:59 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On 5/13/25 7:35 AM, Marcelo Schmitt wrote:
> The AD4170 chip can use an externally supplied clock at the XTAL2 pin, or
> an external crystal connected to the XTAL1 and XTAL2 pins. Alternatively,
> the AD4170 can provide its 16 MHz internal clock at the XTAL2 pin. Extend
> the AD4170 driver so it effectively uses the provided external clock, if
> any, or supplies its own clock as a clock provider.
Is support for CLKDIV intentionally omitted? Might be worth mentioning
if that is the case.
>
> Reviewed-by: Nuno Sá <nuno.sa@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
...
> @@ -329,6 +348,9 @@ struct ad4170_state {
> struct completion completion;
> int pins_fn[AD4170_NUM_ANALOG_PINS];
> u32 int_pin_sel;
> + struct clk_hw int_clk_hw;
> + struct clk *ext_clk;
This isn't used outside of ad4170_clock_select() so can be made a local variable there.
> + unsigned int clock_ctrl;
> /*
> * DMA (thus cache coherency maintenance) requires the transfer buffers
> * to live in their own cache lines.
> @@ -1656,6 +1678,120 @@ static int ad4170_parse_channels(struct iio_dev *indio_dev)
> return 0;
> }
>
> +static struct ad4170_state *clk_hw_to_ad4170(struct clk_hw *hw)
> +{
> + return container_of(hw, struct ad4170_state, int_clk_hw);
> +}
> +
> +static unsigned long ad4170_sel_clk(struct ad4170_state *st,
> + unsigned int clk_sel)
> +{
> + st->clock_ctrl &= ~AD4170_CLOCK_CTRL_CLOCKSEL_MSK;
> + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, clk_sel);
Do we need to claim direct mode here to avoid poking registers during
a buffered read?
> + return regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
> +}
> +
> +static unsigned long ad4170_clk_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + return AD4170_INT_CLOCK_16MHZ;
> +}
> +
> +static int ad4170_clk_output_is_enabled(struct clk_hw *hw)
> +{
> + struct ad4170_state *st = clk_hw_to_ad4170(hw);
> + u32 clk_sel;
> +
> + clk_sel = FIELD_GET(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, st->clock_ctrl);
> + return clk_sel == AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT;
> +}
> +
> +static int ad4170_clk_output_prepare(struct clk_hw *hw)
> +{
> + struct ad4170_state *st = clk_hw_to_ad4170(hw);
> +
> + return ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT);
> +}
> +
> +static void ad4170_clk_output_unprepare(struct clk_hw *hw)
> +{
> + struct ad4170_state *st = clk_hw_to_ad4170(hw);
> +
> + ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT);
> +}
> +
> +static const struct clk_ops ad4170_int_clk_ops = {
> + .recalc_rate = ad4170_clk_recalc_rate,
> + .is_enabled = ad4170_clk_output_is_enabled,
> + .prepare = ad4170_clk_output_prepare,
> + .unprepare = ad4170_clk_output_unprepare,
> +};
> +
> +static int ad4170_register_clk_provider(struct iio_dev *indio_dev)
> +{
> + struct ad4170_state *st = iio_priv(indio_dev);
> + struct device *dev = indio_dev->dev.parent;
> + struct clk_init_data init = {};
> + int ret;
> +
> + if (!IS_ENABLED(CONFIG_COMMON_CLK))
Driver depends on COMMON_CLK so isn't this dead code?
> + return 0;
> +
> + if (device_property_read_string(dev, "clock-output-names", &init.name)) {
> + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw",
> + dev_fwnode(dev));
> + if (!init.name)
> + return -ENOMEM;
> + }
> +
> + init.ops = &ad4170_int_clk_ops;
> +
> + st->int_clk_hw.init = &init;
> + ret = devm_clk_hw_register(dev, &st->int_clk_hw);
> + if (ret)
> + return ret;
> +
> + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> + &st->int_clk_hw);
> +}
> +
> +static int ad4170_clock_select(struct iio_dev *indio_dev)
> +{
> + struct ad4170_state *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
Would make more sense to move this inside of the if (ret < 0) instead of
writing over it later.
> + ret = device_property_match_property_string(dev, "clock-names",
> + ad4170_clk_sel,
> + ARRAY_SIZE(ad4170_clk_sel));
> + if (ret < 0) {
> + /* Use internal clock reference */
> + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
> + AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT);
Seems like we would want to start in the AD4170_CLOCK_CTRL_CLOCKSEL_INT state.
Also, could skip registering clock provider if #clock-cells is not present.
> + return ad4170_register_clk_provider(indio_dev);
> + }
> +
> + /* Use external clock reference */
> + st->ext_clk = devm_clk_get_enabled(dev, ad4170_clk_sel[ret]);
> + if (IS_ERR(st->ext_clk))
> + return dev_err_probe(dev, PTR_ERR(st->ext_clk),
> + "Failed to get external clock\n");
> +
> + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK,
> + AD4170_CLOCK_CTRL_CLOCKSEL_EXT + ret);
> +
> + st->mclk_hz = clk_get_rate(st->ext_clk);
> + if (st->mclk_hz < AD4170_EXT_CLOCK_MHZ_MIN ||
> + st->mclk_hz > AD4170_EXT_CLOCK_MHZ_MAX) {
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid external clock frequency %u\n",
> + st->mclk_hz);
> + }
> +
> + return 0;
> +}
> +
> static int ad4170_parse_firmware(struct iio_dev *indio_dev)
> {
> struct ad4170_state *st = iio_priv(indio_dev);
> @@ -1663,7 +1799,13 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
> int reg_data, ret;
> unsigned int i;
>
> - st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
> + ret = ad4170_clock_select(indio_dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to setup device clock\n");
> +
> + ret = regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl);
> + if (ret)
> + return ret;
Why not just do the regmap_write() in ad4170_clock_select() to keep it all
together?
>
> for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
> st->pins_fn[i] = AD4170_PIN_UNASIGNED;
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-13 15:47 ` David Lechner
@ 2025-05-16 15:45 ` Marcelo Schmitt
2025-05-16 16:06 ` David Lechner
2025-05-21 8:33 ` Krzysztof Kozlowski
0 siblings, 2 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-16 15:45 UTC (permalink / raw)
To: David Lechner
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel,
jic23, lars, Michael.Hennerich, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl
...
> > +
> > + properties:
> > + adi,reference-select:
> > + description: |
> > + Selects the reference source to use when converting on the specific
> > + channel. Valid values are:
> > + 0: Differential reference voltage REFIN+ - REFIN−.
> > + 1: Differential reference voltage REFIN2+ - REFIN2−.
> > + 2: Internal 2.5V referece (REFOUT) relative to AVSS.
> > + 3: Analog supply voltage (AVDD) relative AVSS.
> > + $ref: /schemas/types.yaml#/definitions/uint8
> > + enum: [0, 1, 2, 3]
> Using strings instead of int for this and most of the other custom enums here
> would make them self-documenting and easier to use.
The numbers match the values that are documented in the datasheet for each
option of voltage reference available to use with a channel. So we would be
using numbers mostly to define values of some unit and pin numbers (e.g. 100 for
the microamp property)? Not really excited about doing this change because I
think it will make the dtb a bit larger and the driver code a bit more lengthy,
but can do that for v4.
...
> > + adi,sensor-type:
> > + description: |
> > + Type of sensor connected to the device. Depending on the sensor type
> > + (weigh scale, RTD, or thermocouple) the values of sensor-node
> > + properties have slightly different constraints. This property
> > + specifies which particular external sensor is connected to the ADC so
> > + the sensor-node properties can be properly parsed and verified. The
> > + possible sensor types are:
> > + 0: weigh scale;
> > + 1: RTD;
> > + 2: thermocouple.
> > + $ref: /schemas/types.yaml#/definitions/uint8
> This property seems reduandant since it has to match the node name.
>
> i.e. weighscale@... is is always adi,sensor-type = <0>; and so on.
Yes, can we rely on node names I'll do that for v4.
...
> > +
> > + adi,power-down-switch-pin:
> > + description:
> > + Number of the GPIO used as power-down switch for the bridge circuit.
> > + $ref: /schemas/types.yaml#/definitions/uint8
> > + enum: [0, 1]
> This isn't required, so what is the default if omitted?
We don't care about it when the property is omitted.
Do we need a default even when the property is not required and we don't care
when it's not set?
...
> > + diff-channels:
> > + description: |
> > + This property is used for defining the inputs of a differential
> > + voltage channel. The first value is the positive input and the second
> > + value is the negative input of the channel.
> > +
> > + Besides the analog input pins AIN0 to AIN8, there are special inputs
> > + that can be selected with the following values:
> > + 17: Internal temperature sensor
> > + 18: (AVDD-AVSS)/5
> > + 19: (IOVDD-DGND)/5
> > + 20: DAC output
> > + 21: ALDO
> > + 22: DLDO
> > + 23: AVSS
> > + 24: DGND
> > + 25: REFIN+
> > + 26: REFIN-
> > + 27: REFIN2+
> > + 28: REFIN2-
> > + 29: REFOUT
> > + For the internal temperature sensor, use the input number for both
> > + inputs (i.e. diff-channels = <17 17>).
> > + items:
> > + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> > + 26, 27, 28, 29]
>
> A Header file with macros for these would be nice since it seems like we
> have to use the higher-numbered ones a lot with the common-mode-channel
> properties in the examples.
The RFC set had a header with macros for those numbers, but making dt properties
"look nice" was said to no be a reason to have binding headers.
https://lore.kernel.org/linux-iio/ikq55kcfu2lmxzeeobu4zwf67xypyikadnpycw2m4d7o6gvmi2@tkepvcvzqzoh/
Also, no other binding would use those values. So, we would have a header
specific for adi,ad4170?
>
> > +
> > + single-channel: true
> > +
> > + common-mode-channel: true
> > +
> > + bipolar: true
> > +
> > + adi,buffered-positive:
> > + description: |
> > + Enable precharge buffer, full buffer, or skip reference buffering of
> > + the positive voltage reference. Because the output impedance of the
> > + source driving the voltage reference inputs may be dynamic, RC
> > + combinations of those inputs can cause DC gain errors if the reference
> > + inputs go unbuffered into the ADC. Enable reference buffering if the
> > + provided reference source has dynamic high impedance output. Note the
> > + absolute voltage allowed on positive reference inputs (REFIN+,
> > + REFIN2+) is from AVSS − 50 mV to AVDD + 50 mV when the reference
> > + buffers are disabled but narrows to AVSS to AVDD when reference
> > + buffering is enabled or in precharge mode.
> > + 0: Reference precharge buffer.
> > + 1: Full Buffer.
> > + 2: Bypass reference buffers (buffering disabled).
> > + $ref: /schemas/types.yaml#/definitions/uint8
> > + enum: [0, 1, 2]
> > + default: 1
> > +
> > + adi,buffered-negative:
> > + description: |
> > + Enable precharge buffer, full buffer, or skip reference buffering of
> > + the negative voltage reference. Because the output impedance of the
> > + source driving the voltage reference inputs may be dynamic, RC
> > + combinations of those inputs can cause DC gain errors if the reference
> > + inputs go unbuffered into the ADC. Enable reference buffering if the
> > + provided reference source has dynamic high impedance output. Note the
> > + absolute voltage allowed on negative reference inputs (REFIN-,
> > + REFIN2-) is from AVSS − 50 mV to AVDD + 50 mV when the reference
> > + buffers are disabled but narrows to AVSS to AVDD when reference
> > + buffering is enabled or in precharge mode.
> > + 0: Reference precharge buffer.
> > + 1: Full Buffer.
> > + 2: Bypass reference buffers (buffering disabled).
> > + $ref: /schemas/types.yaml#/definitions/uint8
> > + enum: [0, 1, 2]
> > + default: 1
> Could make a $def for these too to reduce duplication.
I think so, but how? They are only documented here. I can merge them into a
single adi,buffered property. That will also reduce duplication.
>
> Also another case where string type would make more sense.
>
> > +
> > + required:
> > + - reg
>
> reg is already required by adc.yaml
>
> > +
> > + oneOf:
> > + - required: [single-channel]
>
> Is there a default for common-mode-channel if it isn't required in this case?
It should be required. Will make common-mode-channel required in this case.
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-16 15:45 ` Marcelo Schmitt
@ 2025-05-16 16:06 ` David Lechner
2025-05-21 8:33 ` Krzysztof Kozlowski
1 sibling, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-05-16 16:06 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel,
jic23, lars, Michael.Hennerich, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl
On 5/16/25 10:45 AM, Marcelo Schmitt wrote:
> ...
>>> +
>>> + properties:
>>> + adi,reference-select:
>>> + description: |
>>> + Selects the reference source to use when converting on the specific
>>> + channel. Valid values are:
>>> + 0: Differential reference voltage REFIN+ - REFIN−.
>>> + 1: Differential reference voltage REFIN2+ - REFIN2−.
>>> + 2: Internal 2.5V referece (REFOUT) relative to AVSS.
>>> + 3: Analog supply voltage (AVDD) relative AVSS.
>>> + $ref: /schemas/types.yaml#/definitions/uint8
>>> + enum: [0, 1, 2, 3]
>> Using strings instead of int for this and most of the other custom enums here
>> would make them self-documenting and easier to use.
>
> The numbers match the values that are documented in the datasheet for each
> option of voltage reference available to use with a channel. So we would be
> using numbers mostly to define values of some unit and pin numbers (e.g. 100 for
> the microamp property)? Not really excited about doing this change because I
> think it will make the dtb a bit larger and the driver code a bit more lengthy,
> but can do that for v4.
I don't think it is too bad since we have match_string() to convert the strings
to an enum value. So it would just be a matter of adding the string tables.
But I don't feel terribly strongly about it anyway.
> ...
>>> +
>>> + adi,power-down-switch-pin:
>>> + description:
>>> + Number of the GPIO used as power-down switch for the bridge circuit.
>>> + $ref: /schemas/types.yaml#/definitions/uint8
>>> + enum: [0, 1]
>> This isn't required, so what is the default if omitted?
>
> We don't care about it when the property is omitted.
> Do we need a default even when the property is not required and we don't care
> when it's not set?
Ah, in that case, maybe add a bit to the description to say that this
is omitted when there isn't a bridge circuit wired up.
>
> ...
>>> + diff-channels:
>>> + description: |
>>> + This property is used for defining the inputs of a differential
>>> + voltage channel. The first value is the positive input and the second
>>> + value is the negative input of the channel.
>>> +
>>> + Besides the analog input pins AIN0 to AIN8, there are special inputs
>>> + that can be selected with the following values:
>>> + 17: Internal temperature sensor
>>> + 18: (AVDD-AVSS)/5
>>> + 19: (IOVDD-DGND)/5
>>> + 20: DAC output
>>> + 21: ALDO
>>> + 22: DLDO
>>> + 23: AVSS
>>> + 24: DGND
>>> + 25: REFIN+
>>> + 26: REFIN-
>>> + 27: REFIN2+
>>> + 28: REFIN2-
>>> + 29: REFOUT
>>> + For the internal temperature sensor, use the input number for both
>>> + inputs (i.e. diff-channels = <17 17>).
>>> + items:
>>> + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24, 25,
>>> + 26, 27, 28, 29]
>>
>> A Header file with macros for these would be nice since it seems like we
>> have to use the higher-numbered ones a lot with the common-mode-channel
>> properties in the examples.
>
> The RFC set had a header with macros for those numbers, but making dt properties
> "look nice" was said to no be a reason to have binding headers.
>
> https://lore.kernel.org/linux-iio/ikq55kcfu2lmxzeeobu4zwf67xypyikadnpycw2m4d7o6gvmi2@tkepvcvzqzoh/
>
Hmm, OK I never got that complaint before. Although the headers I have made before
were defining arbitrary numbers for phandle cells, and not something from the
datasheet like this.
> Also, no other binding would use those values. So, we would have a header
> specific for adi,ad4170?
Yes.
>
>>
>>> +
>>> + single-channel: true
>>> +
>>> + common-mode-channel: true
>>> +
>>> + bipolar: true
>>> +
>>> + adi,buffered-positive:
>>> + description: |
>>> + Enable precharge buffer, full buffer, or skip reference buffering of
>>> + the positive voltage reference. Because the output impedance of the
>>> + source driving the voltage reference inputs may be dynamic, RC
>>> + combinations of those inputs can cause DC gain errors if the reference
>>> + inputs go unbuffered into the ADC. Enable reference buffering if the
>>> + provided reference source has dynamic high impedance output. Note the
>>> + absolute voltage allowed on positive reference inputs (REFIN+,
>>> + REFIN2+) is from AVSS − 50 mV to AVDD + 50 mV when the reference
>>> + buffers are disabled but narrows to AVSS to AVDD when reference
>>> + buffering is enabled or in precharge mode.
>>> + 0: Reference precharge buffer.
>>> + 1: Full Buffer.
>>> + 2: Bypass reference buffers (buffering disabled).
>>> + $ref: /schemas/types.yaml#/definitions/uint8
>>> + enum: [0, 1, 2]
>>> + default: 1
>>> +
>>> + adi,buffered-negative:
>>> + description: |
>>> + Enable precharge buffer, full buffer, or skip reference buffering of
>>> + the negative voltage reference. Because the output impedance of the
>>> + source driving the voltage reference inputs may be dynamic, RC
>>> + combinations of those inputs can cause DC gain errors if the reference
>>> + inputs go unbuffered into the ADC. Enable reference buffering if the
>>> + provided reference source has dynamic high impedance output. Note the
>>> + absolute voltage allowed on negative reference inputs (REFIN-,
>>> + REFIN2-) is from AVSS − 50 mV to AVDD + 50 mV when the reference
>>> + buffers are disabled but narrows to AVSS to AVDD when reference
>>> + buffering is enabled or in precharge mode.
>>> + 0: Reference precharge buffer.
>>> + 1: Full Buffer.
>>> + 2: Bypass reference buffers (buffering disabled).
>>> + $ref: /schemas/types.yaml#/definitions/uint8
>>> + enum: [0, 1, 2]
>>> + default: 1
>> Could make a $def for these too to reduce duplication.
>
> I think so, but how? They are only documented here. I can merge them into a
> single adi,buffered property. That will also reduce duplication.
You already have $defs:, so just add precharge-buffer: there with
the description:, etc., then here, just:
adi,buffered-positive:
$ref: '#/$defs/precharge-buffer'
adi,buffered-negative:
$ref: '#/$defs/precharge-buffer'
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support
2025-05-13 12:36 ` [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support Marcelo Schmitt
@ 2025-05-20 17:06 ` Bartosz Golaszewski
0 siblings, 0 replies; 30+ messages in thread
From: Bartosz Golaszewski @ 2025-05-20 17:06 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, jic23, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, marcelo.schmitt1
On Tue, May 13, 2025 at 2:36 PM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> The AD4170 has four multifunctional pins that can be used as GPIOs. The
> GPIO functionality can be accessed when the AD4170 chip is not busy
> performing continuous data capture or handling any other register
> read/write request. Also, the AD4170 does not provide any interrupt based
> on GPIO pin states so AD4170 GPIOs can't be used as interrupt sources.
>
> Implement gpio_chip callbacks to make AD4170 GPIO pins controllable through
> the gpiochip interface.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v2 -> v3
> - Defined masks for updating GPIO mode register.
> - Replaced regmap_clear/set_bits() by regmap_update_bits() to set GPIO direction.
> - Removed GPIO direction check before setting GPIO output values.
> - Made use of regmap_assign_bits() to set GPIO output reg bits.
> - Made value to be set as GPIO output state be either 0 or 1.
> - No longer locking on state mutex on GPIO set since GPIO output should not
> conflict with other direct mode functionality (e.g. single-shot read).
>
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad4170.c | 224 ++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 224 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 6e4b14243599..a328f03eea34 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -78,6 +78,7 @@ config AD4170
> select IIO_BUFFER
> select IIO_TRIGGERED_BUFFER
> depends on COMMON_CLK
> + select GPIOLIB
In general GPIOLIB should be depended on, not selected.
The rest looks good to me so with that:
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-16 15:45 ` Marcelo Schmitt
2025-05-16 16:06 ` David Lechner
@ 2025-05-21 8:33 ` Krzysztof Kozlowski
1 sibling, 0 replies; 30+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-21 8:33 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: David Lechner, Marcelo Schmitt, linux-iio, devicetree, linux-gpio,
linux-kernel, jic23, lars, Michael.Hennerich, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl
On Fri, May 16, 2025 at 12:45:05PM GMT, Marcelo Schmitt wrote:
> ...
> > > +
> > > + properties:
> > > + adi,reference-select:
> > > + description: |
> > > + Selects the reference source to use when converting on the specific
> > > + channel. Valid values are:
> > > + 0: Differential reference voltage REFIN+ - REFIN−.
> > > + 1: Differential reference voltage REFIN2+ - REFIN2−.
> > > + 2: Internal 2.5V referece (REFOUT) relative to AVSS.
> > > + 3: Analog supply voltage (AVDD) relative AVSS.
> > > + $ref: /schemas/types.yaml#/definitions/uint8
> > > + enum: [0, 1, 2, 3]
> > Using strings instead of int for this and most of the other custom enums here
> > would make them self-documenting and easier to use.
>
> The numbers match the values that are documented in the datasheet for each
> option of voltage reference available to use with a channel. So we would be
> using numbers mostly to define values of some unit and pin numbers (e.g. 100 for
> the microamp property)? Not really excited about doing this change because I
> think it will make the dtb a bit larger and the driver code a bit more lengthy,
> but can do that for v4.
You must use what is already there, the same property, the same type.
And since existing property was integer, I am unhappy that Dumitru
Ceclan decided to go with a string in commit 3d50d03f2194 ("dt-bindings:
adc: add AD7173"), although that is probably on us.
So now there is a mess and we are going to have either exception or
warnings for these devices (always), because schema expects exactly one
type.
So instead of coming or proposing the third type, just use what was
already in the bindings.
Same for every other property here, really, don't come with your own
custom stuff.
>
> ...
> > > + adi,sensor-type:
> > > + description: |
> > > + Type of sensor connected to the device. Depending on the sensor type
> > > + (weigh scale, RTD, or thermocouple) the values of sensor-node
> > > + properties have slightly different constraints. This property
> > > + specifies which particular external sensor is connected to the ADC so
> > > + the sensor-node properties can be properly parsed and verified. The
> > > + possible sensor types are:
> > > + 0: weigh scale;
> > > + 1: RTD;
> > > + 2: thermocouple.
> > > + $ref: /schemas/types.yaml#/definitions/uint8
> > This property seems reduandant since it has to match the node name.
> >
> > i.e. weighscale@... is is always adi,sensor-type = <0>; and so on.
>
> Yes, can we rely on node names I'll do that for v4.
Again, don't come with own new way of doing things. What node name?
Isn't this always "channel"?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
2025-05-13 15:47 ` David Lechner
@ 2025-05-21 8:41 ` Krzysztof Kozlowski
2025-05-22 15:07 ` Marcelo Schmitt
2025-05-25 10:11 ` Jonathan Cameron
2 siblings, 1 reply; 30+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-21 8:41 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, jic23, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, May 13, 2025 at 09:33:40AM GMT, Marcelo Schmitt wrote:
> Add device tree documentation for AD4170 and similar sigma-delta ADCs.
> The AD4170 is a 24-bit, multichannel, sigma-delta ADC.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v2 -> v3
>
> [device tree changes]
> - Removed unneeded allOf.
> - Removed occurences of adi,sensor-type type re-declaration.
> - Created type for the AD4170 channels, allowing to avoid dt doc repetition.
>
> .../bindings/iio/adc/adi,ad4170.yaml | 544 ++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 551 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> new file mode 100644
> index 000000000000..0a06258b6631
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> @@ -0,0 +1,544 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/adi,ad4170.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD4170 and similar Analog to Digital Converters
> +
> +maintainers:
> + - Marcelo Schmitt <marcelo.schmitt@analog.com>
> +
> +description: |
> + Analog Devices AD4170 series of Sigma-delta Analog to Digital Converters.
> + Specifications can be found at:
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4190-4.pdf
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4195-4.pdf
> +
> +$ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +$defs:
> + ad4170-channel:
> + type: object
> + $ref: /schemas/iio/adc/adc.yaml#
> + description:
> + Common properties for configuring AD4170 channels.
> +
> + properties:
> + adi,reference-select:
> + description: |
> + Selects the reference source to use when converting on the specific
> + channel. Valid values are:
> + 0: Differential reference voltage REFIN+ - REFIN−.
> + 1: Differential reference voltage REFIN2+ - REFIN2−.
> + 2: Internal 2.5V referece (REFOUT) relative to AVSS.
> + 3: Analog supply voltage (AVDD) relative AVSS.
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1, 2, 3]
> +
> +
Remove excessive lines, also in other places.
> + sensor-node:
> + type: object
> + $ref: '#/$defs/ad4170-channel'
I do not understand this binding. channel@ node is a channel and sensors
like rtd@ is also channel but also sensor. What is the point of channel@
which is not a sensor?
> + description:
> + The AD4170 and similar designs have features to aid interfacing with weigh
> + scale, RTD, and thermocouple sensors. Each of those sensor types requires
> + either distinct wiring configuration or external circuitry for proper
> + sensor operation and can use different AD4170 functionality on their
> + setups. A key characteristic of those external sensors is that they must
> + be excited either by voltage supply or by AD4170 excitation signals. The
> + sensor can then be read through a pair of analog inputs. These properties
> + describe external sensor circuitry connected to the ADC.
> +
> + properties:
> + reg:
> + description:
> + Channel number. Connects the sensor to the channel with this number
> + of the device.
> + minimum: 1
> + maximum: 16
...
> > +patternProperties:
> + "^channel@[0-9a-f]$":
> + $ref: '#/$defs/ad4170-channel'
> + unevaluatedProperties: false
> + description:
> + Represents the external channels which are connected to the ADC.
> +
> + properties:
> + reg:
> + description:
> + The channel number.
> + minimum: 0
> + maximum: 15
> +
> + diff-channels:
> + description: |
> + This property is used for defining the inputs of a differential
> + voltage channel. The first value is the positive input and the second
> + value is the negative input of the channel.
> +
> + Besides the analog input pins AIN0 to AIN8, there are special inputs
> + that can be selected with the following values:
> + 17: Internal temperature sensor
> + 18: (AVDD-AVSS)/5
> + 19: (IOVDD-DGND)/5
> + 20: DAC output
> + 21: ALDO
> + 22: DLDO
> + 23: AVSS
> + 24: DGND
> + 25: REFIN+
> + 26: REFIN-
> + 27: REFIN2+
> + 28: REFIN2-
> + 29: REFOUT
> + For the internal temperature sensor, use the input number for both
> + inputs (i.e. diff-channels = <17 17>).
> + items:
> + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> + 26, 27, 28, 29]
> +
> + single-channel: true
> +
> + common-mode-channel: true
> +
> + bipolar: true
Why all these 'true' are needed here and everywhere else?
> +
> + adi,buffered-positive:
> + description: |
> + Enable precharge buffer, full buffer, or skip reference buffering of
> + the positive voltage reference. Because the output impedance of the
> + source driving the voltage reference inputs may be dynamic, RC
> + combinations of those inputs can cause DC gain errors if the reference
> + inputs go unbuffered into the ADC. Enable reference buffering if the
> + provided reference source has dynamic high impedance output. Note the
> + absolute voltage allowed on positive reference inputs (REFIN+,
> + REFIN2+) is from AVSS − 50 mV to AVDD + 50 mV when the reference
> + buffers are disabled but narrows to AVSS to AVDD when reference
> + buffering is enabled or in precharge mode.
> + 0: Reference precharge buffer.
> + 1: Full Buffer.
> + 2: Bypass reference buffers (buffering disabled).
> + $ref: /schemas/types.yaml#/definitions/uint8
> + enum: [0, 1, 2]
> + default: 1
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-21 8:41 ` Krzysztof Kozlowski
@ 2025-05-22 15:07 ` Marcelo Schmitt
2025-05-25 10:05 ` Jonathan Cameron
0 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-22 15:07 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel,
jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl
...
>
> > + sensor-node:
> > + type: object
> > + $ref: '#/$defs/ad4170-channel'
>
> I do not understand this binding. channel@ node is a channel and sensors
> like rtd@ is also channel but also sensor. What is the point of channel@
> which is not a sensor?
>
The sensor node is meant to describe a channel that has extra setup
configuration. For example, a common ADC channel could look like this
-------- +VREF ------ +-------------------+
´ ` ´ ` / |
/ \ / \ / --- < IN+ |
`-´ `-´ | |
-------- -VREF ------ | |
| ADC |
-------- +VREF ------ | |
´ ` ´ ` | |
\ / \ / \ --- < IN- |
`-´ `-´ \ +VREF -VREF |
-------- -VREF ------ +-------------------+
^ ^
| +---- External -VREF
External +VREF
The the channel@ node for that would look like
adc@0 {
...
channel@0 {
reg = <0>;
bipolar;
diff-channels = <0 1>;
};
};
Though, some sigma-delta ADCs (including AD4170) are fancy and have features
that relate to what is connected to the ADC inputs. For example, an RTD would
be connected like
External +VREF
|
+----------------------------------|-------+---- External -VREF
| +----------------------+ |
+---Rref----+ +-----v-------v----+
| | / REFIN+ REFIN- |
+---+-----------|------------> < IN+ |
| | | |
3-wire RTD | | ADC |
| | | |
+--------------------+-------> < IN- |
| | | \ GPIO2 GPIO3 |
v | | +-------v-----v----+
GND | +-------------------+ |
| <--- IOUT1 |
+------------------------------+
<--- IOUT0
A better drawing can be found in AD4170 datasheet Figure 115. 3-Wire RTD Application.
https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf#unique_151_Connect_42_ID10430
Since the RTD sensor requires additional hardware connections, the proposed
dt-binding describes those differently, e.g.
adc@0 {
...
rtd@0 {
reg = <0>;
bipolar;
diff-channels = <0 1>;
adi,sensor-type = /bits/ 8 <1>;
adi,reference-select = <0>;
adi,excitation-pins = <19 20>;
adi,excitation-current-microamp = <500>;
};
That allows the ADC chip to be configured to provide the excitation signals to
properly handle the RTD sensor. Because the hardware connections and
operation requirements vary among those external sensor types (RTDs,
thermocouples, load cell weigh scales), some properties are only applicable to
specific sensor types. Also, because those are extra connections, the related
properties are not meaningful to typical ADC channels.
Though, the supported features are not different from those described in
Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml.
In that binding, the properties related to external sensor support are set
on the device node (not related to any channel). The binding proposed on AD4170
RFC patch was more similar to adi,ad4130.yaml and somehow avoiding to introduce
custom/new properties, but that lead to a lot of duplication.
The currently proposed dt-binding for ad4170 avoids repetition and, to some
degree, tries to provide better description of connected bridge sensors and
their related properties as that seemed to be one of Jonathan's suggestions
to the RFC version.
https://lore.kernel.org/linux-iio/20241219140353.787ffccc@jic23-huawei/
Even though I'm naturally biased in favor of my own code, I see this is fairly
different from bindings describing the exact same features and that can lead
to confusion. I'll use the same types for properties that describe the same
things/features. Would it also be preferred to drop the defs and just have
channel nodes?
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-22 15:07 ` Marcelo Schmitt
@ 2025-05-25 10:05 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:05 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Krzysztof Kozlowski, Marcelo Schmitt, linux-iio, devicetree,
linux-gpio, linux-kernel, lars, Michael.Hennerich, dlechner,
nuno.sa, andy, robh, krzk+dt, conor+dt, linus.walleij, brgl
On Thu, 22 May 2025 12:07:41 -0300
Marcelo Schmitt <marcelo.schmitt1@gmail.com> wrote:
> ...
> >
> > > + sensor-node:
> > > + type: object
> > > + $ref: '#/$defs/ad4170-channel'
> >
> > I do not understand this binding. channel@ node is a channel and sensors
> > like rtd@ is also channel but also sensor. What is the point of channel@
> > which is not a sensor?
> >
> The sensor node is meant to describe a channel that has extra setup
> configuration. For example, a common ADC channel could look like this
>
> -------- +VREF ------ +-------------------+
> ´ ` ´ ` / |
> / \ / \ / --- < IN+ |
> `-´ `-´ | |
> -------- -VREF ------ | |
> | ADC |
> -------- +VREF ------ | |
> ´ ` ´ ` | |
> \ / \ / \ --- < IN- |
> `-´ `-´ \ +VREF -VREF |
> -------- -VREF ------ +-------------------+
> ^ ^
> | +---- External -VREF
> External +VREF
>
> The the channel@ node for that would look like
> adc@0 {
> ...
> channel@0 {
> reg = <0>;
> bipolar;
> diff-channels = <0 1>;
> };
> };
>
> Though, some sigma-delta ADCs (including AD4170) are fancy and have features
> that relate to what is connected to the ADC inputs. For example, an RTD would
> be connected like
>
> External +VREF
> |
> +----------------------------------|-------+---- External -VREF
> | +----------------------+ |
> +---Rref----+ +-----v-------v----+
> | | / REFIN+ REFIN- |
> +---+-----------|------------> < IN+ |
> | | | |
> 3-wire RTD | | ADC |
> | | | |
> +--------------------+-------> < IN- |
> | | | \ GPIO2 GPIO3 |
> v | | +-------v-----v----+
> GND | +-------------------+ |
> | <--- IOUT1 |
> +------------------------------+
> <--- IOUT0
>
> A better drawing can be found in AD4170 datasheet Figure 115. 3-Wire RTD Application.
> https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf#unique_151_Connect_42_ID10430
>
> Since the RTD sensor requires additional hardware connections, the proposed
> dt-binding describes those differently, e.g.
>
> adc@0 {
> ...
> rtd@0 {
> reg = <0>;
> bipolar;
> diff-channels = <0 1>;
> adi,sensor-type = /bits/ 8 <1>;
> adi,reference-select = <0>;
> adi,excitation-pins = <19 20>;
> adi,excitation-current-microamp = <500>;
> };
>
> That allows the ADC chip to be configured to provide the excitation signals to
> properly handle the RTD sensor. Because the hardware connections and
> operation requirements vary among those external sensor types (RTDs,
> thermocouples, load cell weigh scales), some properties are only applicable to
> specific sensor types. Also, because those are extra connections, the related
> properties are not meaningful to typical ADC channels.
>
> Though, the supported features are not different from those described in
> Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml.
> In that binding, the properties related to external sensor support are set
> on the device node (not related to any channel). The binding proposed on AD4170
> RFC patch was more similar to adi,ad4130.yaml and somehow avoiding to introduce
> custom/new properties, but that lead to a lot of duplication.
>
> The currently proposed dt-binding for ad4170 avoids repetition and, to some
> degree, tries to provide better description of connected bridge sensors and
> their related properties as that seemed to be one of Jonathan's suggestions
> to the RFC version.
> https://lore.kernel.org/linux-iio/20241219140353.787ffccc@jic23-huawei/
I don't always make good suggestions :( DT maintainers have more experience
in making coherent and predictable bindings than I do.
One of the big problems is that we are fishing in the dark when trying to
make up 'generic' bindings. Sometimes they just end up not extending well
at all.
>
> Even though I'm naturally biased in favor of my own code, I see this is fairly
> different from bindings describing the exact same features and that can lead
> to confusion. I'll use the same types for properties that describe the same
> things/features. Would it also be preferred to drop the defs and just have
> channel nodes?
If we did do channel nodes only. We have various options.
1) constraints against adi,sensor-types (which I think should probably be an enum)
That should look similar to the node name constraints.
2) constrain only via documentation. (I don't like this at all)
3) have no constraints at all and rely on documenting what makes sense.
I'm not entirely clear on whether we can ignore the sensor type etc and
provide properties to control more directly everything to do with it? So the
excitation types etc
Jonathan
>
> Thanks,
> Marcelo
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
2025-05-13 15:47 ` David Lechner
2025-05-21 8:41 ` Krzysztof Kozlowski
@ 2025-05-25 10:11 ` Jonathan Cameron
2025-05-26 21:59 ` Marcelo Schmitt
2 siblings, 1 reply; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:11 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 13 May 2025 09:33:40 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> Add device tree documentation for AD4170 and similar sigma-delta ADCs.
> The AD4170 is a 24-bit, multichannel, sigma-delta ADC.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
Hi Marcelo,
A few additional comments from me.
J
> ---
> Change log v2 -> v3
>
> [device tree changes]
> - Removed unneeded allOf.
> - Removed occurences of adi,sensor-type type re-declaration.
> - Created type for the AD4170 channels, allowing to avoid dt doc repetition.
>
> .../bindings/iio/adc/adi,ad4170.yaml | 544 ++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 551 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> new file mode 100644
> index 000000000000..0a06258b6631
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
> @@ -0,0 +1,544 @@
> + sensor-node:
...
> +
> + adi,sensor-type:
> + description: |
> + Type of sensor connected to the device. Depending on the sensor type
> + (weigh scale, RTD, or thermocouple) the values of sensor-node
> + properties have slightly different constraints. This property
> + specifies which particular external sensor is connected to the ADC so
> + the sensor-node properties can be properly parsed and verified. The
> + possible sensor types are:
> + 0: weigh scale;
> + 1: RTD;
> + 2: thermocouple.
> + $ref: /schemas/types.yaml#/definitions/uint8
I think this should be an enum of strings. That will give us a clean
way to extend it for other sensor types in future.
> + required:
> + - reg
> + - diff-channels
> + - bipolar
> + - adi,sensor-type
> + - adi,reference-select
> +
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad4170
> + - adi,ad4190
> + - adi,ad4195
>
> +
> + interrupts:
> + maxItems: 1
What if they are both wired? At that point shouldn't software
make up it's mind which to use?
> +
> + interrupt-names:
> + description:
> + Specify which pin should be configured as Data Ready interrupt.
> + enum:
> + - sdo
> + - dig_aux1
> + default: sdo
> +
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 02/10] iio: adc: Add basic support for AD4170
2025-05-13 12:34 ` [PATCH v3 02/10] iio: adc: Add basic support for AD4170 Marcelo Schmitt
@ 2025-05-25 10:36 ` Jonathan Cameron
2025-05-26 10:21 ` Nuno Sá
1 sibling, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:36 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, Ana-Maria Cusco,
lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 13 May 2025 09:34:00 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> From: Ana-Maria Cusco <ana-maria.cusco@analog.com>
>
> The AD4170 is a multichannel, low noise, 24-bit precision sigma-delta
> analog to digital converter. The AD4170 design offers a flexible data
> aquisition solution with crosspoint multiplexed analog inputs, configurable
> ADC voltage reference inputs, ultra-low noise integrated PGA, digital
> filtering, wide range of configurable output data rates, internal
> oscillator and temperature sensor, four GPIOs, and integrated features for
> interfacing with load cell weigh scales, RTD, and thermocouple sensors.
>
> Add basic support for the AD4170 ADC with the following features:
> - Single-shot read.
> - Analog front end PGA configuration.
> - Differential and pseudo-differential input configuration.
>
> Signed-off-by: Ana-Maria Cusco <ana-maria.cusco@analog.com>
> Co-developed-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
A few minor things inline.
J
> diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
> new file mode 100644
> index 000000000000..bf19b31095ee
> --- /dev/null
> +++ b/drivers/iio/adc/ad4170.c
> +
> +/*
> + * Verifies whether the channel configuration is valid by checking the provided
> + * input type, polarity, and voltage references result in a sane input range.
> + * Returns negative error code on failure.
> + */
> +static int ad4170_get_input_range(struct ad4170_state *st,
> + struct iio_chan_spec const *chan,
> + unsigned int ch_reg, unsigned int ref_sel)
> +{
> + bool bipolar = chan->scan_type.sign == 's';
> + struct device *dev = &st->spi->dev;
> + int refp, refn, ain_voltage, ret;
> +
> + switch (ref_sel) {
> + case AD4170_REF_REFIN1:
> + if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV ||
> + st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV)
> + return dev_err_probe(dev, -ENODEV,
> + "REFIN± selected but not provided\n");
> +
> + refp = st->vrefs_uv[AD4170_REFIN1P_SUP];
> + refn = st->vrefs_uv[AD4170_REFIN1N_SUP];
> + break;
> + case AD4170_REF_REFIN2:
> + if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV ||
> + st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV)
> + return dev_err_probe(dev, -ENODEV,
> + "REFIN2± selected but not provided\n");
> +
> + refp = st->vrefs_uv[AD4170_REFIN2P_SUP];
> + refn = st->vrefs_uv[AD4170_REFIN2N_SUP];
> + break;
> + case AD4170_REF_AVDD:
> + refp = st->vrefs_uv[AD4170_AVDD_SUP];
> + refn = st->vrefs_uv[AD4170_AVSS_SUP];
> + break;
> + case AD4170_REF_REFOUT:
> + /* REFOUT is 2.5 V relative to AVSS */
> + refp = st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V;
> + refn = st->vrefs_uv[AD4170_AVSS_SUP];
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /*
> + * Find out the analog input range from the channel type, polarity, and
> + * voltage reference selection.
> + * AD4170 channels are either differential or pseudo-differential.
> + * Diff input voltage range: −VREF/gain to +VREF/gain (datasheet page 6)
> + * Pseudo-diff input voltage range: 0 to VREF/gain (datasheet page 6)
> + */
> + if (chan->differential) {
> + if (!bipolar)
> + return dev_err_probe(&st->spi->dev, -EINVAL,
dev
> + "Channel %u differential unipolar\n",
> + ch_reg);
> +
> + /*
> + * Differential bipolar channel.
> + * avss-supply is never above 0V.
> + * Assuming refin1n-supply not above 0V.
> + * Assuming refin2n-supply not above 0V.
> + */
> + return refp + abs(refn);
> + }
> + /*
> + * Some configurations can lead to invalid setups.
> + * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS),
> + * and pseudo-diff channel configuration set, then the input range
> + * should go from 0V to +VREF (single-ended - datasheet pg 10), but
> + * REFOUT/AVSS range would be -2.5V to 0V.
> + * Check the positive reference is higher than 0V for pseudo-diff
> + * channels.
> + */
> + if (refp <= 0)
> + return dev_err_probe(&st->spi->dev, -EINVAL,
dev
> + "REF+ <= GND for pseudo-diff chan %u\n",
> + ch_reg);
> +
> + if (bipolar)
> + return refp;
> +
> + /*
> + * Pseudo-differential unipolar channel.
> + * Input expected to swing from IN- to +VREF.
> + */
> + ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ain_voltage);
> + if (ret)
> + return ret;
> +
> + if (refp - ain_voltage <= 0)
> + return dev_err_probe(&st->spi->dev, -EINVAL,
dev
> + "Negative input >= REF+ for pseudo-diff chan %u\n",
> + ch_reg);
> +
> + return refp - ain_voltage;
> +}
> +static int ad4170_parse_reference(struct ad4170_state *st,
> + struct fwnode_handle *child,
> + struct ad4170_setup *setup)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> + u8 aux;
> +
> + /* Optional positive reference buffering, if omitted we use the default */
I'd drop the "if omitted" part as the next line makes that clear.
> + aux = AD4170_REF_BUF_FULL; /* Default to full precharge buffer enabled. */
> + ret = fwnode_property_read_u8(child, "adi,buffered-positive", &aux);
> + if (!ret) {
> + if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS)
Given default is within these limits (I assume!), can simplified as:
aux = AD4170_REF_BUF_FULL;
fwnode_property_read_u8(child, "adi,buffered-positive", &aux);
if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS)
return dev_err_probe(dev, -EINVAL,
"Invalid adi,buffered-positive: %u\n", aux);
setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, aux);
> + aux);
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid adi,buffered-positive: %u\n",
> + aux);
> + }
> + setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, aux);
> +
> + /* Optional negative reference buffering, if omitted we use the default */
> + aux = AD4170_REF_BUF_FULL; /* Default to full precharge buffer enabled. */
Similar refactor to above applies here and dropping the obvious what happens
if omitted comment.
> + ret = fwnode_property_read_u8(child, "adi,buffered-negative", &aux);
> + if (!ret) {
> + if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS)
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid adi,buffered-negative: %u\n",
> + aux);
> + }
> + setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_M_MSK, aux);
> +
> + /* Optional voltage reference selection, if omitted we use the default */
> + aux = AD4170_REF_REFOUT; /* Default reference selection. */
And here.
> + ret = fwnode_property_read_u8(child, "adi,reference-select", &aux);
> + if (!ret) {
> + if (aux > AD4170_REF_AVDD)
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid reference selected %u\n",
> + aux);
> + }
> + setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, aux);
> +
> + return 0;
> +}
> +
> +static int ad4170_parse_adc_channel_type(struct device *dev,
> + struct fwnode_handle *child,
> + struct iio_chan_spec *chan)
> +{
> + u32 pins[2];
> + int ret, ret2;
> +
> + /* Parse pseudo-differential channel configuration */
> + ret = fwnode_property_read_u32(child, "single-channel", &pins[0]);
> + ret2 = fwnode_property_read_u32(child, "common-mode-channel", &pins[1]);
> + if (!ret && ret2)
> + return dev_err_probe(dev, ret,
> + "single-ended channels must define common-mode-channel\n");
ret == 0 so that will report success.
Move the ret2 logic down into this (!ret) statement that comes next then you
can just use ret and avoid this sort of issue. (Likely smatch would have
caught this but better to never have a bug report + fix :)
> + if (!ret) {
> + chan->differential = false;
> + chan->channel = pins[0];
> + chan->channel2 = pins[1];
> + return 0;
> + }
> +
> + /* Parse differential channel configuration */
> + ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
> + ARRAY_SIZE(pins));
> + if (!ret) {
> + chan->differential = true;
> + chan->channel = pins[0];
> + chan->channel2 = pins[1];
> + return 0;
> + }
> + return dev_err_probe(dev, ret,
> + "Channel must define one of diff-channels or single-channel.\n");
> +}
> +
> +static int ad4170_probe(struct spi_device *spi)
> +{
> +
> + init_completion(&st->completion);
> +
> + if (spi->irq) {
> + ret = devm_request_irq(&st->spi->dev, st->spi->irq,
Use dev and spi->irq.
> + &ad4170_irq_handler, IRQF_ONESHOT,
> + indio_dev->name, indio_dev);
> + if (ret)
> + return ret;
> + }
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support
2025-05-13 12:35 ` [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support Marcelo Schmitt
@ 2025-05-25 10:41 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:41 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 13 May 2025 09:35:03 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> Add support for sinc3, sinc5, and averaged sinc5 digital filters along with
> sample frequency configuration.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> +static int ad4170_set_filter_type(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + unsigned int val)
> +{
> + struct ad4170_state *st = iio_priv(indio_dev);
> + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
> + struct ad4170_setup *setup = &chan_info->setup;
> + unsigned int old_filter_fs, old_filter, filter_type_conf;
> + int ret;
> +
> + switch (val) {
> + case AD4170_SINC5_AVG:
> + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5_AVG;
> + break;
> + case AD4170_SINC5:
> + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5;
> + break;
> + case AD4170_SINC3:
> + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC3;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (!iio_device_claim_direct(indio_dev))
> + return -EBUSY;
> +
> + guard(mutex)(&st->lock);
You need to scope this because otherwise locks are released in a different
order to how they are taken.
iio_device_claim_direct (takes mlock in the core)
guard(mutex)(&st->lock)
iio_device_release_direct (releases mlock in the core.
... scope finishes
guard(mutex)(&st->lock) cleanup happens.
Current code might be fine, but it's harder to reason about.
> + /*
> + * The filters provide the same ODR for a given filter_fs value but
> + * there are different minimum and maximum filter_fs limits for each
> + * filter. The filter_fs value will be adjusted if the current filter_fs
> + * is out of the limits of the just requested filter. Since the
> + * filter_fs value affects the ODR (sampling_frequency), changing the
> + * filter may lead to a change in the sampling frequency.
> + */
> + old_filter = setup->filter;
> + old_filter_fs = setup->filter_fs;
> + if (val == AD4170_SINC5_AVG || val == AD4170_SINC3)
> + setup->filter_fs = clamp(val, AD4170_SINC3_MIN_FS,
> + AD4170_SINC3_MAX_FS);
> + else
> + setup->filter_fs = clamp(val, AD4170_SINC5_MIN_FS,
> + AD4170_SINC5_MAX_FS);
> +
> + setup->filter &= ~AD4170_FILTER_FILTER_TYPE_MSK;
> + setup->filter |= FIELD_PREP(AD4170_FILTER_FILTER_TYPE_MSK,
> + filter_type_conf);
> +
> + ret = ad4170_write_channel_setup(st, chan->address, false);
> + if (ret) {
> + setup->filter = old_filter;
> + setup->filter_fs = old_filter_fs;
> + }
> +
> + iio_device_release_direct(indio_dev);
> + return ret;
> +}
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture
2025-05-13 12:35 ` [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture Marcelo Schmitt
@ 2025-05-25 10:46 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:46 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 13 May 2025 09:35:23 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> Extend the AD4170 driver to allow buffered data capture in continuous read
> mode. In continuous read mode, the chip skips the instruction phase and
> outputs just ADC sample data, enabling faster sample rates to be reached.
> The internal channel sequencer always starts sampling from channel 0 and
> channel 0 must be enabled if more than one channel is selected for data
> capture. The scan mask validation callback checks if the aforementioned
> condition is met.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
I'm curious - why no timestamps?
> diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
> index 218f768042fe..e8856c911dfd 100644
> --- a/drivers/iio/adc/ad4170.c
> +++ b/drivers/iio/adc/ad4170.c
>
> enum ad4170_ref_buf {
> @@ -309,9 +322,13 @@ struct ad4170_state {
> struct spi_device *spi;
> struct regmap *regmap;
> int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
> + __be32 bounce_buffer[AD4170_MAX_CHANNELS];
> + struct spi_message msg;
> + struct spi_transfer xfer;
> + struct iio_trigger *trig;
> + struct completion completion;
> int pins_fn[AD4170_NUM_ANALOG_PINS];
> u32 int_pin_sel;
> - struct completion completion;
Probably better to move this to it's new location when it's introduced and
reduce noise a little here.
> /*
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors
2025-05-13 12:36 ` [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors Marcelo Schmitt
@ 2025-05-25 10:57 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-25 10:57 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-gpio, linux-kernel, lars,
Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 13 May 2025 09:36:47 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> The AD4170 design has features to aid interfacing with weigh scale and RTD
> sensors that are expected to be setup with external circuitry for proper
> sensor operation. A key characteristic of those sensors is that the circuit
> they are in must be excited with a pair of signals. The external circuit
> can be excited either by voltage supply or by AD4170 excitation signals.
> The sensor can then be read through a different pair of lines that are
> connected to AD4170 ADC.
>
> Configure AD4170 to handle external circuit sensors.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
A few comments inline. In general this is in a good shape subject
to us figuring out the right dt-binding.
Jonathan
> +
> +static int ad4170_setup_bridge(struct ad4170_state *st,
> + struct fwnode_handle *child,
> + struct ad4170_setup *setup, u32 *exc_pins,
> + int num_exc_pins, int exc_cur, bool ac_excited)
> +{
> + unsigned long gpio_mask;
> + int ret;
> +
> + /*
> + * If a specific current is provided through
> + * adi,excitation-current-microamp, set excitation pins provided through
> + * adi,excitation-pins to excite the bridge circuit.
> + */
> + if (exc_cur > 0)
> + return ad4170_setup_current_src(st, child, setup, exc_pins,
> + num_exc_pins, exc_cur,
> + ac_excited);
> +
> + /*
> + * Else, use predefined ACX1, ACX1 negated, ACX2, ACX2 negated signals
> + * to AC excite the bridge. Those signals are output on GPIO2, GPIO0,
> + * GPIO3, and GPIO1, respectively. If only two pins are specified for AC
> + * excitation, use ACX1 and ACX2 (GPIO2 and GPIO3).
> + *
> + * Also, to avoid any short-circuit condition when more than one channel
> + * is enabled, set GPIO2 and GPIO0 high, and set GPIO1 and GPIO3 low to
> + * DC excite the bridge whenever a channel without AC excitation is
> + * selected. That is needed because GPIO pins are controlled by the next
> + * highest priority GPIO function when a channel doesn't enable AC
> + * excitation. See datasheet Figure 113 Weigh Scale (AC Excitation) for
> + * an example circuit diagram.
> + */
> + if (num_exc_pins == 2) {
> + setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3);
> +
> + gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK;
> + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 3) |
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 2));
Can we find a way to document relevance of 2 and 3 here? Ideally
appropriate defines or similar. Feels like a FIELD_PREP is appropriate.
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(st->regmap,
> + AD4170_GPIO_OUTPUT_REG,
> + BIT(3) | BIT(2));
These are also cryptic. Probably some FIELD_PREP() here as well.
> + if (ret)
> + return ret;
> +
> + st->gpio_fn[3] |= AD4170_GPIO_OUTPUT;
> + st->gpio_fn[2] |= AD4170_GPIO_OUTPUT;
> + } else {
> + setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x2);
> +
> + gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK |
> + AD4170_GPIO_MODE_GPIO1_MSK | AD4170_GPIO_MODE_GPIO0_MSK;
> + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask,
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 3) |
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 2) |
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 1) |
> + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * 0));
As above. FIELD_PREP()
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(st->regmap,
> + AD4170_GPIO_OUTPUT_REG,
> + BIT(3) | BIT(2) | BIT(1) | BIT(0));
> + if (ret)
> + return ret;
> +
> + st->gpio_fn[3] |= AD4170_GPIO_OUTPUT;
> + st->gpio_fn[2] |= AD4170_GPIO_OUTPUT;
> + st->gpio_fn[1] |= AD4170_GPIO_OUTPUT;
> + st->gpio_fn[0] |= AD4170_GPIO_OUTPUT;
> + }
> +
> + return 0;
> +}
> +
> +static int ad4170_parse_external_sensor(struct ad4170_state *st,
> + struct fwnode_handle *child,
> + struct ad4170_setup *setup,
> + struct iio_chan_spec *chan, u8 s_type)
> +{
> + unsigned int num_exc_pins, exc_cur, reg_val;
> + struct device *dev = &st->spi->dev;
> + u32 pins[2], exc_pins[4];
> + bool ac_excited, vbias;
> + unsigned int i;
> + int ret;
> +
> + ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
> + ARRAY_SIZE(pins));
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to read sensor diff-channels\n");
> +
> + chan->differential = true;
> + chan->channel = pins[0];
> + chan->channel2 = pins[1];
> +
> + ac_excited = fwnode_property_read_bool(child, "adi,excitation-ac");
> +
> + num_exc_pins = fwnode_property_count_u32(child, "adi,excitation-pins");
> + if (num_exc_pins != 1 && num_exc_pins != 2 && num_exc_pins != 4)
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid number of excitation pins\n");
> +
> + ret = fwnode_property_read_u32_array(child, "adi,excitation-pins",
> + exc_pins, num_exc_pins);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to read adi,excitation-pins\n");
> +
> + ret = ad4170_validate_excitation_pins(st, exc_pins, num_exc_pins);
> + if (ret)
> + return ret;
> +
> + exc_cur = 0;
> + ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp",
> + &exc_cur);
> + if (ret && s_type == AD4170_RTD_SENSOR)
> + return dev_err_probe(dev, ret,
> + "Failed to read adi,excitation-current-microamp\n");
> +
> + for (i = 0; i < ARRAY_SIZE(ad4170_iout_current_ua_tbl); i++)
> + if (ad4170_iout_current_ua_tbl[i] == exc_cur)
> + break;
> +
> + if (i == ARRAY_SIZE(ad4170_iout_current_ua_tbl))
> + return dev_err_probe(dev, ret,
> + "Invalid excitation current: %uuA\n",
> + exc_cur);
> +
> + /* Get the excitation current configuration value */
> + exc_cur = ret;
> +
> + if (s_type == AD4170_THERMOCOUPLE_SENSOR) {
> + vbias = fwnode_property_read_bool(child, "adi,vbias");
> + if (vbias) {
> + st->pins_fn[chan->channel2] |= AD4170_PIN_VBIAS;
> + reg_val = BIT(chan->channel2);
> + return regmap_write(st->regmap, AD4170_V_BIAS_REG,
> + reg_val);
> + }
> + }
> + if (s_type == AD4170_WEIGH_SCALE_SENSOR) {
> + ret = ad4170_setup_bridge(st, child, setup, exc_pins,
> + num_exc_pins, exc_cur, ac_excited);
No {} needed as both single statements.
> + } else {
> + ret = ad4170_setup_rtd(st, child, setup, exc_pins, num_exc_pins,
> + exc_cur, ac_excited);
> + }
> + return ret;
> +}
> +
> static int ad4170_parse_reference(struct ad4170_state *st,
> struct fwnode_handle *child,
> struct ad4170_setup *setup)
> @@ -1849,6 +2231,7 @@ static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
> struct ad4170_state *st = iio_priv(indio_dev);
> struct device *dev = &st->spi->dev;
> struct ad4170_chan_info *chan_info;
> + u8 s_type = AD4170_ADC_SENSOR;
> struct ad4170_setup *setup;
> struct iio_chan_spec *chan;
> unsigned int ch_reg;
> @@ -1880,10 +2263,32 @@ static int ad4170_parse_channel_node(struct iio_dev *indio_dev,
> if (ret)
> return ret;
>
> - ret = ad4170_parse_adc_channel_type(dev, child, chan);
> - if (ret < 0)
> - return ret;
> + ret = fwnode_property_read_u8(child, "adi,sensor-type", &s_type);
use same pattern for handling defaults as I suggested in earlier patches.
s_type = ADI4170_ADC_SESNOR;
fwnode_property_read_u8(child, "adi,sensor-type", &s_type);
if (s_type > ...
> + if (!ret) {
> + if (s_type > AD4170_THERMOCOUPLE_SENSOR)
> + return dev_err_probe(dev, ret,
> + "Invalid adi,sensor-type: %u\n",
> + s_type);
> + }
> + switch (s_type) {
> + case AD4170_ADC_SENSOR:
> + ret = ad4170_parse_adc_channel_type(dev, child, chan);
> + if (ret)
> + return ret;
>
> + break;
> + case AD4170_WEIGH_SCALE_SENSOR:
> + case AD4170_THERMOCOUPLE_SENSOR:
> + case AD4170_RTD_SENSOR:
> + ret = ad4170_parse_external_sensor(st, child, setup, chan,
> + s_type);
> + if (ret)
> + return ret;
> +
> + break;
> + default:
> + return -EINVAL;
> + }
> bipolar = fwnode_property_read_bool(child, "bipolar");
> setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar);
> if (bipolar)
> @@ -2087,6 +2492,12 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
> for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
> st->pins_fn[i] = AD4170_PIN_UNASIGNED;M
UNASSIGNED
(obviously I missed this earlier)
>
> + for (i = 0; i < AD4170_NUM_GPIO_PINS; i++)
> + st->gpio_fn[i] = AD4170_GPIO_UNASIGNED;
Same here.
> +
> + for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++)
> + st->cur_src_pins[i] = AD4170_CURRENT_SRC_DISABLED;
> +
> /* On power on, device defaults to using SDO pin for data ready signal */
> st->int_pin_sel = AD4170_INT_PIN_SDO;
> ret = device_property_match_property_string(dev, "interrupt-names",
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 02/10] iio: adc: Add basic support for AD4170
2025-05-13 12:34 ` [PATCH v3 02/10] iio: adc: Add basic support for AD4170 Marcelo Schmitt
2025-05-25 10:36 ` Jonathan Cameron
@ 2025-05-26 10:21 ` Nuno Sá
1 sibling, 0 replies; 30+ messages in thread
From: Nuno Sá @ 2025-05-26 10:21 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel
Cc: Ana-Maria Cusco, jic23, lars, Michael.Hennerich, dlechner,
nuno.sa, andy, robh, krzk+dt, conor+dt, linus.walleij, brgl,
marcelo.schmitt1
On Tue, 2025-05-13 at 09:34 -0300, Marcelo Schmitt wrote:
> From: Ana-Maria Cusco <ana-maria.cusco@analog.com>
>
> The AD4170 is a multichannel, low noise, 24-bit precision sigma-delta
> analog to digital converter. The AD4170 design offers a flexible data
> aquisition solution with crosspoint multiplexed analog inputs, configurable
> ADC voltage reference inputs, ultra-low noise integrated PGA, digital
> filtering, wide range of configurable output data rates, internal
> oscillator and temperature sensor, four GPIOs, and integrated features for
> interfacing with load cell weigh scales, RTD, and thermocouple sensors.
>
> Add basic support for the AD4170 ADC with the following features:
> - Single-shot read.
> - Analog front end PGA configuration.
> - Differential and pseudo-differential input configuration.
>
> Signed-off-by: Ana-Maria Cusco <ana-maria.cusco@analog.com>
> Co-developed-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
Looks very good. Just some small notes...
> Change log v2 -> v3
> - Updated Copyright year.
> - Separated handling of channel setup cases for better understanding of code
> flow.
> - Now comparing setups field by field instead of using memcmp().
> - Disable channel on ad4170_read_sample() error path.
> - Reinit completion before entering single conversion mode.
> - Organized ad4170_sinc3_filt_fs_tbl.
> - Used clamp to simplify configuration value checking.
> - Returned earlier whenever possible.
> - Used HZ_PER_KHZ/MHZ.
> - Declared internal voltage reference constant AD4170_INT_REF_2_5V
> - Many other minor code style and readability improvements.
>
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 12 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ad4170.c | 1553 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 1567 insertions(+)
> create mode 100644 drivers/iio/adc/ad4170.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0a8f2c7a139c..541e37ed304e 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,ad4170.yaml
> +F: drivers/iio/adc/ad4170.c
>
> ANALOG DEVICES INC AD4695 DRIVER
> M: Michael Hennerich <michael.hennerich@analog.com>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 0fe6601e59ed..594b9f55ec0a 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -70,6 +70,18 @@ config AD4130
> To compile this driver as a module, choose M here: the module will
> be
> called ad4130.
>
> +
> +config AD4170
> + tristate "Analog Device AD4170 ADC Driver"
> + depends on SPI
> + select REGMAP_SPI
> + help
> + Say yes here to build support for Analog Devices AD4170 SPI analog
> + to digital converters (ADC).
> +
> + To compile this driver as a module, choose M here: the module will
> be
> + called ad4170.
> +
> config AD4695
> tristate "Analog Device AD4695 ADC Driver"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 07d4b832c42e..d3a1376d1f96 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
> obj-$(CONFIG_AD4000) += ad4000.o
> obj-$(CONFIG_AD4030) += ad4030.o
> obj-$(CONFIG_AD4130) += ad4130.o
> +obj-$(CONFIG_AD4170) += ad4170.o
> obj-$(CONFIG_AD4695) += ad4695.o
> obj-$(CONFIG_AD4851) += ad4851.o
> obj-$(CONFIG_AD7091R) += ad7091r-base.o
> diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
> new file mode 100644
> index 000000000000..bf19b31095ee
> --- /dev/null
> +++ b/drivers/iio/adc/ad4170.c
> @@ -0,0 +1,1553 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2025 Analog Devices, Inc.
> + * Author: Ana-Maria Cusco <ana-maria.cusco@analog.com>
> + * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/unaligned.h>
> +#include <linux/units.h>
> +#include <linux/util_macros.h>
>
...
> +
> +static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg, unsigned int writeval,
> + unsigned int *readval)
> +{
> + struct ad4170_state *st = iio_priv(indio_dev);
> +
> + if (readval)
> + return regmap_read(st->regmap, reg, readval);
> + else
> + return regmap_write(st->regmap, reg, writeval);
redundant else
...
>
> +
> +/*
> + * Sets the ADC operating mode. Supported modes are
> + * - Single conversion mode
> + * - Idle mode
> + */
> +static int ad4170_set_mode(struct ad4170_state *st, unsigned int mode)
> +{
> + return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
> + AD4170_ADC_CTRL_MODE_MSK,
> + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK,
> mode));
> +}
I'm usually not a fan of these wrappers.
...
>
> +
> +static int ad4170_parse_firmware(struct iio_dev *indio_dev)
> +{
> + struct ad4170_state *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + int reg_data, ret;
> + unsigned int i;
> +
> + st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
> +
> + for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
> + st->pins_fn[i] = AD4170_PIN_UNASIGNED;
Isn't the above the default already?
- Nuno Sá
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain
2025-05-13 12:34 ` [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain Marcelo Schmitt
@ 2025-05-26 10:24 ` Nuno Sá
0 siblings, 0 replies; 30+ messages in thread
From: Nuno Sá @ 2025-05-26 10:24 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 2025-05-13 at 09:34 -0300, Marcelo Schmitt wrote:
> Add support for ADC calibration gain configuration.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v2 -> v3
> - New patch spun out of the base driver patch.
>
> drivers/iio/adc/ad4170.c | 29 ++++++++++++++++++++++++++++-
> 1 file changed, 28 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
> index bf19b31095ee..1df214f7fdec 100644
> --- a/drivers/iio/adc/ad4170.c
> +++ b/drivers/iio/adc/ad4170.c
> @@ -642,7 +642,8 @@ static const struct iio_chan_spec ad4170_channel_template
> = {
> .differential = 1,
> .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> BIT(IIO_CHAN_INFO_SCALE) |
> - BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_OFFSET) |
> + BIT(IIO_CHAN_INFO_CALIBSCALE),
> .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
> .scan_type = {
> .realbits = 24,
> @@ -953,6 +954,9 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
> pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
> *val = chan_info->offset_tbl[pga];
> return IIO_VAL_INT;
> + case IIO_CHAN_INFO_CALIBSCALE:
> + *val = setup->gain;
To be nitpicky, the only way this is correct for all archs is also locking the
load. Or read_once() but likely not worth it.
Other than that, looks good
- Nuno Sá
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias
2025-05-13 12:34 ` [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias Marcelo Schmitt
@ 2025-05-26 10:27 ` Nuno Sá
0 siblings, 0 replies; 30+ messages in thread
From: Nuno Sá @ 2025-05-26 10:27 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel
Cc: jic23, lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh,
krzk+dt, conor+dt, linus.walleij, brgl, marcelo.schmitt1
On Tue, 2025-05-13 at 09:34 -0300, Marcelo Schmitt wrote:
> Add support for ADC calibration bias/offset configuration.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v2 -> v3
> - New patch spun out of the base driver patch.
>
> drivers/iio/adc/ad4170.c | 26 ++++++++++++++++++++++++++
> 1 file changed, 26 insertions(+)
>
> diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c
> index 1df214f7fdec..b02fdd25b4c8 100644
> --- a/drivers/iio/adc/ad4170.c
> +++ b/drivers/iio/adc/ad4170.c
> @@ -643,6 +643,7 @@ static const struct iio_chan_spec ad4170_channel_template
> = {
> .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> BIT(IIO_CHAN_INFO_SCALE) |
> BIT(IIO_CHAN_INFO_OFFSET) |
> + BIT(IIO_CHAN_INFO_CALIBBIAS) |
> BIT(IIO_CHAN_INFO_CALIBSCALE),
> .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
> .scan_type = {
> @@ -954,6 +955,9 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
> pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
> *val = chan_info->offset_tbl[pga];
> return IIO_VAL_INT;
> + case IIO_CHAN_INFO_CALIBBIAS:
> + *val = setup->offset;
same nit...
> + return IIO_VAL_INT;
> case IIO_CHAN_INFO_CALIBSCALE:
> *val = setup->gain;
> return IIO_VAL_INT;
> @@ -1083,6 +1087,25 @@ static int ad4170_set_pga(struct ad4170_state *st,
> return 0;
> }
>
> +static int ad4170_set_calib_offset(struct ad4170_state *st,
> + struct iio_chan_spec const *chan, int val)
> +{
> + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
> + struct ad4170_setup *setup = &chan_info->setup;
> + u32 old_offset;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> + old_offset = setup->offset;
> + setup->offset = val;
Why doing the above dance?
> +
> + ret = ad4170_write_channel_setup(st, chan->address, false);
> + if (ret)
> + setup->offset = old_offset;
> +
We could update the value in here.
> + return ret;
I guess ret > 0 is not a thing? I find it more readable:
return 0;
It makes it clear we got success :)
- Nuno Sá
> +}
> +
> static int ad4170_set_calib_gain(struct ad4170_state *st,
> struct iio_chan_spec const *chan, int val)
> {
> @@ -1111,6 +1134,8 @@ static int __ad4170_write_raw(struct iio_dev *indio_dev,
> switch (info) {
> case IIO_CHAN_INFO_SCALE:
> return ad4170_set_pga(st, chan, val, val2);
> + case IIO_CHAN_INFO_CALIBBIAS:
> + return ad4170_set_calib_offset(st, chan, val);
> case IIO_CHAN_INFO_CALIBSCALE:
> return ad4170_set_calib_gain(st, chan, val);
> default:
> @@ -1139,6 +1164,7 @@ static int ad4170_write_raw_get_fmt(struct iio_dev
> *indio_dev,
> switch (info) {
> case IIO_CHAN_INFO_SCALE:
> return IIO_VAL_INT_PLUS_NANO;
> + case IIO_CHAN_INFO_CALIBBIAS:
> case IIO_CHAN_INFO_CALIBSCALE:
> return IIO_VAL_INT;
> default:
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-25 10:11 ` Jonathan Cameron
@ 2025-05-26 21:59 ` Marcelo Schmitt
2025-05-31 15:50 ` Jonathan Cameron
0 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-05-26 21:59 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel,
lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl
Hi Jonathan,
I'm still working on the changes suggested for this set.
Just to mention, I've updated the dt-binding to declare only channel nodes under
the ADC node.
...
> > + adi,sensor-type:
> > + description: |
> > + Type of sensor connected to the device. Depending on the sensor type
> > + (weigh scale, RTD, or thermocouple) the values of sensor-node
> > + properties have slightly different constraints. This property
> > + specifies which particular external sensor is connected to the ADC so
> > + the sensor-node properties can be properly parsed and verified. The
> > + possible sensor types are:
> > + 0: weigh scale;
> > + 1: RTD;
> > + 2: thermocouple.
> > + $ref: /schemas/types.yaml#/definitions/uint8
>
> I think this should be an enum of strings. That will give us a clean
> way to extend it for other sensor types in future.
Ack
...
> > +
> > + interrupts:
> > + maxItems: 1
>
> What if they are both wired? At that point shouldn't software
> make up it's mind which to use?
Not sure I understand the concern here. Is 'interrupt-names' property expected
to precede 'interrupts' in dt-doc? For AD4170 and similar parts, the data
ready signal (/RDY) is by default provided on the SDO line after the completion
of a conversion. Alternatively, it can be provided on the DIG_AUX1 pin in
which case the chip disables the RDY function on SDO. So, there can only be one
data ready interrupt enabled at a time. Guess I'll add some description to make
clear only one interrupt can be enabled.
> > +
> > + interrupt-names:
> > + description:
> > + Specify which pin should be configured as Data Ready interrupt.
> > + enum:
> > + - sdo
> > + - dig_aux1
> > + default: sdo
> > +
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170
2025-05-26 21:59 ` Marcelo Schmitt
@ 2025-05-31 15:50 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-05-31 15:50 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-gpio, linux-kernel,
lars, Michael.Hennerich, dlechner, nuno.sa, andy, robh, krzk+dt,
conor+dt, linus.walleij, brgl
On Mon, 26 May 2025 18:59:48 -0300
Marcelo Schmitt <marcelo.schmitt1@gmail.com> wrote:
> Hi Jonathan,
>
> I'm still working on the changes suggested for this set.
> Just to mention, I've updated the dt-binding to declare only channel nodes under
> the ADC node.
>
> ...
> > > + adi,sensor-type:
> > > + description: |
> > > + Type of sensor connected to the device. Depending on the sensor type
> > > + (weigh scale, RTD, or thermocouple) the values of sensor-node
> > > + properties have slightly different constraints. This property
> > > + specifies which particular external sensor is connected to the ADC so
> > > + the sensor-node properties can be properly parsed and verified. The
> > > + possible sensor types are:
> > > + 0: weigh scale;
> > > + 1: RTD;
> > > + 2: thermocouple.
> > > + $ref: /schemas/types.yaml#/definitions/uint8
> >
> > I think this should be an enum of strings. That will give us a clean
> > way to extend it for other sensor types in future.
> Ack
>
> ...
> > > +
> > > + interrupts:
> > > + maxItems: 1
> >
> > What if they are both wired? At that point shouldn't software
> > make up it's mind which to use?
>
> Not sure I understand the concern here. Is 'interrupt-names' property expected
> to precede 'interrupts' in dt-doc? For AD4170 and similar parts, the data
> ready signal (/RDY) is by default provided on the SDO line after the completion
> of a conversion. Alternatively, it can be provided on the DIG_AUX1 pin in
> which case the chip disables the RDY function on SDO. So, there can only be one
> data ready interrupt enabled at a time. Guess I'll add some description to make
> clear only one interrupt can be enabled.
DT is describing the wiring, not what should be enabled by the driver.
There is no need for it to restrict to 1 interrupt if someone has wired them
both that I can think of. In that circumstance the driver gets to decide
between the mess of shared SDO / dataready handling vs a nice
separate signal on dig_aux1
If both are wired to the host on appropriate pins, up to the driver to
decide what it wants. A driver may also only supports the
dig_aux1 combination or only the sdo combination and so just fail to load
if it isn't happy with the one the DT author thought mattered.
Providing both if that is what is wired, gives the flexibility to find
the right one.
>
> > > +
> > > + interrupt-names:
> > > + description:
> > > + Specify which pin should be configured as Data Ready interrupt.
> > > + enum:
> > > + - sdo
> > > + - dig_aux1
> > > + default: sdo
> > > +
>
> Thanks,
> Marcelo
>
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2025-05-31 15:50 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-13 12:32 [PATCH v3 00/10] Add support for AD4170 series of ADCs Marcelo Schmitt
2025-05-13 12:33 ` [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170 Marcelo Schmitt
2025-05-13 15:47 ` David Lechner
2025-05-16 15:45 ` Marcelo Schmitt
2025-05-16 16:06 ` David Lechner
2025-05-21 8:33 ` Krzysztof Kozlowski
2025-05-21 8:41 ` Krzysztof Kozlowski
2025-05-22 15:07 ` Marcelo Schmitt
2025-05-25 10:05 ` Jonathan Cameron
2025-05-25 10:11 ` Jonathan Cameron
2025-05-26 21:59 ` Marcelo Schmitt
2025-05-31 15:50 ` Jonathan Cameron
2025-05-13 12:34 ` [PATCH v3 02/10] iio: adc: Add basic support for AD4170 Marcelo Schmitt
2025-05-25 10:36 ` Jonathan Cameron
2025-05-26 10:21 ` Nuno Sá
2025-05-13 12:34 ` [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain Marcelo Schmitt
2025-05-26 10:24 ` Nuno Sá
2025-05-13 12:34 ` [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias Marcelo Schmitt
2025-05-26 10:27 ` Nuno Sá
2025-05-13 12:35 ` [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support Marcelo Schmitt
2025-05-25 10:41 ` Jonathan Cameron
2025-05-13 12:35 ` [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture Marcelo Schmitt
2025-05-25 10:46 ` Jonathan Cameron
2025-05-13 12:35 ` [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support Marcelo Schmitt
2025-05-13 16:59 ` David Lechner
2025-05-13 12:36 ` [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support Marcelo Schmitt
2025-05-20 17:06 ` Bartosz Golaszewski
2025-05-13 12:36 ` [PATCH v3 09/10] iio: adc: ad4170: Add support for internal temperature sensor Marcelo Schmitt
2025-05-13 12:36 ` [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors Marcelo Schmitt
2025-05-25 10:57 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).