Skip to contentSkip to conversation

Add support for AD4170 series of ADCs

PATCH Superseded Marcelo Schmitt 7 participants 30 messages May 13, 2025 – May 31, 2025

Subsystems (4)

v3 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v3 cover Add support for AD4170 series of ADCs

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/[email protected]/

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/[email protected]/

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

Signature · 1 line
2.47.2
#1 lore raw

PATCH v3 1/10 dt-bindings: iio: adc: Add AD4170

Add device tree documentation for AD4170 and similar sigma-delta ADCs. The AD4170 is a 24-bit, multichannel, sigma-delta ADC.

---
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
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +544 -0
new file mode 100644
index 000000000000..0a06258b6631
@@ -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 <[email protected]>
+
+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>;
+            };
+        };
+    };
+...
+
MAINTAINERS +7 -0
index cc9582b14ced..0a8f2c7a139c 100644
@@ -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 <[email protected]>
+L:	[email protected]
+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 <[email protected]>
 M:	Nuno Sá <[email protected]>
Signature · 1 line
2.47.2
reviewer
#2 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

Quoted 51 lines · Marcelo Schmitt

Add device tree documentation for AD4170 and similar sigma-delta ADCs. The AD4170 is a 24-bit, multichannel, sigma-delta ADC.

---
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
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +28 -0
new file mode 100644
index 000000000000..0a06258b6631
@@ -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 <[email protected]>
+
+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:

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 38

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+      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.

Quoted 34 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +34 -0
+
+
+  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: |

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 82

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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.

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 87

Quoted 5 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +5 -0
+
+      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.

Quoted 22 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +22 -0
+
+      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,

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 119

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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?

Quoted 17 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +17 -0
+
+      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
+
+

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 146

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+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/

Quoted 42 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +42 -0
+      (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:

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 200

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+    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.

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 206

Quoted 6 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +6 -0
+
+  '#clock-cells':
+    const: 0
+
+  clock-output-names:
+    maxItems: 1

And if #clock-cells is given then clocks is forbidden.

Quoted 51 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +51 -0
+
+  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

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 267

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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.

Quoted 34 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +34 -0
+
+      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

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 311

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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.

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 130

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+    required:
+      - reg

reg is already required by adc.yaml

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 317

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+    oneOf:
+      - required: [single-channel]

Is there a default for common-mode-channel if it isn't required in this case?

Quoted 70 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +70 -0
+        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:

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 397

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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.

#3 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

...

Quoted 12 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 38

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+
+    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]
Quoted 2 lines

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.

...

Quoted 12 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 82

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+      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
Quoted 3 lines

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.

...

Quoted 7 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 119

Quoted 6 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +6 -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?

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?

...

Quoted 27 lines
Quoted 16 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +16 -0
+      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

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 267

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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]
Quoted 3 lines

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?

Quoted 38 lines
Quoted 38 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +37 -0
+
+      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
Quoted 8 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 292

Quoted 7 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +7 -0
+          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.

Quoted 7 lines

Also another case where string type would make more sense.

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 130

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+    required:
+      - reg
Quoted 7 lines

reg is already required by adc.yaml

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 317

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+    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

reviewer
#4 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

Quoted 16 lines · Marcelo Schmitt

...

Quoted 12 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 38

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+
+    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]
Quoted 2 lines

Using strings instead of int for this and most of the other custom enums here would make them self-documenting and easier to use.

Quoted 6 lines

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.

Quoted 7 lines

...

Quoted 6 lines
Quoted 6 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +6 -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]
Quoted 5 lines
Quoted 1 line

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.

Quoted 29 lines

...

Quoted 27 lines
Quoted 26 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +26 -0
+      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]
Quoted 9 lines
Quoted 3 lines

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.

Quoted 2 lines

Also, no other binding would use those values. So, we would have a header specific for adi,ad4170?

Yes.

Quoted 48 lines
Quoted 39 lines
Quoted 39 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +37 -0
+
+      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
Quoted 8 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 292

Quoted 7 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +7 -0
+          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.

Quoted 2 lines

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'
#5 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

In reply to Marcelo Schmitt
Quoted 16 lines · Marcelo Schmitt

...

Quoted 12 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 38

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+
+    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]
Quoted 2 lines

Using strings instead of int for this and most of the other custom enums here would make them self-documenting and easier to use.

Quoted 6 lines

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.

Quoted 14 lines

...

Quoted 12 lines
Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+      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
Quoted 5 lines
Quoted 3 lines

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

#6 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

In reply to Marcelo Schmitt
Quoted 53 lines · Marcelo Schmitt

Add device tree documentation for AD4170 and similar sigma-delta ADCs. The AD4170 is a 24-bit, multichannel, sigma-delta ADC.

---
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
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +30 -0
new file mode 100644
index 000000000000..0a06258b6631
@@ -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 <[email protected]>
+
+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: |

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 40

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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.

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 43

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+  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?

Quoted 17 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +17 -0
+    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

...

Quoted 36 lines
Quoted 1 line

+patternProperties:

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +35 -0
+  "^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

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 273

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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?

Quoted 9 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +9 -0
+
+      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

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 292

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+          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

#7 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

...

Quoted 9 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 43

Quoted 4 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+  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

maintainer
#8 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

Quoted 92 lines · On Thu, 22 May 2025 12:07:41 -0300 Marcelo Schmitt…

...

Quoted 9 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 43

Quoted 4 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+  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.

Quoted 5 lines

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.

Quoted 6 lines

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

Quoted 4 lines

Thanks,
Marcelo

maintainer
#9 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

In reply to Marcelo Schmitt
Quoted 4 lines · On Tue, 13 May 2025 09:33:40 -0300 Marcelo Schmitt…

Add device tree documentation for AD4170 and similar sigma-delta ADCs. The AD4170 is a 24-bit, multichannel, sigma-delta ADC.

Hi Marcelo,

A few additional comments from me.

J

Quoted 21 lines

---
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
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +1 -0
new file mode 100644
index 000000000000..0a06258b6631
@@ -0,0 +1,544 @@
 
+  sensor-node:

...

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 82

Quoted 13 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +13 -0
+
+      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.

Quoted 8 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +8 -0
+    required:
+      - reg
+      - diff-channels
+      - bipolar
+      - adi,sensor-type
+      - adi,reference-select
+
+

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 180

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +9 -0
+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?

Quoted 9 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +9 -0
+
+  interrupt-names:
+    description:
+      Specify which pin should be configured as Data Ready interrupt.
+    enum:
+      - sdo
+      - dig_aux1
+    default: sdo
+
#10 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

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.

...

Quoted 13 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 82

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+      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
Quoted 2 lines

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

...

Quoted 6 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 180

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+  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.

Quoted 9 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 189

Quoted 9 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +9 -0
+
+  interrupt-names:
+    description:
+      Specify which pin should be configured as Data Ready interrupt.
+    enum:
+      - sdo
+      - dig_aux1
+    default: sdo
+

Thanks,
Marcelo

maintainer
#11 lore raw

Re: [PATCH v3 01/10] dt-bindings: iio: adc: Add AD4170

Quoted 32 lines · On Mon, 26 May 2025 18:59:48 -0300 Marcelo Schmitt…

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.

...

Quoted 13 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 82

Quoted 12 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +12 -0
+      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  
Quoted 2 lines

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

...

Quoted 6 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 180

Quoted 3 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +3 -0
+
+  interrupts:
+    maxItems: 1  

What if they are both wired? At that point shouldn't software make up it's mind which to use?

Quoted 7 lines

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.

Quoted 14 lines
Quoted 10 lines

Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml line 189

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +9 -0
+
+  interrupt-names:
+    description:
+      Specify which pin should be configured as Data Ready interrupt.
+    enum:
+      - sdo
+      - dig_aux1
+    default: sdo
+  

Thanks,
Marcelo

#12 lore raw

PATCH v3 2/10 iio: adc: Add basic support for AD4170

In reply to Marcelo Schmitt

From: Ana-Maria Cusco <[email protected]>

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.

---
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
MAINTAINERS +1 -0
index 0a8f2c7a139c..541e37ed304e 100644
@@ -1352,6 +1352,7 @@ L:	[email protected]
 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 <[email protected]>
drivers/iio/adc/Kconfig +12 -0
index 0fe6601e59ed..594b9f55ec0a 100644
@@ -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
drivers/iio/adc/Makefile +1 -0
index 07d4b832c42e..d3a1376d1f96 100644
@@ -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
drivers/iio/adc/ad4170.c new file +1553 -0

1,553 lines not on this page · raw message

Signature · 1 line
2.47.2
maintainer
#13 lore raw

Re: [PATCH v3 02/10] iio: adc: Add basic support for AD4170

Quoted 10 lines · On Tue, 13 May 2025 09:34:00 -0300 Marcelo Schmitt…

From: Ana-Maria Cusco <[email protected]>

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.

Quoted 8 lines

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.

A few minor things inline.

J

Quoted 52 lines
drivers/iio/adc/ad4170.c new file +46 -0
new file mode 100644
index 000000000000..bf19b31095ee
 
+
+/*
+ * 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;
+	}
+

drivers/iio/adc/ad4170.c line 811

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	/*
+	 * 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

Quoted 12 lines
drivers/iio/adc/ad4170.c new file +12 -0
+					     "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);
+	}
+	/*

drivers/iio/adc/ad4170.c line 833

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	 * 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

Quoted 6 lines
drivers/iio/adc/ad4170.c new file +6 -0
+				     "REF+ <= GND for pseudo-diff chan %u\n",
+				     ch_reg);
+
+	if (bipolar)
+		return refp;
+

drivers/iio/adc/ad4170.c line 849

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	/*
+	 * 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

Quoted 7 lines
drivers/iio/adc/ad4170.c new file +5 -0
+				     "Negative input >= REF+ for pseudo-diff chan %u\n",
+				     ch_reg);
+
+	return refp - ain_voltage;
+}
 
 

drivers/iio/adc/ad4170.c line 1153

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +9 -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 */

I'd drop the "if omitted" part as the next line makes that clear.

drivers/iio/adc/ad4170.c line 1157

Quoted 4 lines
drivers/iio/adc/ad4170.c new file +4 -0
+	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);

drivers/iio/adc/ad4170.c line 1165

Quoted 9 lines
drivers/iio/adc/ad4170.c new file +9 -0
+					     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.

drivers/iio/adc/ad4170.c line 1176

Quoted 11 lines
drivers/iio/adc/ad4170.c new file +11 -0
+	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.

Quoted 15 lines
drivers/iio/adc/ad4170.c new file +15 -0
+	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)

drivers/iio/adc/ad4170.c line 1201

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+{
+	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 :)

Quoted 19 lines
drivers/iio/adc/ad4170.c new file +19 -0
+	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");
+}

drivers/iio/adc/ad4170.c

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +8 -0
 
+
+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.

Quoted 8 lines
drivers/iio/adc/ad4170.c new file +8 -0
+				       &ad4170_irq_handler, IRQF_ONESHOT,
+				       indio_dev->name, indio_dev);
+		if (ret)
+			return ret;
+	}
+
+	return devm_iio_device_register(dev, indio_dev);
+}
#14 lore raw

Re: [PATCH v3 02/10] iio: adc: Add basic support for AD4170

In reply to Marcelo Schmitt
Quoted 15 lines · Marcelo Schmitt

From: Ana-Maria Cusco <[email protected]>

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.

Quoted 4 lines

---

Looks very good. Just some small notes...

Quoted 103 lines

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

MAINTAINERS +0 -0
index 0a8f2c7a139c..541e37ed304e 100644
@@ -1352,6 +1352,7 @@ L:	[email protected]

 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 <[email protected]>

drivers/iio/adc/Kconfig +0 -0
index 0fe6601e59ed..594b9f55ec0a 100644
@@ -70,6 +70,18 @@ config AD4130

    To compile this driver as a module, choose M here: the module will be
    called ad4130.

drivers/iio/adc/Kconfig +10 -0
+
+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

drivers/iio/adc/Kconfig +2 -0
+	  called ad4170.
+

 config AD4695
  tristate "Analog Device AD4695 ADC Driver" depends on SPI

drivers/iio/adc/Makefile +0 -0
index 07d4b832c42e..d3a1376d1f96 100644
@@ -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

drivers/iio/adc/ad4170.c new file +26 -0
new file mode 100644
index 000000000000..bf19b31095ee
@@ -0,0 +1,1553 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Analog Devices, Inc.
+ * Author: Ana-Maria Cusco <[email protected]>
+ * Author: Marcelo Schmitt <[email protected]>
+ */
+
+#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>

...

drivers/iio/adc/ad4170.c line 291

Quoted 11 lines
drivers/iio/adc/ad4170.c new file +11 -0
+
+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

...

Quoted 12 lines
drivers/iio/adc/ad4170.c new file +11 -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,
Quoted 2 lines

mode));
+}

I'm usually not a fan of these wrappers.

...

drivers/iio/adc/ad4170.c line 1318

Quoted 13 lines
drivers/iio/adc/ad4170.c new file +12 -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;

Isn't the above the default already?

- Nuno Sá

#15 lore raw

PATCH v3 3/10 iio: adc: ad4170: Add support for calibration gain

In reply to Marcelo Schmitt

Add support for ADC calibration gain configuration.

---
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(-)
drivers/iio/adc/ad4170.c +28 -1
index bf19b31095ee..1df214f7fdec 100644
@@ -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;
 	}
Signature · 1 line
2.47.2
#16 lore raw

Re: [PATCH v3 03/10] iio: adc: ad4170: Add support for calibration gain

Quoted 26 lines · Marcelo Schmitt

Add support for ADC calibration gain configuration.

---
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(-)
drivers/iio/adc/ad4170.c +0 -0
index bf19b31095ee..1df214f7fdec 100644
@@ -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) |

drivers/iio/adc/ad4170.c +2 -1
-			      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,

drivers/iio/adc/ad4170.c static int ad4170_read_raw(struct iio_dev *indio_dev, · line 958

Quoted 5 lines
drivers/iio/adc/ad4170.c +2 -0
@@ -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á

Quoted 1 line
#17 lore raw

PATCH v3 4/10 iio: adc: ad4170: Add support for calibration bias

In reply to Marcelo Schmitt

Add support for ADC calibration bias/offset configuration.

---
Change log v2 -> v3
- New patch spun out of the base driver patch.

 drivers/iio/adc/ad4170.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
drivers/iio/adc/ad4170.c +26 -0
index 1df214f7fdec..b02fdd25b4c8 100644
@@ -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:
Signature · 1 line
2.47.2
#18 lore raw

Re: [PATCH v3 04/10] iio: adc: ad4170: Add support for calibration bias

Quoted 24 lines · Marcelo Schmitt

Add support for ADC calibration bias/offset configuration.

---
Change log v2 -> v3
- New patch spun out of the base driver patch.

 drivers/iio/adc/ad4170.c | 26 ++++++++++++++++++++++++++

 1 file changed, 26 insertions(+)
drivers/iio/adc/ad4170.c +0 -0
index 1df214f7fdec..b02fdd25b4c8 100644
@@ -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) |

drivers/iio/adc/ad4170.c +1 -0
+			      BIT(IIO_CHAN_INFO_CALIBBIAS) |

        BIT(IIO_CHAN_INFO_CALIBSCALE), .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), .scan_type = {

drivers/iio/adc/ad4170.c static int ad4170_read_raw(struct iio_dev *indio_dev, · line 959

Quoted 5 lines
drivers/iio/adc/ad4170.c +2 -0
@@ -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...

Quoted 9 lines
drivers/iio/adc/ad4170.c +1 -0
+		return IIO_VAL_INT;

  case IIO_CHAN_INFO_CALIBSCALE:
  *val = setup->gain;
  return IIO_VAL_INT;

drivers/iio/adc/ad4170.c +0 -0
@@ -1083,6 +1087,25 @@ static int ad4170_set_pga(struct ad4170_state *st,
  	return 0;
  }

drivers/iio/adc/ad4170.c +1 -0
+static int ad4170_set_calib_offset(struct ad4170_state *st,

drivers/iio/adc/ad4170.c static int ad4170_set_pga(struct ad4170_state *st, · line 1100

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+				   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?

drivers/iio/adc/ad4170.c static int ad4170_set_pga(struct ad4170_state *st, · line 1105

Quoted 5 lines
drivers/iio/adc/ad4170.c +5 -0
+
+	ret = ad4170_write_channel_setup(st, chan->address, false);
+	if (ret)
+		setup->offset = old_offset;
+

We could update the value in here.

drivers/iio/adc/ad4170.c static int ad4170_set_pga(struct ad4170_state *st, · line 1106

Quoted 1 line
drivers/iio/adc/ad4170.c +1 -0
+	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á

Quoted 23 lines
drivers/iio/adc/ad4170.c +2 -0
+}
+

 static int ad4170_set_calib_gain(struct ad4170_state *st, struct iio_chan_spec const *chan, int val) {

drivers/iio/adc/ad4170.c +0 -0
@@ -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);

drivers/iio/adc/ad4170.c +2 -0
+	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:

drivers/iio/adc/ad4170.c +0 -0
@@ -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;

drivers/iio/adc/ad4170.c +1 -0
+	case IIO_CHAN_INFO_CALIBBIAS:

  case IIO_CHAN_INFO_CALIBSCALE:
  return IIO_VAL_INT;
  default:

#19 lore raw

PATCH v3 5/10 iio: adc: ad4170: Add digital filter and sample frequency config support

In reply to Marcelo Schmitt

Add support for sinc3, sinc5, and averaged sinc5 digital filters along with sample frequency configuration.

---
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(-)
drivers/iio/adc/ad4170.c +270 -3
index b02fdd25b4c8..218f768042fe 100644
@@ -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,
Signature · 1 line
2.47.2
maintainer
#20 lore raw

Re: [PATCH v3 05/10] iio: adc: ad4170: Add digital filter and sample frequency config support

Quoted 23 lines · On Tue, 13 May 2025 09:35:03 -0300 Marcelo Schmitt…

Add support for sinc3, sinc5, and averaged sinc5 digital filters along with sample frequency configuration.

drivers/iio/adc/ad4170.c +18 -0
+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:

drivers/iio/adc/ad4170.c static int ad4170_set_channel_enable(struct ad4170_state *st, · line 755

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+		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.

Quoted 30 lines
drivers/iio/adc/ad4170.c +30 -0
+	/*
+	 * 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;
+}
#21 lore raw

PATCH v3 6/10 iio: adc: ad4170: Add support for buffered data capture

In reply to Marcelo Schmitt

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.

---
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(-)
drivers/iio/adc/Kconfig +2 -0
index 594b9f55ec0a..0d3d3af862f8 100644
@@ -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).
drivers/iio/adc/ad4170.c +209 -2

311 lines not on this page · raw message

Signature · 1 line
2.47.2
maintainer
#22 lore raw

Re: [PATCH v3 06/10] iio: adc: ad4170: Add support for buffered data capture

Quoted 9 lines · On Tue, 13 May 2025 09:35:23 -0300 Marcelo Schmitt…

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.

I'm curious - why no timestamps?

Quoted 9 lines
drivers/iio/adc/ad4170.c +0 -0
index 218f768042fe..e8856c911dfd 100644
 
 
 enum ad4170_ref_buf {
@@ -309,9 +322,13 @@ struct ad4170_state {
 	struct spi_device *spi;

drivers/iio/adc/ad4170.c struct ad4170_state { · line 314

Quoted 10 lines
drivers/iio/adc/ad4170.c +5 -1
 	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.

Quoted 1 line
drivers/iio/adc/ad4170.c +0 -0
 	/*
#23 lore raw

PATCH v3 7/10 iio: adc: ad4170: Add clock provider support

In reply to Marcelo Schmitt

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.

---
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(-)
drivers/iio/adc/Kconfig +1 -0
index 0d3d3af862f8..6e4b14243599 100644
@@ -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).
drivers/iio/adc/ad4170.c +143 -1

204 lines not on this page · raw message

Signature · 1 line
2.47.2
reviewer
#24 lore raw

Re: [PATCH v3 07/10] iio: adc: ad4170: Add clock provider support

Quoted 5 lines · Marcelo Schmitt

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.

Quoted 4 lines

---

...

drivers/iio/adc/ad4170.c struct ad4170_state { · line 352

Quoted 6 lines
drivers/iio/adc/ad4170.c +2 -0
@@ -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.

Quoted 8 lines
drivers/iio/adc/ad4170.c +1 -0
+	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;
 }
 

drivers/iio/adc/ad4170.c static int ad4170_parse_channels(struct iio_dev *indio_dev) · line 1690

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -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?

Quoted 37 lines
drivers/iio/adc/ad4170.c +37 -0
+	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,

drivers/iio/adc/ad4170.c static int ad4170_parse_channels(struct iio_dev *indio_dev) · line 1737

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+};
+
+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?

Quoted 17 lines
drivers/iio/adc/ad4170.c +17 -0
+		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,

drivers/iio/adc/ad4170.c static int ad4170_parse_channels(struct iio_dev *indio_dev) · line 1764

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+					   &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.

drivers/iio/adc/ad4170.c static int ad4170_parse_channels(struct iio_dev *indio_dev) · line 1771

Quoted 7 lines
drivers/iio/adc/ad4170.c +7 -0
+	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.

Quoted 28 lines
drivers/iio/adc/ad4170.c +23 -0
+		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;

drivers/iio/adc/ad4170.c static int ad4170_parse_firmware(struct iio_dev *indio_dev) · line 1808

Quoted 10 lines
drivers/iio/adc/ad4170.c +7 -1
 	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?

Quoted 3 lines
drivers/iio/adc/ad4170.c +0 -0
 	for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
 		st->pins_fn[i] = AD4170_PIN_UNASIGNED;
#25 lore raw

PATCH v3 8/10 iio: adc: ad4170: Add GPIO controller support

In reply to Marcelo Schmitt

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.

---
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(-)
drivers/iio/adc/Kconfig +1 -0
index 6e4b14243599..a328f03eea34 100644
@@ -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).
drivers/iio/adc/ad4170.c +223 -1

283 lines not on this page · raw message

Signature · 1 line
2.47.2
Acked-by on v3 8/10
#26 lore raw

Re: [PATCH v3 08/10] iio: adc: ad4170: Add GPIO controller support

Quoted 30 lines · Marcelo Schmitt

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.

---
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(-)
drivers/iio/adc/Kconfig +0 -0
index 6e4b14243599..a328f03eea34 100644

drivers/iio/adc/Kconfig config AD4170 · line 81

Quoted 4 lines
drivers/iio/adc/Kconfig +1 -0
@@ -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:

#27 lore raw

PATCH v3 9/10 iio: adc: ad4170: Add support for internal temperature sensor

In reply to Marcelo Schmitt

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.

---
Change log v2 -> v3
- Wrapped line according to logical approach.

 drivers/iio/adc/ad4170.c | 72 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 69 insertions(+), 3 deletions(-)
drivers/iio/adc/ad4170.c +69 -3
index 31f126a0f76f..1150c2e7ca1a 100644
@@ -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;
Signature · 1 line
2.47.2
#28 lore raw

PATCH v3 10/10 iio: adc: ad4170: Add support for weigh scale and RTD sensors

In reply to Marcelo Schmitt

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.

---
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(-)
drivers/iio/adc/ad4170.c +415 -4
index 1150c2e7ca1a..f80fbc6b663a 100644
@@ -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",
Signature · 1 line
2.47.2
maintainer
#29 lore raw

Re: [PATCH v3 10/10] iio: adc: ad4170: Add support for weigh scale and RTD sensors

Quoted 11 lines · On Tue, 13 May 2025 09:36:47 -0300 Marcelo Schmitt…

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.

A few comments inline. In general this is in a good shape subject to us figuring out the right dt-binding.

Jonathan

Quoted 30 lines
drivers/iio/adc/ad4170.c +30 -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

drivers/iio/adc/ad4170.c static int ad4170_gpio_init(struct iio_dev *indio_dev) · line 2024

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	 * 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.

drivers/iio/adc/ad4170.c static int ad4170_gpio_init(struct iio_dev *indio_dev) · line 2030

Quoted 6 lines
drivers/iio/adc/ad4170.c +6 -0
+		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.

Quoted 5 lines
drivers/iio/adc/ad4170.c +5 -0
+		if (ret)
+			return ret;
+
+		st->gpio_fn[3] |= AD4170_GPIO_OUTPUT;
+		st->gpio_fn[2] |= AD4170_GPIO_OUTPUT;

drivers/iio/adc/ad4170.c static int ad4170_gpio_init(struct iio_dev *indio_dev) · line 2045

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	} 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()

Quoted 79 lines
drivers/iio/adc/ad4170.c +78 -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_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");

drivers/iio/adc/ad4170.c static int ad4170_gpio_init(struct iio_dev *indio_dev) · line 2142

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+		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.

Quoted 19 lines
drivers/iio/adc/ad4170.c +8 -0
+	} 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;

drivers/iio/adc/ad4170.c static int ad4170_parse_channel_node(struct iio_dev *indio_dev, · line 2266

Quoted 7 lines
drivers/iio/adc/ad4170.c +1 -3
@@ -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 > ...
Quoted 29 lines
drivers/iio/adc/ad4170.c +24 -0
+	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)

drivers/iio/adc/ad4170.c static int ad4170_parse_firmware(struct iio_dev *indio_dev) · line 2493

Quoted 2 lines
drivers/iio/adc/ad4170.c +0 -0
@@ -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)

drivers/iio/adc/ad4170.c static int ad4170_parse_firmware(struct iio_dev *indio_dev) · line 2496

Quoted 3 lines
drivers/iio/adc/ad4170.c +2 -0
+	for (i = 0; i < AD4170_NUM_GPIO_PINS; i++)
+		st->gpio_fn[i] = AD4170_GPIO_UNASIGNED;

Same here.

Quoted 7 lines
drivers/iio/adc/ad4170.c +4 -0
+
+	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",