Skip to contentSkip to conversation

iio: adc: Add support for AD4170 series of ADCs

PATCH Superseded Marcelo Schmitt 7 participants 27 messages Apr 28, 2025 – May 12, 2025

Subsystems (3)

v2 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v2 cover iio: adc: Add support for AD4170 series of ADCs

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 buffered ADC reading.
Patch 4 adds clock provider support
Patch 5 adds GPIO controller support.
Patch 6 adds internal temperature sensor support.
Patch 7 adds support for external RTD and bridge circuit sensors.

I believe to have updated the series according to all suggestions offered during v1 review. The exceptions should be the IOVDD (discussed below) and regmap bulk access which I decided to replace by a custom regmap_read/write implementation instead.

Thank you for providing your insights and help.

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 external sensor connected.
- A few other minor improvements.

Previously, I thought IOVDD supply could be optional but it seems to be required. I have only remote access to a setup with EVAL-AD4170-4 but am pretty sure the link in the eval board was set to provide 3.3V to IOVDD pin. Datasheet sais 'This supply sets the interface logic levels on the SPI interface and powers an internal regulator for operation of the digital processing.' Since IOVDD supply will presumably be enabled, I kept reading it's voltage even though ADC channels might not use that info.

[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.
- Added support for clock-output-names
- Dropped '|' from descriptions when not needed.
- Added extra example

Link to v1: https://lore.kernel.org/linux-iio/[email protected]/

Marcelo

Ana-Maria Cusco (1):
iio: adc: Add basic support for AD4170

Marcelo Schmitt (6):
dt-bindings: iio: adc: Add AD4170
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          |  554 ++++
 MAINTAINERS                                   |    8 +
 drivers/iio/adc/Kconfig                       |   16 +
 drivers/iio/adc/Makefile                      |    1 +
 drivers/iio/adc/ad4170.c                      | 2846 +++++++++++++++++
 5 files changed, 3425 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
 create mode 100644 drivers/iio/adc/ad4170.c

base-commit: 1c2409fe38d5c19015d69851d15ba543d1911932

Signature · 1 line
2.47.2
#1 lore raw

PATCH v2 1/7 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.

---
[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.
- Added support for clock-output-names
- Dropped '|' from descriptions when not needed.
- Added extra example
-

 .../bindings/iio/adc/adi,ad4170.yaml          | 554 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 561 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +554 -0
new file mode 100644
index 000000000000..679825be1f15
@@ -0,0 +1,554 @@
+# 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:
+  sensor-node:
+    type: object
+    $ref: /schemas/iio/adc/adc.yaml#
+    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,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]
+
+      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: adc.yaml
+    type: object
+    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
+
+      adi,reference-select:
+        description: |
+          Select 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.
+          If this field is left empty, the internal reference is selected.
+        $ref: /schemas/types.yaml#/definitions/uint8
+        enum: [0, 1, 2, 3]
+        default: 2
+
+    required:
+      - reg
+
+    allOf:
+      - 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.
+        $ref: /schemas/types.yaml#/definitions/uint8
+        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.
+        $ref: /schemas/types.yaml#/definitions/uint8
+        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.
+        $ref: /schemas/types.yaml#/definitions/uint8
+        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 030d90d38341..991b6e2e373a 100644
@@ -1337,6 +1337,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
maintainer
#2 lore raw

Re: [PATCH v2 1/7] dt-bindings: iio: adc: Add AD4170

Quoted 333 lines · Marcelo Schmitt

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

---
[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.
- Added support for clock-output-names
- Dropped '|' from descriptions when not needed.
- Added extra example
-

 .../bindings/iio/adc/adi,ad4170.yaml          | 554 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 561 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +305 -0
new file mode 100644
index 000000000000..679825be1f15
@@ -0,0 +1,554 @@
+# 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:
+  sensor-node:
+    type: object
+    $ref: /schemas/iio/adc/adc.yaml#
+    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,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]
+
+      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: adc.yaml
+    type: object
+    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
+

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

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+      adi,reference-select:
+        description: |
+          Select 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.
+          If this field is left empty, the internal reference is selected.
+        $ref: /schemas/types.yaml#/definitions/uint8

You already defined the type in the $defs, so no need to do it again.

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

Quoted 7 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +7 -0
+        enum: [0, 1, 2, 3]
+        default: 2
+
+    required:
+      - reg
+
+    allOf:

Don't need allOf.

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+      - oneOf:
+          - required: [single-channel]
+            properties:
+              diff-channels: false
+          - required: [diff-channels]
+            properties:
+              single-channel: false
+              common-mode-channel: false
+
+  "^weighscale@":

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

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +10 -0
+    $ref: '#/$defs/sensor-node'
+    unevaluatedProperties: false
+
+    properties:
+      diff-channels: true
+      bipolar: true
+
+      adi,sensor-type:
+        description: Weigh scale sensor.
+        $ref: /schemas/types.yaml#/definitions/uint8

Already defined the type. And for the rest...

Rob

#3 lore raw

Re: [PATCH v2 1/7] dt-bindings: iio: adc: Add AD4170

...

Quoted 12 lines

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

Quoted 11 lines
Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file +11 -0
+
+      adi,reference-select:
+        description: |
+          Select 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.
+          If this field is left empty, the internal reference is selected.
+        $ref: /schemas/types.yaml#/definitions/uint8
Quoted 1 line

You already defined the type in the $defs, so no need to do it again.

The ADC channel node doesn't include/reference the sensor-node. Wouldn't adi,reference-select default to uint32 if the type is dropped?

The issue here is that adi,reference-select is a property that is meaningful both for conventional ADC channels and for channels connecting to external sensors. Thus, the unfortunate repetition of the property. Another possibility would to be add another type for the ad4170 channels. Something like

$defs:
ad4170-channel:

    $ref: /schemas/iio/adc/adc.yaml#

      adi,reference-select:
		....

sensor-node:

    type: object
    $ref: '#/$defs/ad4170-channel'
	...

patternProperties:
"^channel@[0-9a-f]$":

    $ref: '#/$defs/ad4170-channel'
	...

"^weighscale@":

    $ref: '#/$defs/sensor-node'
	...

"^rtd@":

    $ref: '#/$defs/sensor-node'
	...

would that be a better way of having those?

Thanks,
Marcelo

#4 lore raw

PATCH v2 2/7 iio: adc: Add basic support for AD4170

In reply to Marcelo Schmitt

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

Add support for the AD4170 ADC with the following features:
- Single-shot read.
- Analog front end PGA configuration.
- Digital filter and sampling frequency configuration.
- Calibration gain and offset configuration.
- Differential and pseudo-differential input configuration.

---
changes since v1
- Replaced 3 regmap configs by regmap with custom reg_read/write implementation.

 MAINTAINERS              |    1 +
 drivers/iio/adc/Kconfig  |   12 +
 drivers/iio/adc/Makefile |    1 +
 drivers/iio/adc/ad4170.c | 1888 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 1902 insertions(+)
 create mode 100644 drivers/iio/adc/ad4170.c
MAINTAINERS +1 -0
index 991b6e2e373a..56cd87028dfd 100644
@@ -1343,6 +1343,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 636469392945..d5d0308da007 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 +1888 -0

1,888 lines not on this page · raw message

Signature · 1 line
2.47.2
#5 lore raw

Re: [PATCH v2 2/7] iio: adc: Add basic support for AD4170

Quoted 8 lines · Marcelo Schmitt

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

Add support for the AD4170 ADC with the following features:
- Single-shot read.
- Analog front end PGA configuration.
- Digital filter and sampling frequency configuration.
- Calibration gain and offset configuration.
- Differential and pseudo-differential input configuration.

...

Quoted 2 lines

+/*
+ * Copyright (C) 2024 Analog Devices, Inc.

My calendar shows 2025...

Quoted 3 lines

+ * Author: Ana-Maria Cusco <[email protected]>
+ * Author: Marcelo Schmitt <[email protected]>
+ */

...

Quoted 1 line

+#define AD4170_DEFAULT_SAMP_RATE (125 * KILO)

HZ_PER_KHZ ?

...

drivers/iio/adc/ad4170.c line 165

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+/* Internal and external clock properties */
+#define AD4170_INT_CLOCK_16MHZ				(16 * MEGA)

HZ_PER_MHZ

drivers/iio/adc/ad4170.c line 167

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+#define AD4170_EXT_CLOCK_MHZ_MIN			(1 * MEGA)
+#define AD4170_EXT_CLOCK_MHZ_MAX			(17 * MEGA)

Ditto. But are you sure that it's 17 and not 16?

...

drivers/iio/adc/ad4170.c line 268

Quoted 3 lines
drivers/iio/adc/ad4170.c new file +3 -0
+static const unsigned int ad4170_sinc3_filt_fs_tbl[] = {
+	4, 8, 12, 16, 20, 40, 48, 80, 100, 256, 500, 1000, 5000, 8332, 10000,
+	25000, 50000, 65532,

Please, make something like 4 or 8 per line with a comment.

	4, 8, 12, 16, 20, 40, 48, 80,			/*  0 -  7 */
	...						/*  8 - 15 */
	...
Quoted 1 line

+};

...

drivers/iio/adc/ad4170.c line 328

Quoted 8 lines
drivers/iio/adc/ad4170.c new file +8 -0
+struct ad4170_chan_info {
+	int setup_num; /* Index to access state setup_infos array */
+	struct ad4170_setup setup; /* cached setup */
+	int input_range_uv;
+	u32 scale_tbl[10][2];
+	int offset_tbl[10];
+	bool initialized;
+	bool enabled;

pahole approved?

Quoted 1 line

+};

...

Quoted 1 line

+struct ad4170_state {

Ditto.

Quoted 19 lines
drivers/iio/adc/ad4170.c new file +19 -0
+	struct regmap *regmap;
+	struct spi_device *spi;
+	int vrefs_uv[AD4170_MAX_SUP];
+	struct mutex lock; /* Protect read-modify-write and multi write sequences */
+	struct iio_chan_spec chans[AD4170_MAX_CHANNELS];
+	struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS];
+	struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS];
+	u32 mclk_hz;
+	int pins_fn[AD4170_NUM_ANALOG_PINS];
+	u32 int_pin_sel;
+	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
+	struct completion completion;
+	/*
+	 * DMA (thus cache coherency maintenance) requires the transfer buffers
+	 * to live in their own cache lines.
+	 */
+	u8 tx_buf[AD4170_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN);
+	u8 rx_buf[AD4170_SPI_MAX_XFER_LEN];
+};

...

drivers/iio/adc/ad4170.c line 395

Quoted 7 lines
drivers/iio/adc/ad4170.c new file +6 -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);
 
+	int ret = -EINVAL;

Redundant assignment.

Quoted 7 lines
drivers/iio/adc/ad4170.c new file +7 -0
+	if (readval)
+		ret = regmap_read(st->regmap, reg, readval);
+	else
+		ret = regmap_write(st->regmap, reg, writeval);
+
+	return ret;
+}

...

Quoted 5 lines
drivers/iio/adc/ad4170.c new file +5 -0
+static int ad4170_find_setup(struct ad4170_state *st,
+			     struct ad4170_setup *target_setup,
+			     unsigned int *setup_num, bool *overwrite)
+{
+	unsigned int i;

drivers/iio/adc/ad4170.c line 506

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+
+	*setup_num = AD4170_INVALID_SETUP;
+	*overwrite = false;
+
+	for (i = 0; i < AD4170_MAX_SETUPS; i++) {
+		struct ad4170_setup_info *setup_info = &st->setup_infos[i];
+
+		/* Immediately accept a matching setup. */
+		if (!memcmp(target_setup, &setup_info->setup,
+			    sizeof(*target_setup))) {

Does this handle paddings okay?

Quoted 20 lines
drivers/iio/adc/ad4170.c new file +20 -0
+			*setup_num = i;
+			return 0;
+		}
+
+		/* Ignore all setups which are used by enabled channels. */
+		if (setup_info->enabled_channels)
+			continue;
+
+		/* Find the least used slot. */
+		if (*setup_num == AD4170_INVALID_SETUP ||
+		    setup_info->channels < st->setup_infos[*setup_num].channels)
+			*setup_num = i;
+	}
+
+	if (*setup_num == AD4170_INVALID_SETUP)
+		return -EINVAL;
+
+	*overwrite = true;
+	return 0;
+}

...

drivers/iio/adc/ad4170.c line 563

Quoted 13 lines
drivers/iio/adc/ad4170.c new file +12 -0
+static int ad4170_link_channel_setup(struct ad4170_state *st,
+				     unsigned int chan_addr,
+				     unsigned int setup_num)
+{
+	struct ad4170_setup_info *setup_info = &st->setup_infos[setup_num];
+	struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr];
+	int ret;
+
+	ret = regmap_update_bits(st->regmap, AD4170_CHAN_SETUP_REG(chan_addr),
+				 AD4170_CHAN_SETUP_SETUP_MSK,
 
+				 FIELD_PREP(AD4170_CHAN_SETUP_SETUP_MSK,
+					    setup_num));

I think it's fine to have these two on one line.

Quoted 7 lines
drivers/iio/adc/ad4170.c new file +7 -0
+	if (ret)
+		return ret;
+
+	chan_info->setup_num = setup_num;
+	setup_info->channels++;
+	return 0;
+}

...

drivers/iio/adc/ad4170.c line 672

Quoted 12 lines
drivers/iio/adc/ad4170.c new file +12 -0
+	/* Case 4 */
+	if (on_enable && chan_info->setup_num != AD4170_INVALID_SETUP)
+		return 0;
+
+	if (!on_enable && !chan_info->enabled) {
+		if (chan_info->setup_num != AD4170_INVALID_SETUP)
+			/* Case 3 */
+			ad4170_unlink_channel(st, chan_addr);
+
+		/* Cases 3 & 5 */
+		return 0;
+	}

I believe this can be refactored a bit.

	/* Cases 3, 4, and 5 */
	if (chan_info->setup_num != AD4170_INVALID_SETUP) {
		/* Case 4 */
		if (on_enable)
			return 0;

		/* Case 3 */
		if (!chan_info->enabled) {
			ad4170_unlink_channel(st, chan_addr);
			return 0;
		}
	} else if (!on_enable && !chan_info->enabled) {
		/* Case 5 */
		return 0;
	}

In my opinion it gives each case separated and hence better understanding of the code flow. For the curiosity one may check the object file sizes (with help of bloat-o-meter script).

...

drivers/iio/adc/ad4170.c line 749

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

Redundant assignment.

...

drivers/iio/adc/ad4170.c line 869

Quoted 4 lines
drivers/iio/adc/ad4170.c new file +4 -0
+	switch (ain_n) {
+	case AD4170_CHAN_MAP_AVDD_AVSS_N:
+		*ain_voltage = (st->vrefs_uv[AD4170_AVDD_SUP]
+				- st->vrefs_uv[AD4170_AVSS_SUP]) / 5;

Please use better indentation and split.

	... tmp;

		tmp = st->vrefs_uv[AD4170_AVDD_SUP] - st->vrefs_uv[AD4170_AVSS_SUP];
		*ain_voltage = tmp / 5;
Quoted 31 lines
drivers/iio/adc/ad4170.c new file +31 -0
+		return 0;
+	case AD4170_CHAN_MAP_IOVDD_DGND_N:
+		*ain_voltage = st->vrefs_uv[AD4170_IOVDD_SUP] / 5;
+		return 0;
+	case AD4170_CHAN_MAP_AVSS:
+		*ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP];
+		return 0;
+	case AD4170_CHAN_MAP_DGND:
+		*ain_voltage = 0;
+		return 0;
+	case AD4170_CHAN_MAP_REFIN1_P:
+		if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV)
+			return dev_err_probe(dev, -ENODEV,
+					     "input set to REFIN+ but ref not provided\n");
+
+		*ain_voltage = st->vrefs_uv[AD4170_REFIN1P_SUP];
+		return 0;
+	case AD4170_CHAN_MAP_REFIN1_N:
+		if (st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV)
+			return dev_err_probe(dev, -ENODEV,
+					     "input set to REFIN- but ref not provided\n");
+
+		*ain_voltage = st->vrefs_uv[AD4170_REFIN1N_SUP];
+		return 0;
+	case AD4170_CHAN_MAP_REFIN2_P:
+		if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV)
+			return dev_err_probe(dev, -ENODEV,
+					     "input set to REFIN2+ but ref not provided\n");
+
+		*ain_voltage = st->vrefs_uv[AD4170_REFIN2P_SUP];
+		return 0;

drivers/iio/adc/ad4170.c line 910

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	case AD4170_CHAN_MAP_REFIN2_N:
+		if (st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV)
+			return dev_err_probe(dev, -ENODEV,
+					     "input set to REFIN2- but ref not provided\n");
+
+		*ain_voltage = st->vrefs_uv[AD4170_REFIN2N_SUP];
+		return 0;
+	case AD4170_CHAN_MAP_REFOUT:
+		/* REFOUT is 2.5V relative to AVSS so take that into account */
+		*ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP] + (2500 * MILLI);

Using 2500000 here is probably slightly better choice as MILLI a bit ambiguous (since the result is in MICROvolts).

drivers/iio/adc/ad4170.c line 438

Quoted 4 lines
drivers/iio/adc/ad4170.c new file +3 -0
+		return 0;
+	}
 
+	return -EINVAL;

Make this default case.

...

drivers/iio/adc/ad4170.c line 957

Quoted 7 lines
drivers/iio/adc/ad4170.c new file +7 -0
+static int ad4170_validate_channel(struct ad4170_state *st,
+				   struct iio_chan_spec const *chan)
+{
+	int ret;
+
+	ret = ad4170_validate_channel_input(st, chan->channel, false);
+	if (ret < 0)

Why ' < 0'? Same Q to all similar checks. (And here you even have it inconsistent with the below)

Quoted 5 lines
drivers/iio/adc/ad4170.c new file +5 -0
+		return ret;
+
+	return ad4170_validate_channel_input(st, chan->channel2,
+					     !chan->differential);
+}

...

Quoted 4 lines
drivers/iio/adc/ad4170.c new file +4 -0
+static int ad4170_get_input_range(struct ad4170_state *st,
+				  struct iio_chan_spec const *chan,
+				  unsigned int ch_reg, unsigned int ref_sel)
+{

drivers/iio/adc/ad4170.c line 982

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	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+, REFIN− selected but not provided\n");

Maybe

					     "REFIN± selected but not provided\n");

?

drivers/iio/adc/ad4170.c line 991

Quoted 8 lines
drivers/iio/adc/ad4170.c new file +8 -0
+		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+, REFIN2− selected but not provided\n");

Ditto.

drivers/iio/adc/ad4170.c line 1002

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+		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] + (2500 * MILLI);

2500000 ? Perhaps define this constant. I see it second time already.

Quoted 59 lines
drivers/iio/adc/ad4170.c new file +58 -0
+		refn = st->vrefs_uv[AD4170_AVSS_SUP];
+		break;
+	default:
+		return -EINVAL;
+	}
 
+	/*
+	 * Find out the analog input range from the channel type, polarity, and
+	 * voltage reference selection.
+	 * AD4170 channels are either differential or pseudo-differential.
+	 * Diff input voltage range: −VREF/gain to +VREF/gain (datasheet page 6)
+	 * Pseudo-diff input voltage range: 0 to VREF/gain (datasheet page 6)
+	 */
+	if (chan->differential) {
+		if (!bipolar)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Channel %u differential unipolar\n",
+					     ch_reg);
+
+		/*
+		 * Differential bipolar channel.
+		 * avss-supply is never above 0V.
+		 * Assuming refin1n-supply not above 0V.
+		 * Assuming refin2n-supply not above 0V.
+		 */
+		return refp + abs(refn);
+	}
+	/*
+	 * Some configurations can lead to invalid setups.
+	 * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS),
+	 * and pseudo-diff channel configuration set, then the input range
+	 * should go from 0V to +VREF (single-ended - datasheet pg 10), but
+	 * REFOUT/AVSS range would be -2.5V to 0V.
+	 * Check the positive reference is higher than 0V for pseudo-diff
+	 * channels.
+	 */
+	if (refp <= 0)
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "REF+ <= GND for pseudo-diff chan %u\n",
+				     ch_reg);
+
+	if (bipolar)
+		return refp;
+
+	/*
+	 * Pseudo-differential unipolar channel.
+	 * Input expected to swing from IN- to +VREF.
+	 */
+	ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ain_voltage);
+	if (ret < 0)
+		return ret;
+
+	if (refp - ain_voltage <= 0)
+		return dev_err_probe(&st->spi->dev, -EINVAL,
+				     "Negative input >= REF+ for pseudo-diff chan %u\n",
+				     ch_reg);
+
+	return refp - ain_voltage;
+}

...

drivers/iio/adc/ad4170.c line 1081

Quoted 1 line
drivers/iio/adc/ad4170.c new file +1 -0
+	settling_time_ms = DIV_ROUND_UP(6291164 * MILLI, st->mclk_hz);

Would it work on 32-bit? int*long == long.

...

drivers/iio/adc/ad4170.c line 1200

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+	ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ainm_voltage);
+	if (ret < 0)

Why ' < 0' ?

drivers/iio/adc/ad4170.c line 1201

Quoted 1 line
drivers/iio/adc/ad4170.c new file +1 -0
+		return dev_err_probe(&st->spi->dev, ret,
	struct device = &st->spi->dev;

drivers/iio/adc/ad4170.c line 1202

Quoted 1 line
drivers/iio/adc/ad4170.c new file +1 -0
+				     "Failed to fill scale table\n");

will help in this and many other cases to make them shorter.

...

drivers/iio/adc/ad4170.c line 1219

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+		nv = (u64)chan_info->input_range_uv * NANO;
+		lshift = (pga >> 3 & 1);  /* handle cases 8 and 9 */
		lshift = !!(pga & BIT(3));

?

drivers/iio/adc/ad4170.c line 1220

Quoted 1 line
drivers/iio/adc/ad4170.c new file +1 -0
+		rshift = precision_bits - bipolar + (pga & 0x7) - lshift;

GENMASK() ?

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+		chan_info->scale_tbl[pga][0] = 0;
+		chan_info->scale_tbl[pga][1] = div_u64(nv >> rshift, MILLI);

...

drivers/iio/adc/ad4170.c line 1348

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

Why '>' ?

Quoted 1 line
(quoted diff) +1 -0
+		return -EINVAL;

...

drivers/iio/adc/ad4170.c line 1541

Quoted 3 lines
drivers/iio/adc/ad4170.c new file +3 -0
+	ret = fwnode_property_read_u32_array(child, "diff-channels", pins,
+					     ARRAY_SIZE(pins));
+	if (!ret) {

Use usual patter, checking for errors first.

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

Quoted 11 lines
drivers/iio/adc/ad4170.c new file +10 -0
+static int ad4170_parse_channels(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	unsigned int num_channels;
+	unsigned int chan_num;
+	int ret;
+
+	num_channels = device_get_child_node_count(dev);
 
+

Redundant blank line.

Quoted 13 lines
drivers/iio/adc/ad4170.c new file +13 -0
+	if (num_channels > AD4170_MAX_CHANNELS)
+		return dev_err_probe(dev, -EINVAL, "Too many channels\n");
+
+	device_for_each_child_node_scoped(dev, child) {
+		ret = ad4170_parse_channel_node(indio_dev, child, chan_num++);
+		if (ret)
+			return ret;
+	}
+
+	indio_dev->num_channels = num_channels;
+	indio_dev->channels = st->chans;
+	return 0;
+}

...

drivers/iio/adc/ad4170.c line 1641

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

Why i is signed? Ditto for many similar cases.

Quoted 15 lines
drivers/iio/adc/ad4170.c new file +15 -0
+	st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
+
+	for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
+		st->pins_fn[i] = AD4170_PIN_UNASIGNED;
+
+	/* On power on, device defaults to using SDO pin for data ready signal */
+	st->int_pin_sel = AD4170_INT_PIN_SDO;
+	ret = device_property_match_property_string(dev, "interrupt-names",
+						    ad4170_int_pin_names,
+						    ARRAY_SIZE(ad4170_int_pin_names));
+	if (ret >= 0)
+		st->int_pin_sel = ret;
+
+	reg_data = FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK,
+			      st->int_pin_sel == AD4170_INT_PIN_DIG_AUX1 ?

drivers/iio/adc/ad4170.c line 1667

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+			      AD4170_PIN_MUXING_DIG_AUX1_RDY :
+			      AD4170_PIN_MUXING_DIG_AUX1_DISABLED);
+
+	ret = regmap_update_bits(st->regmap, AD4170_PIN_MUXING_REG,
+				 AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, reg_data);
+	if (ret)
+		return ret;
+
+	return ad4170_parse_channels(indio_dev);
+}

...

This was really hard to review and I am sure I missed something.

Please, split this to 3+ patches (basic + feature1 + feature2 + ...).

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#6 lore raw

Re: [PATCH v2 2/7] iio: adc: Add basic support for AD4170

...

Quoted 5 lines

drivers/iio/adc/ad4170.c line 167

Quoted 3 lines
drivers/iio/adc/ad4170.c new file +2 -0
+#define AD4170_EXT_CLOCK_MHZ_MIN			(1 * MEGA)
+#define AD4170_EXT_CLOCK_MHZ_MAX			(17 * MEGA)

Ditto. But are you sure that it's 17 and not 16?

The maximum external clock rates shown in the datasheet are 17 MHz. The minimum clock low/high times are 27.6 ns, resulting in roughly 18 MHz. So, the specifications indicate that 17 MHz clock would be okay to operate AD4170.

...

Quoted 7 lines

...

drivers/iio/adc/ad4170.c line 1219

Quoted 2 lines
drivers/iio/adc/ad4170.c new file +2 -0
+		nv = (u64)chan_info->input_range_uv * NANO;
+		lshift = (pga >> 3 & 1);  /* handle cases 8 and 9 */
		lshift = !!(pga & BIT(3));
Quoted 5 lines

?

drivers/iio/adc/ad4170.c line 1220

Quoted 1 line
drivers/iio/adc/ad4170.c new file +1 -0
+		rshift = precision_bits - bipolar + (pga & 0x7) - lshift;

GENMASK() ?

		rshift = precision_bits - bipolar + (pga & GENMASK(2, 0)) - lshift;

?

Quoted 5 lines

...

This was really hard to review and I am sure I missed something.

Please, split this to 3+ patches (basic + feature1 + feature2 + ...).

Sorry about that. This part is so flexible that even basic ADC channel support becomes quite complex due to the dynamic setup configurations and possible combinations of input type (differential / pseudo-diff) and PGA. For v3, I split calib gain, offset, and samp freq into separate patches. The PGA, though, is harder to disentangle from the rest of the base driver.

Thanks,
Marcelo

maintainer
#7 lore raw

Re: [PATCH v2 2/7] iio: adc: Add basic support for AD4170

In reply to Marcelo Schmitt
Quoted 9 lines · On Mon, 28 Apr 2025 09:28:03 -0300 Marcelo Schmitt…

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

Add support for the AD4170 ADC with the following features:
- Single-shot read.
- Analog front end PGA configuration.
- Digital filter and sampling frequency configuration.
- Calibration gain and offset configuration.
- Differential and pseudo-differential input configuration.

Quoted 3 lines

Various comments inline, but as Andy mentioned, this is not exactly bite sized to review so the quality will suffer. I know I need to take a break from the screen after this one! :) If you can break it up further that would be great. Some smaller things like filter control are often relatively easy to separate off.

Jonathan

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +4 -0
new file mode 100644
index 000000000000..4d0af15cb48d
@@ -0,0 +1,1888 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2024 Analog Devices, Inc.
+ * Author: Ana-Maria Cusco <[email protected]>

drivers/iio/adc/ad4170.c line 14

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+ * Author: Marcelo Schmitt <[email protected]>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>

This might belong in a later patch. Not sure we have custom attrs in this one.

Quoted 15 lines
drivers/iio/adc/ad4170.c new file +14 -0
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
 
+/* AD4170 register constants */
+

drivers/iio/adc/ad4170.c

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+/* AD4170_CHAN_MAP_REG constants */
+#define AD4170_CHAN_MAP_AIN0			0
+#define AD4170_CHAN_MAP_AIN1			1
+#define AD4170_CHAN_MAP_AIN2			2
+#define AD4170_CHAN_MAP_AIN3			3
+#define AD4170_CHAN_MAP_AIN4			4
+#define AD4170_CHAN_MAP_AIN5			5
+#define AD4170_CHAN_MAP_AIN6			6
+#define AD4170_CHAN_MAP_AIN7			7
+#define AD4170_CHAN_MAP_AIN8			8

Maybe a macro for these?
#define AD4170_CHAN_MAP_AIN(x) (x)

Perhaps it's not worth bothering. I didn't check!

Quoted 34 lines
drivers/iio/adc/ad4170.c new file +33 -0
+#define AD4170_CHAN_MAP_TEMP_SENSOR		17
+#define AD4170_CHAN_MAP_AVDD_AVSS_P		18
+#define AD4170_CHAN_MAP_AVDD_AVSS_N		18
+#define AD4170_CHAN_MAP_IOVDD_DGND_P		19
+#define AD4170_CHAN_MAP_IOVDD_DGND_N		19
+#define AD4170_CHAN_MAP_DAC_P			20
+#define AD4170_CHAN_MAP_DAC_N			20
+#define AD4170_CHAN_MAP_ALDO			21
+#define AD4170_CHAN_MAP_DLDO			22
+#define AD4170_CHAN_MAP_AVSS			23
+#define AD4170_CHAN_MAP_DGND			24
+#define AD4170_CHAN_MAP_REFIN1_P		25
+#define AD4170_CHAN_MAP_REFIN1_N		26
+#define AD4170_CHAN_MAP_REFIN2_P		27
+#define AD4170_CHAN_MAP_REFIN2_N		28
+#define AD4170_CHAN_MAP_REFOUT			29
 
+/*
+ * There are 8 of each MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN
+ * configuration registers. That is, there are 8 miscellaneous registers, MISC0
+ * to MISC7. Each MISC register is associated with a setup; MISCN is associated
+ * with setup number N. The other 5 above mentioned types of registers have
+ * analogous structure. A setup is a set of those registers. For example,
+ * setup 1 comprises of MISC1, AFE1, FILTER1, FILTER_FS1, OFFSET1, and GAIN1
+ * registers. Also, there are 16 CHANNEL_SETUP registers (CHANNEL_SETUP0 to
+ * CHANNEL_SETUP15). Each channel setup is associated with one of the 8 possible
+ * setups. Thus, AD4170 can support up to 16 channels but, since there are only
+ * 8 available setups, channels must share settings if more than 8 channels are
+ * configured.
+ */
+struct ad4170_setup {
+	u16 misc;
+	u16 afe;
+	u16 filter;

drivers/iio/adc/ad4170.c

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	u16 filter_fs;
+	u32 offset; /* For calibration purposes */
+	u32 gain; /* For calibration purposes */
+};
+
+struct ad4170_setup_info {
+	struct ad4170_setup setup;
+	unsigned int enabled_channels;
+	unsigned int channels;
+};

As mentioned below. We had some concerns about matching with memcpy on these structures. Now I don't see holes today so in theory it is fine, but I think we should still do an explicit matching function that checks field by field just to avoid potential future issues.

Quoted 22 lines
drivers/iio/adc/ad4170.c new file +21 -0
+struct ad4170_state {
+	struct regmap *regmap;
+	struct spi_device *spi;
+	int vrefs_uv[AD4170_MAX_SUP];
+	struct mutex lock; /* Protect read-modify-write and multi write sequences */
+	struct iio_chan_spec chans[AD4170_MAX_CHANNELS];
+	struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS];
+	struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS];
+	u32 mclk_hz;
+	int pins_fn[AD4170_NUM_ANALOG_PINS];
+	u32 int_pin_sel;
+	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
+	struct completion completion;
+	/*
+	 * DMA (thus cache coherency maintenance) requires the transfer buffers
+	 * to live in their own cache lines.
+	 */
+	u8 tx_buf[AD4170_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN);
+	u8 rx_buf[AD4170_SPI_MAX_XFER_LEN];
+};
+

drivers/iio/adc/ad4170.c line 398

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +9 -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);
+	int ret = -EINVAL;
+
+	if (readval)
+		ret = regmap_read(st->regmap, reg, readval);

return regmap_read()

Quoted 38 lines
drivers/iio/adc/ad4170.c new file +37 -0
+	else
+		ret = regmap_write(st->regmap, reg, writeval);
+
+	return ret;
+}
 
+
+static int ad4170_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct ad4170_state *st = context;
+	struct spi_transfer t[] = {
+		{
+			.tx_buf = st->tx_buf,
+			.len = 2,
+		},
+		{
+			.rx_buf = st->rx_buf,
+		},
+	};
+	unsigned int size;
+	int ret;
+
+	ret = ad4170_get_reg_size(st, reg, &size);
+	if (ret)
+		return ret;
+
+	put_unaligned_be16(AD4170_REG_READ_MASK | reg, st->tx_buf);
+	t[1].len = size;
+
+	ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
+	if (ret)
+		return ret;
+
+	switch (size) {
+	case 3:
+		*val = get_unaligned_be24(st->rx_buf);
+		break;
+	case 2:

drivers/iio/adc/ad4170.c line 484

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+		*val = get_unaligned_be16(st->rx_buf);
+		break;
+	case 1:
+		*val = st->rx_buf[0];
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;

Might as well return in all the good paths instead of break.

Quoted 8 lines
drivers/iio/adc/ad4170.c new file +7 -0
+}
 
+
+static int ad4170_find_setup(struct ad4170_state *st,
+			     struct ad4170_setup *target_setup,
+			     unsigned int *setup_num, bool *overwrite)
+{
+	unsigned int i;

drivers/iio/adc/ad4170.c line 506

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+
+	*setup_num = AD4170_INVALID_SETUP;
+	*overwrite = false;
+
+	for (i = 0; i < AD4170_MAX_SETUPS; i++) {
+		struct ad4170_setup_info *setup_info = &st->setup_infos[i];
+
+		/* Immediately accept a matching setup. */
+		if (!memcmp(target_setup, &setup_info->setup,
+			    sizeof(*target_setup))) {

Didn't we run into concerns with holes, variable structure definitions etc for these in other drivers and change them all to explicit field matches?

Quoted 43 lines
drivers/iio/adc/ad4170.c new file +41 -0
+			*setup_num = i;
+			return 0;
+		}
+
+		/* Ignore all setups which are used by enabled channels. */
+		if (setup_info->enabled_channels)
+			continue;
+
+		/* Find the least used slot. */
+		if (*setup_num == AD4170_INVALID_SETUP ||
+		    setup_info->channels < st->setup_infos[*setup_num].channels)
+			*setup_num = i;
+	}
+
+	if (*setup_num == AD4170_INVALID_SETUP)
+		return -EINVAL;
+
+	*overwrite = true;
+	return 0;
+}
 
+
+static int ad4170_set_filter_type(struct iio_dev *indio_dev,
+				  struct iio_chan_spec const *chan,
+				  unsigned int val)
+{
 
+
+	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) {

drivers/iio/adc/ad4170.c

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+		if (setup->filter_fs < AD4170_SINC3_MIN_FS)
+			setup->filter_fs = AD4170_SINC3_MIN_FS;
+		if (setup->filter_fs > AD4170_SINC3_MAX_FS)
+			setup->filter_fs = AD4170_SINC3_MAX_FS;
+
+	} else if (val == AD4170_SINC5) {
+		if (setup->filter_fs < AD4170_SINC5_MIN_FS)
+			setup->filter_fs = AD4170_SINC5_MIN_FS;
+		if (setup->filter_fs > AD4170_SINC5_MAX_FS)
+			setup->filter_fs = AD4170_SINC5_MAX_FS;

Smells like a clamp()?

Quoted 13 lines
drivers/iio/adc/ad4170.c new file +11 -0
+	}
 
 
+static int __ad4170_read_sample(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan, int *val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int settling_time_ms, ret;
+
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_SINGLE);
+	if (ret)
+		return ret;
+

drivers/iio/adc/ad4170.c line 1082

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	/*
+	 * When a channel is manually selected by the user, the ADC needs an
+	 * extra time to provide the first stable conversion. The ADC settling
+	 * time depends on the filter type, filter frequency, and ADC clock
+	 * frequency (see datasheet page 53). The maximum settling time among
+	 * all filter configurations is 6291164 / fCLK. Use that formula to wait
+	 * for sufficient time whatever the filter configuration may be.
+	 */
+	settling_time_ms = DIV_ROUND_UP(6291164 * MILLI, st->mclk_hz);
+	reinit_completion(&st->completion);

No chance this triggers before we get here? I'd normally expect the reinit_completion before the thing that might trigger it to be complete which I'd guess is the ad4170_set_mode() call?

Quoted 26 lines
drivers/iio/adc/ad4170.c new file +26 -0
+	ret = wait_for_completion_timeout(&st->completion,
+					  msecs_to_jiffies(settling_time_ms));
+	if (!ret)
+		dev_dbg(&st->spi->dev,
+			"No Data Ready signal. Reading after delay.\n");
+
+	ret = regmap_read(st->regmap, AD4170_DATA_24B_REG, val);
+	if (ret)
+		return ret;
+
+	if (chan->scan_type.sign == 's')
+		*val = sign_extend32(*val, chan->scan_type.realbits - 1);
+
+	return 0;
+}
+
+static int ad4170_read_sample(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan, int *val)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+	/*
+	 * The ADC sequences through all enabled channels. That can lead to
+	 * incorrect channel being sampled if a previous read would have left a

drivers/iio/adc/ad4170.c line 1118

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	 * different channel enabled. Thus, always enable and disable the
+	 * channel on single-shot read.
+	 */
+	ret = ad4170_set_channel_enable(st, chan->address, true);
+	if (ret)
+		return ret;
+
+	ret = __ad4170_read_sample(indio_dev, chan, val);
+	if (ret)
+		dev_err(&st->spi->dev, "failed to read sample: %d\n", ret);

I guess we print it which is better than nothing, but we also eat the return code if the channel disable succeeds which is not good. You will need to use multiple ret/ret2 here or duplicate the set_channel_enable() code in an error path and main path.

Quoted 48 lines
drivers/iio/adc/ad4170.c new file +48 -0
+
+	ret = ad4170_set_channel_enable(st, chan->address, false);
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+static int ad4170_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long info)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+	struct ad4170_setup *setup = &chan_info->setup;
+	enum ad4170_filter_type f_type;
+	unsigned int pga, fs_idx;
+	int ret;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+
+		ret = ad4170_read_sample(indio_dev, chan, val);
+		iio_device_release_direct(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_SCALE:
+		pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+		*val = chan_info->scale_tbl[pga][0];
+		*val2 = chan_info->scale_tbl[pga][1];
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_OFFSET:
+		pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+		*val = chan_info->offset_tbl[pga];
+		return IIO_VAL_INT;
+	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];
+			break;
+		case AD4170_SINC5:

drivers/iio/adc/ad4170.c line 1176

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+			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];
+			break;
+		default:
+			return -EINVAL;
+		}
+		return IIO_VAL_INT_PLUS_MICRO;

I'd return earlier where the breaks are to give a tiny bit simpler flow.

Quoted 33 lines
drivers/iio/adc/ad4170.c new file +33 -0
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*val = setup->offset;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = setup->gain;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev,
+				 struct iio_chan_spec const *chan)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+	int bipolar = chan->scan_type.sign == 's' ? 1 : 0;
+	int precision_bits = chan->scan_type.realbits;
+	int pga, ainm_voltage, ret;
+	unsigned long long offset;
+
+	ainm_voltage = 0;
+	ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ainm_voltage);
+	if (ret < 0)
+		return dev_err_probe(&st->spi->dev, ret,
+				     "Failed to fill scale table\n");
+
+	for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) {
+		u64 nv;
+		unsigned int lshift, rshift;
+
+		/*
+		 * The scale factor to get ADC output codes to values in mV

drivers/iio/adc/ad4170.c line 1219

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+		 * units is given by:
+		 * _scale = (input_range / gain) / 2^precision
+		 * AD4170 gain is a power of 2 so the above can be written as
+		 * _scale = input_range / 2^(precision + gain)
+		 * Keep the input range in µV to avoid truncating the less
+		 * significan bits when right shifting it so to preserve scale
+		 * precision.
+		 */
+		nv = (u64)chan_info->input_range_uv * NANO;
+		lshift = (pga >> 3 & 1);  /* handle cases 8 and 9 */

I'm lost. Where did 8 and 9 come from?

Quoted 39 lines
drivers/iio/adc/ad4170.c new file +39 -0
+		rshift = precision_bits - bipolar + (pga & 0x7) - lshift;
+		chan_info->scale_tbl[pga][0] = 0;
+		chan_info->scale_tbl[pga][1] = div_u64(nv >> rshift, MILLI);
+
+		/*
+		 * If the negative input is not at GND, the conversion result
+		 * (which is relative to IN-) will be offset by the level at IN-.
+		 * Use the scale factor the other way around to go from a known
+		 * voltage to the corresponding ADC output code.
+		 * With that, we are able to get to what would be the output
+		 * code for the voltage at the negative input.
+		 * If the negative input is not fixed, there is no offset.
+		 */
+		offset = ((unsigned long long)abs(ainm_voltage)) * MICRO;
+		offset = DIV_ROUND_CLOSEST_ULL(offset, chan_info->scale_tbl[pga][1]);
+
+		/*
+		 * After divided by the scale, offset will always fit into 31
+		 * bits. For _raw + _offset to be relative to GND, the value
+		 * provided as _offset is of opposite sign than the real offset.
+		 */
+		if (ainm_voltage > 0)
+			chan_info->offset_tbl[pga] = -(int)(offset);
+		else
+			chan_info->offset_tbl[pga] = (int)(offset);
+	}
+	return 0;
+}
+
+static int ad4170_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type, int *length,
+			     long info)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+	enum ad4170_filter_type f_type;
+
+	switch (info) {

drivers/iio/adc/ad4170.c line 1268

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	case IIO_CHAN_INFO_SCALE:
+		*vals = (int *)chan_info->scale_tbl;
+		*length = ARRAY_SIZE(chan_info->scale_tbl) * 2;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		f_type = ad4170_get_filter_type(indio_dev, chan);
+		switch (f_type) {
+		case AD4170_SINC5_AVG:
+			fallthrough;

If there is nothing in it, IIRC you can just skip the fallthrough.

		case AD4170_SINC5_AVG:
		case AD4170_SINC3:
			*vals = ...
Quoted 38 lines
drivers/iio/adc/ad4170.c new file +38 -0
+		case AD4170_SINC3:
+			*vals = (int *)st->sps_tbl[f_type];
+			*length = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) * 2;
+			break;
+		case AD4170_SINC5:
+			*vals = (int *)st->sps_tbl[f_type];
+			*length = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl) * 2;
+			break;
+		default:
+			return -EINVAL;
+		}
+		*type = IIO_VAL_INT_PLUS_MICRO;
+
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4170_set_pga(struct ad4170_state *st,
+			  struct iio_chan_spec const *chan, int val, int val2)
+{
+	struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+	struct ad4170_setup *setup = &chan_info->setup;
+	unsigned int old_pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
+	unsigned int pga;
+	int ret;
+
+	for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) {
+		if (val == chan_info->scale_tbl[pga][0] &&
+		    val2 == chan_info->scale_tbl[pga][1])
+			break;
+	}
+
+	if (pga == AD4170_NUM_PGA_OPTIONS)
+		return -EINVAL;
+
+	if (pga == old_pga)

drivers/iio/adc/ad4170.c line 1316

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+		return 0;
+
+	guard(mutex)(&st->lock);
+	setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+	setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, pga);
+
+	ret = ad4170_write_channel_setup(st, chan->address, false);
+	if (ret) {
+		setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK;
+		setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, old_pga);

I'd

		return ret;

drivers/iio/adc/ad4170.c line 402

Quoted 3 lines
drivers/iio/adc/ad4170.c new file +3 -0
+	}
+
+	return ret;
	return 0;

So the error path is nicely separated.

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

drivers/iio/adc/ad4170.c line 1652

Quoted 10 lines
drivers/iio/adc/ad4170.c new file +10 -0
+	st->mclk_hz = AD4170_INT_CLOCK_16MHZ;
+
+	for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++)
+		st->pins_fn[i] = AD4170_PIN_UNASIGNED;
+
+	/* On power on, device defaults to using SDO pin for data ready signal */
+	st->int_pin_sel = AD4170_INT_PIN_SDO;
+	ret = device_property_match_property_string(dev, "interrupt-names",
+						    ad4170_int_pin_names,
+						    ARRAY_SIZE(ad4170_int_pin_names));

Hmm. This is unusual handling. Normally we just try to get the first interrupt by name and if that fails move on to the second one. What you have here seems valid just not a choice I recall seeing before.

Quoted 15 lines
drivers/iio/adc/ad4170.c new file +15 -0
+	if (ret >= 0)
+		st->int_pin_sel = ret;
+
+	reg_data = FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK,
+			      st->int_pin_sel == AD4170_INT_PIN_DIG_AUX1 ?
+			      AD4170_PIN_MUXING_DIG_AUX1_RDY :
+			      AD4170_PIN_MUXING_DIG_AUX1_DISABLED);
+
+	ret = regmap_update_bits(st->regmap, AD4170_PIN_MUXING_REG,
+				 AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, reg_data);
+	if (ret)
+		return ret;
+
+	return ad4170_parse_channels(indio_dev);
+}
#8 lore raw

PATCH v2 3/7 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 the aforementioned condition is met.

---
changes since v1
- Using bitmap_weight().
- rx_buf changed from __be32 to u8 array to better cope with new regmap config.

 drivers/iio/adc/Kconfig  |   2 +
 drivers/iio/adc/ad4170.c | 199 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 199 insertions(+), 2 deletions(-)
drivers/iio/adc/Kconfig +2 -0
index d5d0308da007..9b4787c127fc 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 +197 -2

276 lines not on this page · raw message

Signature · 1 line
2.47.2
#9 lore raw

Re: [PATCH v2 3/7] iio: adc: ad4170: Add support for buffered data capture

Quoted 7 lines · 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 the aforementioned

checks if the

Quoted 1 line

condition is met.

...

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

Quoted 9 lines
drivers/iio/adc/ad4170.c +9 -0
+static int ad4170_prepare_spi_message(struct ad4170_state *st)
+{
+       /*
+        * Continuous data register read is enabled on buffer postenable so
+        * no instruction phase is needed meaning we don't need to send the
+        * register address to read data. Transfer only needs the read buffer.
+        */
+       st->xfer.rx_buf = &st->rx_buf;
+       st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits);

This will give, e.g., 3 for the realbits == 24. Is this expected?

Quoted 4 lines
drivers/iio/adc/ad4170.c +4 -0
+       spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
+
+       return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg);
+}

...

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

Quoted 4 lines
drivers/iio/adc/ad4170.c +4 -0
+static int ad4170_buffer_predisable(struct iio_dev *indio_dev)
+{
+       struct ad4170_state *st = iio_priv(indio_dev);
+       int ret, i;

Why is 'i' signed?

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

Quoted 6 lines
drivers/iio/adc/ad4170.c +6 -0
+       /*
+        * Use a high register address (virtual register) to request a write of
+        * 0xA5 to the ADC during the first 8 SCLKs of the ADC data read cycle,
+        * thus exiting continuous read.
+        */
+       ret = regmap_write(st->regmap, AD4170_ADC_CTRL_CONT_READ_EXIT_REG, 0);

No error check.

Quoted 14 lines
drivers/iio/adc/ad4170.c +14 -0
+       ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+                                AD4170_ADC_CTRL_CONT_READ_MSK,
+                                FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK,
+                                           AD4170_ADC_CTRL_CONT_READ_DISABLE));
+       if (ret)
+               return ret;
+
+       ret =  ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+       if (ret)
+               return ret;
+
+       /*
+        * The ADC sequences through all the enabled channels (see datasheet
+        * page 95). That can lead to incorrect channel being read if a

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+        * single-shot read (or buffered read with different active_scan_mask)
+        * is done after buffer disable. Disable all channels so only requested
+        * channels will be read.
+        */
+       for (i = 0; i < indio_dev->num_channels; i++) {
+               ret = ad4170_set_channel_enable(st, i, false);
+               if (ret)
+                       return ret;
+       }
+       return ret;

Wouldn't return 0; suffice?

Quoted 1 line

+}

...

Quoted 7 lines
drivers/iio/adc/ad4170.c +7 -0
+static bool ad4170_validate_scan_mask(struct iio_dev *indio_dev,
+                                     const unsigned long *scan_mask)
+{
+       unsigned int masklength = iio_get_masklength(indio_dev);
+
+       /*
+        * The channel sequencer cycles through the enabled channels in

drivers/iio/adc/ad4170.c static int ad4170_initial_config(struct iio_dev *indio_dev)

Quoted 10 lines
drivers/iio/adc/ad4170.c +8 -0
+        * sequential order, from channel 0 to channel 15, bypassing disabled
+        * channels. When more than one channel is enabled, channel 0 must
+        * always be enabled. See datasheet channel_en register description at
+        * page 95.
+        */
+       if (bitmap_weight(scan_mask, masklength) > 1)
 
+               return test_bit(0, scan_mask);
 
+       return true;

Hmm... Is it possible to get weight == 0 and true here?

Quoted 1 line

+}

...

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

Quoted 6 lines
drivers/iio/adc/ad4170.c +6 -0
+static irqreturn_t ad4170_trigger_handler(int irq, void *p)
+{
+       struct iio_poll_func *pf = p;
+       struct iio_dev *indio_dev = pf->indio_dev;
+       struct ad4170_state *st = iio_priv(indio_dev);
+       int i, ret;

Why is 'i' signed? (And even in the original case it's inconsistent to the similar in another function)

Quoted 13 lines
drivers/iio/adc/ad4170.c +13 -0
+       iio_for_each_active_channel(indio_dev, i) {
+               ret = spi_sync(st->spi, &st->msg);
+               if (ret)
+                       goto err_out;
+
+               st->bounce_buffer[i] = get_unaligned_be32(st->rx_buf);
+       }
+
+       iio_push_to_buffers(indio_dev, st->bounce_buffer);
+err_out:
+       iio_trigger_notify_done(indio_dev->trig);
+       return IRQ_HANDLED;
+}

...

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

Quoted 2 lines
drivers/iio/adc/ad4170.c +2 -0
+               return dev_err_probe(&st->spi->dev, ret,
+                                    "Failed to register trigger\n");

One line?

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#10 lore raw

Re: [PATCH v2 3/7] iio: adc: ad4170: Add support for buffered data capture

Hi Andy, thank you for your review.

...

Quoted 11 lines

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

Quoted 9 lines
drivers/iio/adc/ad4170.c +9 -0
+static int ad4170_prepare_spi_message(struct ad4170_state *st)
+{
+       /*
+        * Continuous data register read is enabled on buffer postenable so
+        * no instruction phase is needed meaning we don't need to send the
+        * register address to read data. Transfer only needs the read buffer.
+        */
+       st->xfer.rx_buf = &st->rx_buf;
+       st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits);

This will give, e.g., 3 for the realbits == 24. Is this expected?

Yes, in continuous read mode the ADC outputs just the conversion result bits (24-bits) so a 3-byte length transfer is enough to get the conversion data for a channel.

Quoted 1 line

...

Quoted 5 lines

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

Quoted 3 lines
drivers/iio/adc/ad4170.c +2 -0
+               return dev_err_probe(&st->spi->dev, ret,
+                                    "Failed to register trigger\n");

One line?

It goes up to 89 columns if make in one line. I know there are other places in this driver where 80 columns are exceeded, but in this case it's easier to avoid going beyond 80 columns without drying up the error message. Anyway, I'll make it one line if it's confirmed to be the preferable way to have it.

Thanks,
Marcelo

#11 lore raw

Re: [PATCH v2 3/7] iio: adc: ad4170: Add support for buffered data capture

On Wed, Apr 30, 2025 at 10:40:21AM -0300, Marcelo Schmitt wrote:

...

Quoted 10 lines
Quoted 4 lines

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

Quoted 2 lines
drivers/iio/adc/ad4170.c +2 -0
+               return dev_err_probe(&st->spi->dev, ret,
+                                    "Failed to register trigger\n");

One line?

It goes up to 89 columns if make in one line. I know there are other places in this driver where 80 columns are exceeded, but in this case it's easier to avoid going beyond 80 columns without drying up the error message. Anyway, I'll make it one line if it's confirmed to be the preferable way to have it.

The string literal endings are relaxed in the line limit for 10+ years...

Signature · 2 lines
With Best Regards,
Andy Shevchenko
maintainer
#12 lore raw

Re: [PATCH v2 3/7] iio: adc: ad4170: Add support for buffered data capture

In reply to Marcelo Schmitt
Quoted 25 lines · On Wed, 30 Apr 2025 10:40:21 -0300 Marcelo Schmitt…

Hi Andy, thank you for your review.

...

Quoted 11 lines

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

Quoted 9 lines
drivers/iio/adc/ad4170.c +9 -0
+static int ad4170_prepare_spi_message(struct ad4170_state *st)
+{
+       /*
+        * Continuous data register read is enabled on buffer postenable so
+        * no instruction phase is needed meaning we don't need to send the
+        * register address to read data. Transfer only needs the read buffer.
+        */
+       st->xfer.rx_buf = &st->rx_buf;
+       st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits);  

This will give, e.g., 3 for the realbits == 24. Is this expected?

Yes, in continuous read mode the ADC outputs just the conversion result bits (24-bits) so a 3-byte length transfer is enough to get the conversion data for a channel.

Quoted 1 line

...

Quoted 4 lines
Quoted 3 lines
drivers/iio/adc/ad4170.c +2 -0
+               return dev_err_probe(&st->spi->dev, ret,
+                                    "Failed to register trigger\n");  
Quoted 7 lines
Quoted 1 line

One line?

It goes up to 89 columns if make in one line. I know there are other places in this driver where 80 columns are exceeded, but in this case it's easier to avoid going beyond 80 columns without drying up the error message. Anyway, I'll make it one line if it's confirmed to be the preferable way to have it.

In here there are what I think are multiple ways to get to the same ultimate device. (indio->dev.parent is used the line above). Better perhaps to have one 'dev' that is appropriate for use in both places.

Quoted 4 lines

Thanks,
Marcelo

maintainer
#13 lore raw

Re: [PATCH v2 3/7] iio: adc: ad4170: Add support for buffered data capture

In reply to Marcelo Schmitt
Quoted 9 lines · On Mon, 28 Apr 2025 09:28:20 -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 the aforementioned condition is met.

A few minor things spotted inline.

Thanks,

Jonathan

Quoted 60 lines

---
changes since v1
- Using bitmap_weight().
- rx_buf changed from __be32 to u8 array to better cope with new regmap config.

 drivers/iio/adc/Kconfig  |   2 +
 drivers/iio/adc/ad4170.c | 199 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 199 insertions(+), 2 deletions(-)
drivers/iio/adc/Kconfig +2 -0
index d5d0308da007..9b4787c127fc 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 +7 -0
index 4d0af15cb48d..5fcf1c023ac2 100644
@@ -10,8 +10,12 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/iio/buffer.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
@@ -54,6 +58,7 @@
 #define AD4170_FILTER_FS_REG(x)				(0xC7 + 14 * (x))
 #define AD4170_OFFSET_REG(x)				(0xCA + 14 * (x))
 #define AD4170_GAIN_REG(x)				(0xCD + 14 * (x))
+#define AD4170_ADC_CTRL_CONT_READ_EXIT_REG		0x200 /* virtual reg */
 
 #define AD4170_REG_READ_MASK				BIT(14)
 
@@ -221,6 +226,7 @@ static const unsigned int ad4170_reg_size[] = {
 	[AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3,
 	[AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3,
 	[AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3,
+	[AD4170_ADC_CTRL_CONT_READ_EXIT_REG] = 0,
 };
 
 enum ad4170_ref_buf {
@@ -347,12 +353,16 @@ struct ad4170_state {
 	u32 int_pin_sel;
 	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
 	struct completion completion;
+	struct iio_trigger *trig;

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +4 -1
+	struct spi_transfer xfer;
+	struct spi_message msg;
+	__be32 bounce_buffer[AD4170_MAX_CHANNELS];
 	/*
 	 * DMA (thus cache coherency maintenance) requires the transfer buffers
 	 * to live in their own cache lines.
 	 */
 	u8 tx_buf[AD4170_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN);
-	u8 rx_buf[AD4170_SPI_MAX_XFER_LEN];
+	u8 rx_buf[4];

Why this change?

Quoted 5 lines
drivers/iio/adc/ad4170.c +3 -0
 };
 
+
+static int ad4170_buffer_postenable(struct iio_dev *indio_dev)
+{

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_CONT);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Enables continuous data register read.
+	 * This enables continuous read of the ADC Data register. The ADC must

First two sentences seem to say the same thing.

Quoted 34 lines
drivers/iio/adc/ad4170.c +34 -0
+	 * be in a continuous conversion mode.
+	 */
+	return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				  AD4170_ADC_CTRL_CONT_READ_MSK,
+				  FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK,
+					     AD4170_ADC_CTRL_CONT_READ_ENABLE));
+}
+
+static int ad4170_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret, i;
+
+	/*
+	 * Use a high register address (virtual register) to request a write of
+	 * 0xA5 to the ADC during the first 8 SCLKs of the ADC data read cycle,
+	 * thus exiting continuous read.
+	 */
+	ret = regmap_write(st->regmap, AD4170_ADC_CTRL_CONT_READ_EXIT_REG, 0);
+
+	ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
+				 AD4170_ADC_CTRL_CONT_READ_MSK,
+				 FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK,
+					    AD4170_ADC_CTRL_CONT_READ_DISABLE));
+	if (ret)
+		return ret;
+
+	ret =  ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE);
+	if (ret)
+		return ret;
+
+	/*
+	 * The ADC sequences through all the enabled channels (see datasheet
+	 * page 95). That can lead to incorrect channel being read if a

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	 * single-shot read (or buffered read with different active_scan_mask)
+	 * is done after buffer disable. Disable all channels so only requested
+	 * channels will be read.
+	 */
+	for (i = 0; i < indio_dev->num_channels; i++) {
+		ret = ad4170_set_channel_enable(st, i, false);
+		if (ret)
+			return ret;
+	}
+	return ret;

return 0; When we know we are in a good path we should make that clear.

Quoted 1 line

+}

#14 lore raw

PATCH v2 4/7 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 it's 16 MHz internal clock at the XTAL2 pin. Extend the AD4170 driver so it effectively uses the provided external clock, if any, or supplies it's own clock as a clock provider.

---
changes since v1
- Added support for clock-output-names

 drivers/iio/adc/Kconfig  |   1 +
 drivers/iio/adc/ad4170.c | 141 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 141 insertions(+), 1 deletion(-)
drivers/iio/adc/Kconfig +1 -0
index 9b4787c127fc..60eb79a7975f 100644
@@ -77,6 +77,7 @@ config AD4170
 	select REGMAP_SPI
 	select IIO_BUFFER
 	select IIO_TRIGGERED_BUFFER
+	select COMMON_CLK
 	help
 	  Say yes here to build support for Analog Devices AD4170 SPI analog
 	  to digital converters (ADC).
drivers/iio/adc/ad4170.c +140 -1

195 lines not on this page · raw message

Signature · 1 line
2.47.2
#15 lore raw

Re: [PATCH v2 4/7] iio: adc: ad4170: Add clock provider support

Quoted 4 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 it's 16 MHz internal clock at the XTAL2 pin. Extend

its

Quoted 2 lines

the AD4170 driver so it effectively uses the provided external clock, if any, or supplies it's own clock as a clock provider.

its

...

drivers/iio/adc/ad4170.c enum ad4170_regulator { · line 276

Quoted 2 lines
drivers/iio/adc/ad4170.c +2 -0
+static const char *const ad4170_clk_sel[] = {
+       "ext-clk", "xtal"

Keep trailing comma when it's not a oneliner assignment.

Quoted 1 line

+};

...

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

Quoted 13 lines
drivers/iio/adc/ad4170.c +13 -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))
+               return 0;
+
+       if (device_property_read_string(dev, "clock-output-names", &init.name)) {
+               init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk",
+                                          fwnode_get_name(dev_fwnode(dev)));

Why not %pfw ?

Quoted 14 lines
drivers/iio/adc/ad4170.c +14 -0
+               if (!init.name)
+                       return -ENOMEM;
+       }
+
+       init.ops = &ad4170_int_clk_ops;
+
+       st->int_clk_hw.init = &init;
+       ret = devm_clk_hw_register(dev, &st->int_clk_hw);
+       if (ret)
+               return ret;
+
+       return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+                                          &st->int_clk_hw);
+}

...

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

Quoted 2 lines
drivers/iio/adc/ad4170.c +2 -0
+       ret = ad4170_clock_select(indio_dev);
+       if (ret < 0)

Why ' < 0' part?

Quoted 5 lines
drivers/iio/adc/ad4170.c +5 -0
+               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;
Signature · 2 lines
With Best Regards,
Andy Shevchenko
#16 lore raw

Re: [PATCH v2 4/7] iio: adc: ad4170: Add clock provider support

In reply to Marcelo Schmitt
kernel test robot noticed the following build warnings:

Hi Marcelo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 1c2409fe38d5c19015d69851d15ba543d1911932]

url:    https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/dt-bindings-iio-adc-Add-AD4170/20250428-222010
base:   1c2409fe38d5c19015d69851d15ba543d1911932
patch link:    https://lore.kernel.org/r/bede8227189637568f9425cd6848e21be33c2fd2.1745841276.git.marcelo.schmitt%40analog.com
patch subject: [PATCH v2 4/7] iio: adc: ad4170: Add clock provider support
config: sh-kismet-CONFIG_COMMON_CLK-CONFIG_AD4170-0-0 (https://download.01.org/0day-ci/archive/20250506/[email protected]/config)
reproduce: (https://download.01.org/0day-ci/archive/20250506/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

kismet warnings: (new ones prefixed by >>)

>> kismet: WARNING: unmet direct dependencies detected for COMMON_CLK when selected by AD4170

WARNING: unmet direct dependencies detected for COMMON_CLK
Depends on [n]: !HAVE_LEGACY_CLK [=y]
Selected by [y]:
- AD4170 [=y] && IIO [=y] && SPI [=y]

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
#17 lore raw

PATCH v2 5/7 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.

---
changes since v1
- 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.

 drivers/iio/adc/Kconfig  |   1 +
 drivers/iio/adc/ad4170.c | 191 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 191 insertions(+), 1 deletion(-)
drivers/iio/adc/Kconfig +1 -0
index 60eb79a7975f..ad141bf48679 100644
@@ -78,6 +78,7 @@ config AD4170
 	select IIO_BUFFER
 	select IIO_TRIGGERED_BUFFER
 	select 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 +190 -1

233 lines not on this page · raw message

Signature · 1 line
2.47.2
#18 lore raw

Re: [PATCH v2 5/7] iio: adc: ad4170: Add GPIO controller support

Quoted 9 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.

...

Quoted 8 lines
drivers/iio/adc/ad4170.c +8 -0
+static int ad4170_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+       struct iio_dev *indio_dev = gpiochip_get_data(gc);
+       struct ad4170_state *st = iio_priv(indio_dev);
+       unsigned int val;
+       int ret;
+
+       if (!iio_device_claim_direct(indio_dev))

drivers/iio/adc/ad4170.c static int ad4170_soft_reset(struct ad4170_state *st) · line 1587

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+               return -EBUSY;
+
+       guard(mutex)(&st->lock);
+       ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val);
+       if (ret)
+               goto err_release;
+
+       if (val & BIT(offset * 2 + 1))
+               ret = regmap_update_bits(st->regmap, AD4170_GPIO_OUTPUT_REG,
+                                        BIT(offset), value << offset);

Haven't you seen my previous review (v1)? I asked what if the value is > 1? I also recommended looking at regmap_assign_bits().

Quoted 7 lines
drivers/iio/adc/ad4170.c +7 -0
+       else
+               ret = -EPERM;
+
+err_release:
+       iio_device_release_direct(indio_dev);
+       return ret;
+}
Signature · 2 lines
With Best Regards,
Andy Shevchenko
maintainer
#19 lore raw

Re: [PATCH v2 5/7] iio: adc: ad4170: Add GPIO controller support

In reply to Marcelo Schmitt
Quoted 10 lines · On Mon, 28 Apr 2025 09:28:55 -0300 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.

For GPIO controllers, even ones outside of driver/gpio we generally +CC the maintainer and list. They don't have to comment but that at least gets it on their radar if we are misusing the GPIO chip infrastructure!
Make sure to do that on v3.

In meantime I didn't look hard, but I'm not immediately seeing why we have to disable the gpios when switching direction.

Jonathan

Quoted 8 lines
drivers/iio/adc/ad4170.c +7 -0
+
+static int ad4170_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+	struct iio_dev *indio_dev = gpiochip_get_data(gc);
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;
+

drivers/iio/adc/ad4170.c static int ad4170_soft_reset(struct ad4170_state *st) · line 1636

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	if (!iio_device_claim_direct(indio_dev))
+		return -EBUSY;
+
+	ret = regmap_clear_bits(st->regmap, AD4170_GPIO_MODE_REG,
+				BIT(offset * 2 + 1));
+	if (ret)
+		goto err_release;
+
+	ret = regmap_set_bits(st->regmap, AD4170_GPIO_MODE_REG,
+			      BIT(offset * 2));

As below. If we have to go via disabled, then we need a comment on why.

Quoted 13 lines
drivers/iio/adc/ad4170.c +13 -0
+
+err_release:
+	iio_device_release_direct(indio_dev);
+
+	return ret;
+}
+
+static int ad4170_gpio_direction_output(struct gpio_chip *gc,
+					unsigned int offset, int value)
+{
+	struct iio_dev *indio_dev = gpiochip_get_data(gc);
+	struct ad4170_state *st = iio_priv(indio_dev);
+	int ret;

drivers/iio/adc/ad4170.c static int ad4170_soft_reset(struct ad4170_state *st) · line 1659

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+
+	ret = ad4170_gpio_set(gc, offset, value);
+	if (ret)
+		return ret;
+
+	if (!iio_device_claim_direct(indio_dev))
+		return -EBUSY;
+
+	ret = regmap_clear_bits(st->regmap, AD4170_GPIO_MODE_REG,
+				BIT(offset * 2));

This is odd enough that if required it needs a comment.
I suspect you can just directly update both bits in one write though and this should be a regmap_update_bits() setting the two bit field to the value 2 which is output (vs 1 which is input).

Quoted 11 lines
drivers/iio/adc/ad4170.c +11 -0
+	if (ret)
+		goto err_release;
+
+	ret = regmap_set_bits(st->regmap, AD4170_GPIO_MODE_REG,
+			      BIT(offset * 2 + 1));
+
+err_release:
+	iio_device_release_direct(indio_dev);
+
+	return ret;
+}
#20 lore raw

PATCH v2 6/7 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.

---
changes since v1:
- Dropped a unneeded comment
- Collected review tag

 drivers/iio/adc/ad4170.c | 72 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 69 insertions(+), 3 deletions(-)
drivers/iio/adc/ad4170.c +69 -3
index 612676c1c88a..335b4194c7eb 100644
@@ -891,6 +891,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
@@ -1189,9 +1210,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];
@@ -1852,12 +1891,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
#21 lore raw

Re: [PATCH v2 6/7] iio: adc: ad4170: Add support for internal temperature sensor

Quoted 4 lines · 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.

...

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

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

Do not wrap lines like this. Use a logical approach, i.e. the '477) *' part should be on the previous line.

Quoted 4 lines
drivers/iio/adc/ad4170.c +4 -0
+                       return IIO_VAL_INT_PLUS_NANO;
+               default:
+                       return -EINVAL;
+               }
Signature · 2 lines
With Best Regards,
Andy Shevchenko
maintainer
#22 lore raw

Re: [PATCH v2 6/7] iio: adc: ad4170: Add support for internal temperature sensor

In reply to Marcelo Schmitt
Quoted 6 lines · On Mon, 28 Apr 2025 09:29:12 -0300 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.

One trivial comment below.
I don't mind that much given you already did it this way though.

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

Quoted 12 lines

/*
* 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

drivers/iio/adc/ad4170.c +2 -3
@@ -1189,9 +1210,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:

When you know you are going to have changes like this in later patches in the same posted series it is fine to make the diff smaller by using a switch from the start.

Occasionally you'll get a comment from someone like me who can't hold the whole set in my head and misses that is what is going on. Just reply to say it was diff minimization and likely no one will ever mind!

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

PATCH v2 7/7 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.

---
changes since v1:
- Improved to support more than one external sensor connected.

 drivers/iio/adc/ad4170.c | 375 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 372 insertions(+), 3 deletions(-)
drivers/iio/adc/ad4170.c +372 -3
index 335b4194c7eb..9e4a9b524dd4 100644
@@ -62,6 +62,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
@@ -110,6 +112,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 register constants */
 
 /* AD4170_CLOCK_CTRL_REG constants */
@@ -171,6 +177,21 @@
 #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_AIN0		0
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN1		1
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN2		2
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN3		3
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN4		4
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN5		5
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN6		6
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN7		7
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN8		8
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0		17
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1		18
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2		19
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3		20
+
 /* Device properties and auxiliary constants */
 
 #define AD4170_NUM_ANALOG_PINS				9
@@ -204,6 +225,15 @@
 #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,
@@ -244,6 +274,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,
@@ -305,6 +337,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_AIN0,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN1,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN2,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN3,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN4,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN5,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN6,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN7,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN8,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3,
+};
+
+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;
 };
@@ -378,6 +437,8 @@ struct ad4170_state {
 	struct clk *ext_clk;
 	struct clk_hw int_clk_hw;
 	int pins_fn[AD4170_NUM_ANALOG_PINS];
+	int gpio_fn[AD4170_NUM_GPIO_PINS];
+	unsigned int cur_src_pins[AD4170_NUM_CURRENT_SRC];
 	struct gpio_chip gpiochip;
 	u32 int_pin_sel;
 	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
@@ -925,6 +986,19 @@ static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n,
 	struct device *dev = &st->spi->dev;
 
 	*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) {
+		*ain_voltage = (st->vrefs_uv[AD4170_AVDD_SUP]
+				- st->vrefs_uv[AD4170_AVSS_SUP]) / 2;
+		return 0;
+	}
+
 	if (ain_n <= AD4170_CHAN_MAP_TEMP_SENSOR)
 		return 0;
 
@@ -1742,6 +1816,266 @@ 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_find_table_index(const unsigned int *tbl, size_t len,
+				    unsigned int val)
+{
+	unsigned int i;
+
+	for (i = 0; i < len; i++)
+		if (tbl[i] == val)
+			return i;
+
+	return -EINVAL;
+}
+
+#define ad4170_find_table_index(table, val) \
+	_ad4170_find_table_index(table, ARRAY_SIZE(table), val)
+
+static int ad4170_validate_excitation_pins(struct ad4170_state *st,
+					   u32 *exc_pins, int num_exc_pins)
+{
+	struct device *dev = &st->spi->dev;
+	int ret, i;
+
+	for (i = 0; i < num_exc_pins; i++) {
+		unsigned int pin = exc_pins[i];
+
+		ret = ad4170_find_table_index(ad4170_iout_pin_tbl, pin);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "Invalid excitation pin: %u\n",
+					     pin);
+
+		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_GPIO0;
+
+			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_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 current_src, i, j;
+	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);
+
+		for (j = 0; j < AD4170_NUM_CURRENT_SRC; j++) {
+			/*
+			 * Excitation current chopping is configured in pairs.
+			 * If current chopping configured and the first end of
+			 * the current source pair has already been assigned,
+			 * skip to the next pair of output currents.
+			 */
+			if (ac_excited && j % 2 != 0)
+				continue;
+
+			if (st->cur_src_pins[j] == AD4170_CURRENT_SRC_DISABLED) {
+				st->cur_src_pins[j] = pin;
+				break;
+			}
+		}
+		if (j == AD4170_NUM_CURRENT_SRC)
+			return dev_err_probe(&st->spi->dev, -EINVAL,
+					     "Failed to setup IOUT at pin %u\n",
+					     pin);
+
+		ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(j),
+				   current_src);
+		if (ret)
+			return ret;
+	}
+
+	if (ac_excited && num_exc_pins > 1) {
+		unsigned int exc_cur_pair;
+
+		if (st->cur_src_pins[0] == exc_pins[0])
+			exc_cur_pair = 1;
+		else
+			exc_cur_pair = 2;
+
+		/*
+		 * Configure excitation currents chopping.
+		 * Chop two pairs if using four excitation currents.
+		 */
+		setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK,
+					  num_exc_pins == 2 ? exc_cur_pair : 3);
+	}
+
+	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)
+{
+	int ret;
+
+	/*
+	 * If a specific current is provided through
+	 * adi,excitation-current-microamp, set excitation pins provided through
+	 * adi,excitation-pins to AC excite the bridge circuit. 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.
+	 *
+	 * 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 (exc_cur == 0 && ac_excited) {
+		if (num_exc_pins == 2) {
+			setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3);
+			ret = regmap_set_bits(st->regmap,
+					      AD4170_GPIO_MODE_REG,
+					      BIT(7) | BIT(5));
+			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);
+			ret = regmap_set_bits(st->regmap,
+					      AD4170_GPIO_MODE_REG,
+					      BIT(7) | BIT(5) | BIT(3) | BIT(1));
+			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;
+	}
+
+	return ad4170_setup_current_src(st, child, setup, exc_pins,
+					num_exc_pins, exc_cur, ac_excited);
+}
+
+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;
+	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");
+
+	ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl, exc_cur);
+	if (ret < 0)
+		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)
@@ -1823,6 +2157,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;
@@ -1854,10 +2189,34 @@ 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 < 0)
+			return ret;
+
+		break;
+	case AD4170_WEIGH_SCALE_SENSOR:
+		fallthrough;
+	case AD4170_THERMOCOUPLE_SENSOR:
+		fallthrough;
+	case AD4170_RTD_SENSOR:
+		ret = ad4170_parse_external_sensor(st, child, setup, chan,
+						   s_type);
+		if (ret < 0)
+			return ret;
 
+		break;
+	default:
+		return -EINVAL;
+	}
 	bipolar = fwnode_property_read_bool(child, "bipolar");
 	setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar);
 	if (bipolar)
@@ -2059,6 +2418,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",
@@ -2083,6 +2448,10 @@ static int ad4170_parse_firmware(struct iio_dev *indio_dev)
 
 	/* Only create a GPIO chip if flagged for it */
 	if (device_property_read_bool(&st->spi->dev, "gpio-controller")) {
+		for (i = 0; i < AD4170_NUM_GPIO_PINS; i++)
+			if (st->gpio_fn[i] != AD4170_GPIO_UNASIGNED)
+				return 0;
+
 		ret = ad4170_gpio_init(indio_dev);
 		if (ret)
 			return ret;
Signature · 1 line
2.47.2
#24 lore raw

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

Quoted 10 lines · 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.

...

drivers/iio/adc/ad4170.c static const unsigned int ad4170_sinc5_filt_fs_tbl[] = { · line 357

Quoted 2 lines
drivers/iio/adc/ad4170.c +2 -0
+static const unsigned int ad4170_iout_current_ua_tbl[] = {
+       0, 10, 50, 100, 250, 500, 1000, 1500

Leave trailing comma.

Quoted 1 line

+};

...

drivers/iio/adc/ad4170.c static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n, · line 998

Quoted 3 lines
drivers/iio/adc/ad4170.c +3 -0
+       if (st->pins_fn[ain_n] & AD4170_PIN_VBIAS) {
+               *ain_voltage = (st->vrefs_uv[AD4170_AVDD_SUP]
+                               - st->vrefs_uv[AD4170_AVSS_SUP]) / 2;

Don't wrap like this, keep the logical split, and looking at this I would just put it either on a single line, or use a temporary variable for the expression in parentheses.

Quoted 2 lines
(quoted diff) +2 -0
+               return 0;
+       }

...

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

Quoted 5 lines
drivers/iio/adc/ad4170.c +5 -0
+static int ad4170_validate_excitation_pins(struct ad4170_state *st,
+                                          u32 *exc_pins, int num_exc_pins)
+{
+       struct device *dev = &st->spi->dev;
+       int ret, i;

Should 'i' be signed?

Quoted 19 lines
drivers/iio/adc/ad4170.c +19 -0
+       for (i = 0; i < num_exc_pins; i++) {
+               unsigned int pin = exc_pins[i];
+
+               ret = ad4170_find_table_index(ad4170_iout_pin_tbl, pin);
+               if (ret < 0)
+                       return dev_err_probe(dev, ret,
+                                            "Invalid excitation pin: %u\n",
+                                            pin);
+
+               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_GPIO0;
+

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

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

...

Also consider inverting the conditional in ad4170_setup_bridge() to drop indentation level in a lot of LoCs.

...

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

Quoted 4 lines
drivers/iio/adc/ad4170.c +4 -0
+       switch (s_type) {
+       case AD4170_ADC_SENSOR:
+               ret = ad4170_parse_adc_channel_type(dev, child, chan);
+               if (ret < 0)

Why ' < 0'?

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

Quoted 11 lines
drivers/iio/adc/ad4170.c +11 -0
+                       return ret;
+
+               break;
+       case AD4170_WEIGH_SCALE_SENSOR:
+               fallthrough;
+       case AD4170_THERMOCOUPLE_SENSOR:
+               fallthrough;
+       case AD4170_RTD_SENSOR:
+               ret = ad4170_parse_external_sensor(st, child, setup, chan,
+                                                  s_type);
+               if (ret < 0)

Ditto.

Quoted 6 lines
drivers/iio/adc/ad4170.c +5 -0
+                       return ret;
 
+               break;
+       default:
+               return -EINVAL;
+       }
Signature · 2 lines
With Best Regards,
Andy Shevchenko
#25 lore raw

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

In reply to Marcelo Schmitt
kernel test robot noticed the following build warnings:

Hi Marcelo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 1c2409fe38d5c19015d69851d15ba543d1911932]

url:    https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/dt-bindings-iio-adc-Add-AD4170/20250428-222010
base:   1c2409fe38d5c19015d69851d15ba543d1911932
patch link:    https://lore.kernel.org/r/3687a9e0a479aef9736ad557b341ed2e7d4f5756.1745841276.git.marcelo.schmitt%40analog.com
patch subject: [PATCH v2 7/7] iio: adc: ad4170: Add support for weigh scale and RTD sensors
config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20250502/[email protected]/config)
compiler: powerpc64-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250502/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

drivers/iio/adc/ad4170.c: In function 'ad4170_setup_current_src.isra':

>> drivers/iio/adc/ad4170.c:1881:29: warning: 'current_src' is used uninitialized [-Wuninitialized]
    1881 |                 current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin);
         |                             ^~
   drivers/iio/adc/ad4170.c:1875:22: note: 'current_src' was declared here
    1875 |         unsigned int current_src, i, j;
         |                      ^~~~~~~~~~~

vim +/current_src +1881 drivers/iio/adc/ad4170.c

1869
1870 static int ad4170_setup_current_src(struct ad4170_state *st,

  1871					    struct fwnode_handle *child,
  1872					    struct ad4170_setup *setup, u32 *exc_pins,
  1873					    int num_exc_pins, int exc_cur, bool ac_excited)
  1874	{
  1875		unsigned int current_src, i, j;
  1876		int ret;
  1877	
  1878		for (i = 0; i < num_exc_pins; i++) {
  1879			unsigned int pin = exc_pins[i];
  1880	
> 1881			current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin);

1882 current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, exc_cur);
1883
1884 for (j = 0; j < AD4170_NUM_CURRENT_SRC; j++) {
1885 /*
1886 * Excitation current chopping is configured in pairs.
1887 * If current chopping configured and the first end of
1888 * the current source pair has already been assigned,
1889 * skip to the next pair of output currents.
1890 */
1891 if (ac_excited && j % 2 != 0)
1892 continue;
1893
1894 if (st->cur_src_pins[j] == AD4170_CURRENT_SRC_DISABLED) {
1895 st->cur_src_pins[j] = pin;
1896 break;
1897 }
1898 }
1899 if (j == AD4170_NUM_CURRENT_SRC)
1900 return dev_err_probe(&st->spi->dev, -EINVAL,
1901 "Failed to setup IOUT at pin %u\n",
1902 pin);
1903
1904 ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(j),
1905 current_src);
1906 if (ret)
1907 return ret;
1908 }
1909
1910 if (ac_excited && num_exc_pins > 1) {
1911 unsigned int exc_cur_pair;
1912
1913 if (st->cur_src_pins[0] == exc_pins[0])
1914 exc_cur_pair = 1;
1915 else
1916 exc_cur_pair = 2;
1917
1918 /*
1919 * Configure excitation currents chopping.
1920 * Chop two pairs if using four excitation currents.
1921 */
1922 setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK,
1923 num_exc_pins == 2 ? exc_cur_pair : 3);
1924 }
1925
1926 return 0;
1927 }
1928

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
maintainer
#26 lore raw

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

In reply to Marcelo Schmitt
Quoted 11 lines · On Mon, 28 Apr 2025 09:29:40 -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.

Hi Marcelo

A few comments inline.

Jonathan

Quoted 32 lines

---
changes since v1:
- Improved to support more than one external sensor connected.

 drivers/iio/adc/ad4170.c | 375 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 372 insertions(+), 3 deletions(-)
drivers/iio/adc/ad4170.c +6 -0
index 335b4194c7eb..9e4a9b524dd4 100644
@@ -62,6 +62,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
@@ -110,6 +112,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 register constants */
 
 /* AD4170_CLOCK_CTRL_REG constants */

drivers/iio/adc/ad4170.c line 180

Quoted 4 lines
drivers/iio/adc/ad4170.c +1 -0
@@ -171,6 +177,21 @@
 #define AD4170_FILTER_FILTER_TYPE_SINC5			0x4
 #define AD4170_FILTER_FILTER_TYPE_SINC3			0x6
 
+/* AD4170_CURRENT_SRC_REG constants */

These seem a bit repetitive. Can we use some macros?

#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN(x) (x) #define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(x) (x + 17)

Quoted 54 lines
drivers/iio/adc/ad4170.c +33 -0
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN0		0
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN1		1
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN2		2
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN3		3
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN4		4
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN5		5
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN6		6
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN7		7
+#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN8		8
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0		17
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1		18
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2		19
+#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3		20
+
 /* Device properties and auxiliary constants */
 
 #define AD4170_NUM_ANALOG_PINS				9
@@ -204,6 +225,15 @@
 #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,
@@ -244,6 +274,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,
@@ -305,6 +337,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_AIN0,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN1,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN2,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN3,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN4,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN5,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN6,

drivers/iio/adc/ad4170.c static const unsigned int ad4170_sinc5_filt_fs_tbl[] = { · line 357

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN7,
+	AD4170_CURRENT_SRC_I_OUT_PIN_AIN8,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2,
+	AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3,
+};
+
+static const unsigned int ad4170_iout_current_ua_tbl[] = {
+	0, 10, 50, 100, 250, 500, 1000, 1500

I think Andy got this, but trailing comma.

Quoted 43 lines
drivers/iio/adc/ad4170.c +24 -0
+};
+
+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;
 };
@@ -378,6 +437,8 @@ struct ad4170_state {
 	struct clk *ext_clk;
 	struct clk_hw int_clk_hw;
 	int pins_fn[AD4170_NUM_ANALOG_PINS];
+	int gpio_fn[AD4170_NUM_GPIO_PINS];
+	unsigned int cur_src_pins[AD4170_NUM_CURRENT_SRC];
 	struct gpio_chip gpiochip;
 	u32 int_pin_sel;
 	int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
@@ -925,6 +986,19 @@ static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n,
 	struct device *dev = &st->spi->dev;
 
 	*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) {
+		*ain_voltage = (st->vrefs_uv[AD4170_AVDD_SUP]
+				- st->vrefs_uv[AD4170_AVSS_SUP]) / 2;
+		return 0;
+	}
+
 	if (ain_n <= AD4170_CHAN_MAP_TEMP_SENSOR)
 		return 0;
 
@@ -1742,6 +1816,266 @@ static int ad4170_gpio_init(struct iio_dev *indio_dev)
 	return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev);

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +8 -0
 }
 
+static int _ad4170_find_table_index(const unsigned int *tbl, size_t len,
+				    unsigned int val)
+{
+	unsigned int i;
+
+	for (i = 0; i < len; i++)
+		if (tbl[i] == val)
+			return i;

I wonder if this is worth while vs the opencoded version

	for (i = 0; i < ARRAY_SIZE(ad4170_iout_pin_tbl); i++)
		if (ad4170_iout_pin_tbl[i] == val)
			break;

	if (i == ARRAY_SIZE(ad4170_iout_pin_tbl))
		return -EINVAL;

There is a bit more repetition and opportunity for missmatches between which table etc, but it is pretty compact and you only have two usecases of the function via the macro.

Quoted 65 lines
drivers/iio/adc/ad4170.c +64 -0
+
+	return -EINVAL;
+}
+
+#define ad4170_find_table_index(table, val) \
+	_ad4170_find_table_index(table, ARRAY_SIZE(table), val)
+
+static int ad4170_validate_excitation_pins(struct ad4170_state *st,
+					   u32 *exc_pins, int num_exc_pins)
+{
+	struct device *dev = &st->spi->dev;
+	int ret, i;
+
+	for (i = 0; i < num_exc_pins; i++) {
+		unsigned int pin = exc_pins[i];
+
+		ret = ad4170_find_table_index(ad4170_iout_pin_tbl, pin);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "Invalid excitation pin: %u\n",
+					     pin);
+
+		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_GPIO0;
+
+			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_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)
+{
+	int ret;
+
+	/*
+	 * If a specific current is provided through
+	 * adi,excitation-current-microamp, set excitation pins provided through
+	 * adi,excitation-pins to AC excite the bridge circuit. 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.
+	 *
+	 * 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

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+	 * 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 (exc_cur == 0 && ac_excited) {
+		if (num_exc_pins == 2) {
+			setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3);
+			ret = regmap_set_bits(st->regmap,
+					      AD4170_GPIO_MODE_REG,
+					      BIT(7) | BIT(5));

These are fields in a register with meaning that isn't obvious here. Should have defines to give them a 'meaning'.
I'll guess this is setting two separate 1 bit values in 2 bit fields? Those need defines that take the index of the gpio.

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

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));

This is a bit more obvious as those are the two pins we are using but maybe a comment.

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

Quoted 10 lines
drivers/iio/adc/ad4170.c +10 -0
+			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);
+			ret = regmap_set_bits(st->regmap,
+					      AD4170_GPIO_MODE_REG,
+					      BIT(7) | BIT(5) | BIT(3) | BIT(1));

As above. Looks like a bunch of 2 bit fields with value 1?

Quoted 21 lines
drivers/iio/adc/ad4170.c +21 -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;
+	}
+
+	return ad4170_setup_current_src(st, child, setup, exc_pins,
+					num_exc_pins, exc_cur, ac_excited);
+}