From 09c246c720f0a0cefdaac7e25c395262ec676ccc Mon Sep 17 00:00:00 2001 From: Ana-Maria Cusco Date: Thu, 18 Jul 2024 17:41:29 -0300 Subject: [PATCH 01/14] include: dt-bindings: iio: adc: Add defines for AD4170 Add defines to improve readability of AD4170 device tree nodes. Signed-off-by: Ana-Maria Cusco Signed-off-by: Marcelo Schmitt --- include/dt-bindings/iio/adc/adi,ad4170.h | 96 ++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 include/dt-bindings/iio/adc/adi,ad4170.h diff --git a/include/dt-bindings/iio/adc/adi,ad4170.h b/include/dt-bindings/iio/adc/adi,ad4170.h new file mode 100644 index 00000000000000..5c9e00e08fad0f --- /dev/null +++ b/include/dt-bindings/iio/adc/adi,ad4170.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * AD4170 ADC + * + * Copyright 2024 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef _DT_BINDINGS_IIO_ADC_AD4170_H_ +#define _DT_BINDINGS_IIO_ADC_AD4170_H_ + +/* + * Excitation Current Chopping Control. + * Use for adi,chop-iexc + */ +#define AD4170_MISC_CHOP_IEXC_OFF 0 +/* Chopping of Iout_A and Iout_B Excitation Currents. */ +#define AD4170_MISC_CHOP_IEXC_AB 1 +/* Chopping of Iout_C and Iout_D Excitation Currents. */ +#define AD4170_MISC_CHOP_IEXC_CD 2 +/* Chopping of Both Pairs of Excitation Currents. */ +#define AD4170_MISC_CHOP_IEXC_ABCD 3 + +/* + * Chopping + * Use for adi,chop-adc + */ +/* No Chopping. */ +#define AD4170_MISC_CHOP_ADC_OFF 0 +/* Chops Internal Mux. */ +#define AD4170_MISC_CHOP_ADC_MUX 1 +/* Chops AC Excitation Using 4 GPIO Pins. */ +#define AD4170_MISC_CHOP_ADC_ACX_4PIN 2 +/* Chops AC Excitation Using 2 GPIO Pins. */ +#define AD4170_MISC_CHOP_ADC_ACX_2PIN 3 + +/* + * Reference Selection Mode + * Use for adi,reference-select + */ +#define AD4170_AFE_REFIN_REFIN1 0 +#define AD4170_AFE_REFIN_REFIN2 1 +#define AD4170_AFE_REFIN_REFOUT 2 +#define AD4170_AFE_REFIN_AVDD 3 + +/* + * Definitios for describing channel selections + * Use for adi,channel-map + */ +#define AD4170_MAP_AIN0 0 +#define AD4170_MAP_AIN1 1 +#define AD4170_MAP_AIN2 2 +#define AD4170_MAP_AIN3 3 +#define AD4170_MAP_AIN4 4 +#define AD4170_MAP_AIN5 5 +#define AD4170_MAP_AIN6 6 +#define AD4170_MAP_AIN7 7 +#define AD4170_MAP_AIN8 8 +#define AD4170_MAP_TEMP_SENSOR_P 17 +#define AD4170_MAP_TEMP_SENSOR_N 17 +#define AD4170_MAP_AVDD_AVSS_P 18 +#define AD4170_MAP_AVDD_AVSS_N 18 +#define AD4170_MAP_IOVDD_DGND_P 19 +#define AD4170_MAP_IOVDD_DGND_N 19 +#define AD4170_MAP_DAC_P 20 +#define AD4170_MAP_DAC_N 20 +#define AD4170_MAP_ALDO 21 +#define AD4170_MAP_DLDO 22 +#define AD4170_MAP_AVSS 23 +#define AD4170_MAP_DGND 24 +#define AD4170_MAP_REFIN1_P 25 +#define AD4170_MAP_REFIN1_N 26 +#define AD4170_MAP_REFIN2_P 27 +#define AD4170_MAP_REFIN2_N 28 +#define AD4170_MAP_REFOUT 29 + +/* + * Definitios for describing excitation current output pin selections + * Use for adi,excitation-pin-0/1/2/3 + */ +#define AD4170_CURRENT_IOUT_AIN0 0 +#define AD4170_CURRENT_IOUT_AIN1 1 +#define AD4170_CURRENT_IOUT_AIN2 2 +#define AD4170_CURRENT_IOUT_AIN3 3 +#define AD4170_CURRENT_IOUT_AIN4 4 +#define AD4170_CURRENT_IOUT_AIN5 5 +#define AD4170_CURRENT_IOUT_AIN6 6 +#define AD4170_CURRENT_IOUT_AIN7 7 +#define AD4170_CURRENT_IOUT_AIN8 8 +#define AD4170_CURRENT_IOUT_GPIO0 17 +#define AD4170_CURRENT_IOUT_GPIO1 18 +#define AD4170_CURRENT_IOUT_GPIO2 19 +#define AD4170_CURRENT_IOUT_GPIO3 20 + +#endif /* _DT_BINDINGS_IIO_ADC_AD4170_H_ */ From 754fc5b116232419b56a77149487d559778651f5 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Fri, 19 Jul 2024 14:21:02 -0300 Subject: [PATCH 02/14] dt-bindings: iio: adc: Add AD4170 Add device tree documentation for AD4170 sigma-delta ADCs. Signed-off-by: Marcelo Schmitt --- .../bindings/iio/adc/adi,ad4170.yaml | 458 ++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file mode 100644 index 00000000000000..8708cc46fa7577 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml @@ -0,0 +1,458 @@ +# 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 Analog to Digital Converter + +maintainers: + - Marcelo Schmitt + +description: | + Analog Devices AD4170 Analog to Digital Converter. + Specifications can be found at: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4170-4.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad4170 + + avss-supply: + description: + Referece voltage supply for AVDD. AVSS can be set below 0V to provide a + bipolar power supply to AD4170-4. Must be −2.625V at minimum, 0V maximum. + If not specified, this is assumed to be analog ground. + + 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. + + 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. + + interrupts: + maxItems: 1 + + adi,gpio0-power-down-switch: + type: boolean + description: + Describes whether GPIO0 is used as a switch to disconnect bridge circuits + from AVSS. Pin defaults to GPIO if this property is not present. + + adi,gpio1-power-down-switch: + type: boolean + description: + Describes whether GPIO1 is used as a switch to disconnect bridge circuits + from AVSS. Pin defaults to GPIO if this property is not present. + + adi,vbias-pins: + description: Analog inputs to apply a voltage bias of (AVDD − AVSS) / 2 to. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 9 + items: + minimum: 0 + maximum: 8 + + adi,dig-aux1: + description: + Describes whether DIG_AUX1 pin will operate as data ready output, + synchronization output signal (SYNC_OUT), or if it will be disabled. + A value of 0 indicates DIG_AUX1 pin disabled. High impedance. + A value of 1 indicates DIG_AUX1 is configured as ADC data ready output. + A value of 1 indicates DIG_AUX1 is configured as SYNC_OUT output. + If this property is absent, DIG_AUX1 pin is disabled. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2] + default: 0 + + adi,dig-aux2: + description: + Describes whether DIG_AUX2 pin will function as DAC LDAC input, + synchronization start input (START), or if it will be disabled. + A value of 0 indicates DIG_AUX2 pin is disabled. High impedance. + A value of 1 indicates DIG_AUX2 pin is configured as active-low LDAC input + for the DAC. + A value of 2 indicates DIG_AUX2 pin is configured as START input. + If this property is absent, DIG_AUX2 pin is disabled. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2] + default: 0 + + adi,sync-option: + description: + Describes how ADC conversions are going to be synchronized. A value of 1 + indicates the SYNC_IN pin will function as a synchronization input that + allows the user to control the start of sampling by pulling SYNC_IN high. + Use option number 2 to set the alternate synchronization functionality + which allows per channel conversion start control when multiple channels + are enabled. Option number 0 disables synchronization. + A value of 0 indicates no synchronization. SYNC_IN pin disabled. + A value of 1 indicates standard synchronization functionality. + A value of 2 indicates alternate synchronization functionality. + If this property is absent, no synchronization is performed. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2] + default: 1 + + adi,excitation-pin-0: + description: | + Specifies the pin on which IOUT0 will be made available. + Besides the analog pins 0 to 8, the excitation current can be output to + GPIO pins. + 17: Output excitation current IOUT0 to GPIO0. + 18: Output excitation current IOUT0 to GPIO1. + 19: Output excitation current IOUT0 to GPIO2. + 20: Output excitation current IOUT0 to GPIO3. + If this property is absent, IOUT0 is not routed to any pin. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20] + default: 0 + + adi,excitation-pin-1: + description: | + Specifies the pin on which IOUT1 will be made available. + Besides the analog pins 0 to 8, the excitation current can be output to + GPIO pins. + 17: Output excitation current IOUT1 to GPIO0. + 18: Output excitation current IOUT1 to GPIO1. + 19: Output excitation current IOUT1 to GPIO2. + 20: Output excitation current IOUT1 to GPIO3. + If this property is absent, IOUT1 is not routed to any pin. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20] + default: 0 + + adi,excitation-pin-2: + description: | + Specifies the pin on which IOUT2 will be made available. + Besides the analog pins 0 to 8, the excitation current can be output to + GPIO pins. + 17: Output excitation current IOUT2 to GPIO0. + 18: Output excitation current IOUT2 to GPIO1. + 19: Output excitation current IOUT2 to GPIO2. + 20: Output excitation current IOUT2 to GPIO3. + If this property is absent, IOUT2 is not routed to any pin. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20] + default: 0 + + adi,excitation-pin-3: + description: | + Specifies the pin on which IOUT3 will be made available. + Besides the analog pins 0 to 8, the excitation current can be output to + GPIO pins. + 17: Output excitation current IOUT3 to GPIO0. + 18: Output excitation current IOUT3 to GPIO1. + 19: Output excitation current IOUT3 to GPIO2. + 20: Output excitation current IOUT3 to GPIO3. + If this property is absent, IOUT3 is not routed to any pin. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20] + default: 0 + + adi,excitation-current-0-microamp: + description: | + Excitation current in microamps to be applied to IOUT0 output pin + specified in adi,excitation-pin-0. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + default: 0 + + adi,excitation-current-1-microamp: + description: | + Excitation current in microamps to be applied to IOUT1 output pin + specified in adi,excitation-pin-1. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + default: 0 + + adi,excitation-current-2-microamp: + description: | + Excitation current in microamps to be applied to IOUT2 output pin + specified in adi,excitation-pin-2. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + default: 0 + + adi,excitation-current-3-microamp: + description: | + Excitation current in microamps to be applied to IOUT3 output pin + specified in adi,excitation-pin-3. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + default: 0 + + adi,chop-iexc: + description: | + Specifies the chopping/swapping functionality for excitation currents. + 0: No Chopping of Excitation Currents. + 1: Chop/swap IOUT0 and IOUT1 (pair AB) excitation currents. + 2: Chop/swap IOUT2 and IOUT3 (pair CD) excitation currents. + 3: Chop/swap both pairs (pair AB and pair CD) of excitation currents. + If this property is absent, no chopping is performed. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2, 3] + default: 0 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + "^channel@([0-9]|1[0-5])$": + $ref: adc.yaml + type: object + unevaluatedProperties: false + description: | + Represents the external channels which are connected to the ADC. + + properties: + reg: + description: | + The channel number. The device can have up to 16 channels numbered + from 0 to 15. + items: + 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: Temeprature sensor input + 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 + + There are macros for those values in dt-bindings/iio/adi,ad4170.h. + + items: + minimum: 0 + maximum: 31 + + single-channel: true + + common-mode-channel: true + + bipolar: true + + adi,config-setup-slot: + description: | + Specifies which of the eight setups are used to configure the channel. + A setup comprises of: AFE, FILTER, FILTER_FS, MISC, offset register, + and gain register. More than one channel can use the same + configuration setup slot in which case they will share the settings + for the above mentioned registers. + items: + minimum: 0 + maximum: 7 + + adi,chop-adc: + description: | + Specifies the chopping/swapping functionality for a channel setup. + Macros for adi,chop-adc values are available in + dt-bindings/iio/adi,ad4170.h. When enabled, the analog inputs are + continuously swapped and a conversion is generated for each phase. + The analog input pins are connected in one direction, sampled, + swapped, sampled again, and then the conversion results are averaged. + The input swapp minimizes system offset and offset drift. + This property also specifies wheter AC excitation using 2 or 4 GPIOs + are going to be used. + 0: No channel chop. + 1: Chop/swap the channel inputs. + 2: AC Excitation using 4 GPIOs. + 3: AC Excitation using 2 GPIOs. + If this property is absent, no chopping is performed. + $ref: /schemas/types.yaml#/definitions/uint16 + enum: [0, 1, 2, 3] + default: 0 + + adi,burnout-current-nanoamp: + description: | + Current in nanoamps to be applied for this channel. Burnout currents + are only active when the channel is selected for conversion. + enum: [0, 100, 2000, 10000] + default: 0 + + adi,buffered-negative: + description: Enable buffered mode for negative input. + type: boolean + + adi,buffered-positive: + description: Enable buffered mode for positive input. + type: boolean + + 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 relative AVSS. + If this field is left empty, the internal reference is selected. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 2 + + required: + - reg + - adi,config-setup-slot + + allOf: + - oneOf: + - required: [single-channel] + properties: + diff-channels: false + - required: [diff-channels] + properties: + single-channel: false + common-mode-channel: false + +required: + - compatible + - reg + - avdd-supply + - iovdd-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad4170"; + reg = <0>; + avdd-supply = <&avdd>; + iovdd-supply = <&iovdd>; + #address-cells = <1>; + #size-cells = <0>; + dmas = <&axi_dmac_0 0>; + dma-names = "rx"; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio_in>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + adi,dig-aux1 = /bits/ 8 <1>; + adi,dig-aux2 = /bits/ 8 <0>; + adi,sync-option = /bits/ 8 <0>; + adi,excitation-pin-0 = <19>; + adi,excitation-current-0-microamp = <10>; + adi,excitation-pin-1 = <20>; + adi,excitation-current-1-microamp = <10>; + adi,chop-iexc = /bits/ 8 <1>; + adi,vbias-pins = <5 6>; + + // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input range + // Fully differential. If AVSS < 0V, Fully differential true bipolar + channel@0 { + reg = <0>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <0>; + adi,reference-select = <3>; + adi,burnout-current-nanoamp = <100>; + }; + // Sample AIN2 with respect to DGND throughout AVDD/DGND input range + // Peseudo-differential unipolar (fig. 2a) + channel@1 { + reg = <1>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <1>; + adi,reference-select = <3>; + }; + // Sample AIN3 with respect to 2.5V throughout AVDD/AVSS input range + // Pseudo-differential bipolar (fig. 2b) + channel@2 { + reg = <2>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <2>; + adi,reference-select = <3>; + }; + // Sample AIN4 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential ture bipolar if AVSS < 0V (fig. 2c) + channel@3 { + reg = <3>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <3>; + adi,reference-select = <3>; + }; + // Sample AIN5 with respect to 2.5V throughout AVDD/REFOUT input range + // Pseudo-differential unipolar (AD4170 datasheet page 46 example) + channel@4 { + reg = <4>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <2>; + }; + // Sample AIN6 with respect to REFIN+ throughout AVDD/AVSS input range + // Pseudo-differential unipolar + channel@5 { + reg = <5>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <2>; + }; + // Sample AIN7 with respect to DGND throughout REFIN+/REFIN- input range + // Pseudo-differential bipolar + channel@6 { + reg = <6>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <5>; + adi,reference-select = <0>; + }; + // Temperature sensor + channel@7 { + reg = <7>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <6>; + adi,reference-select = <0>; + }; + }; + }; +... + From 8608d2af14c5328e2b7c7ecd0831dd2d45a84f92 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Tue, 19 Nov 2024 14:01:07 -0300 Subject: [PATCH 03/14] arm: dts: Add Cyclone5 DE10 nano device tree with altr,pio-21.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes introduced by this commit were originally made to arm/boot/dts/socfpga_cyclone5_de10_nano.dtsi by Dragos Bogdan. Though, de10_nano device tree includes were latter reorganized by Uwe Kleine-König (91f167052638), making socfpga_cyclone5_de10_nano.dtsi the generic dtsi for de10_nano and socfpga_cyclone5_de10_nano_hps.dtsi the include file for a specific FPGA configuration. Instead of changing the generic dtsi for de10_nano, create socfpga_cyclone5_de10_nano_hps_pio21.dtsi to have the configuration for a specific GPIO driver version. Signed-off-by: Dragos Bogdan Signed-off-by: Marcelo Schmitt --- .../dts/socfpga_cyclone5_de10_nano_ad4170.dts | 95 +++++++++ .../socfpga_cyclone5_de10_nano_hps_pio21.dtsi | 192 ++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts create mode 100644 arch/arm/boot/dts/socfpga_cyclone5_de10_nano_hps_pio21.dtsi diff --git a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts new file mode 100644 index 00000000000000..4f3401e692007c --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices CN0540 + * https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/cn0540.html + * https://www.analog.com/en/products/ad4170.html + * https://wiki.analog.com/resources/tools-software/linux-build/generic/socfpga + * + * hdl_project: + * board_revision: + * + * Copyright (C) 2024 Analog Devices Inc. + */ +/dts-v1/; +#include "socfpga_cyclone5_de10_nano_hps_pio21.dtsi" +#include +#include + +/ { + avdd: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; +}; + +&fpga_axi { + axi_dmac_0: rx-dma@0x00020000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x00020000 0x00000800>; + interrupt-parent = <&intc>; + interrupts = <0 44 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + clocks = <&h2f_user0_clk>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = <1>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + spi@0x00030000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x00030000 0x00010000>; + interrupt-parent = <&intc>; + interrupts = <0 45 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&sys_clk &h2f_user0_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + num-cs = <1>; + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4170@0 { + compatible = "adi,ad4170"; + reg = <0>; + spi-cpol; + spi-cpha; + avdd-supply = <&avdd>; + #address-cells = <1>; + #size-cells = <0>; + + dmas = <&axi_dmac_0 0>; + dma-names = "rx"; + + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio_in>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "adc_rdy"; + adi,dig-aux1-function = "rdy"; + + channel@0 { + reg = <0>; + adi,reference-select = <2>; + /* AIN8, DGND */ + diff-channels = <8 24>; + }; + + channel@1 { + reg = <1>; + adi,reference-select = <2>; + /* TEMP_SENSOR+, TEMP_SENSOR- */ + diff-channels = <17 17>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_hps_pio21.dtsi b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_hps_pio21.dtsi new file mode 100644 index 00000000000000..434a1eec6262d0 --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_hps_pio21.dtsi @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017, Intel Corporation + * + * based on socfpga_cyclone5_de0_nano_soc.dts + * + * This include contains the representation of devices instantiated in the FPGA + * by https://github.com/analogdevicesinc/hdl/blob/main/projects/common/de10nano/de10nano_system_qsys.tcl + * which is used by most ADI projects for the de10nano. + */ + +#include "socfpga_cyclone5_de10_nano.dtsi" + +/ { + sys_clk: sys_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <50000000>; + clock-output-names = "sys_clk"; + }; + + ref_clk: ref_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <50000000>; + clock-output-names = "reference_clock"; + }; + + ltc2308_vref: voltage-regulator { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <4096000>; + regulator-max-microvolt = <4096000>; + regulator-always-on; + }; +}; + +&fpga_axi { + gpio_in: gpio_in@10100 { + compatible = "altr,pio-21.1", "altr,pio-1.0"; + reg = <0x00010100 0x00000010>; + interrupts = <0 42 1>; + altr,gpio-bank-width = <32>; /* embeddedsw.dts.params.altr,gpio-bank-width type NUMBER */ + altr,interrupt-type = <2>; /* embeddedsw.dts.params.altr,interrupt-type type NUMBER */ + altr,interrupt_type = <2>; /* embeddedsw.dts.params.altr,interrupt_type type NUMBER */ + edge_type = <1>; /* embeddedsw.dts.params.edge_type type NUMBER */ + level_trigger = <0>; /* embeddedsw.dts.params.level_trigger type NUMBER */ + resetvalue = <0>; /* embeddedsw.dts.params.resetvalue type NUMBER */ + #gpio-cells = <2>; + gpio-controller; + + #interrupt-cells = <2>; + interrupt-controller; + }; + + axi_sysid_0: axi-sysid-0@18000 { + compatible = "adi,axi-sysid-1.00.a"; + reg = <0x00018000 0x8000>; + }; + + hdmi_tx_dma: dma@80000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x00080000 0x00000800>; + interrupt-parent = <&intc>; + interrupts = <0 47 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + clocks = <&sys_clk>; + }; + + axi_hdmi@90000 { + compatible = "adi,axi-hdmi-tx-1.00.a"; + reg = <0x00090000 0x10000>; + dmas = <&hdmi_tx_dma 0>; + dma-names = "video"; + clocks = <&pixel_clock 0>; + adi,is-rgb; + + port { + axi_hdmi_out: endpoint { + remote-endpoint = <&adv7513_in>; + }; + }; + }; + + pixel_clock: fpll@100000 { + #clock-cells = <0x1>; + compatible = "altr,c5-fpll"; + reg = <0x00100000 0x00000100>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&ref_clk>; + assigned-clocks = <&pixel_clock 0>, <&pixel_clock 1>; + assigned-clock-rates = <148500000>, <100000000>; + clock-output-names = "c5_out0", "c5_out1", "c5_out2", + "c5_out3", "c5_out4", "c5_out5", + "c5_out6", "c5_out7", "c5_out8"; + adi,fractional-carry-bit = <32>; + + fpll_c0: channel@0 { + reg = <0>; + adi,extended-name = "PIXEL_CLOCK"; + }; + + fpll_c1: channel@1 { + reg = <1>; + adi,extended-name = "DMA_CLOCK"; + }; + }; + + gpio_out: gpio_out@109000 { + compatible = "altr,pio-1.0"; + reg = <0x00109000 0x00000010>; + altr,gpio-bank-width = <32>; + resetvalue = <0>; + #gpio-cells = <2>; + gpio-controller; + }; + + spi@10a000 { + compatible = "altr,spi-18.1", "altr,spi-1.0"; + reg = <0x0010a000 0x00000020>; + interrupt-parent = <&intc>; + interrupts = <0 43 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <0x1>; + #size-cells = <0x0>; + + ltc2308: adc@0 { + compatible = "adi,ltc2308"; + reg = <0>; + spi-max-frequency = <40000000>; + vref-supply = <<c2308_vref>; + cnv-gpios = <&gpio_out 9 GPIO_ACTIVE_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + channel@0 { + reg = <0>; + }; + channel@1 { + reg = <1>; + }; + channel@2 { + reg = <2>; + }; + channel@3 { + reg = <3>; + }; + channel@4 { + reg = <4>; + }; + channel@5 { + reg = <5>; + }; + channel@6 { + reg = <6>; + }; + channel@7 { + reg = <7>; + }; + }; + }; +}; + +&i2c0 { + adv7513@39 { + compatible = "adi,adv7513"; + reg = <0x39>, <0x3f>; + reg-names = "primary", "edid"; + + adi,input-depth = <8>; + adi,input-colorspace = "rgb"; + adi,input-clock = "1x"; + adi,clock-delay = <0>; + + #sound-dai-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + adv7513_in: endpoint { + remote-endpoint = <&axi_hdmi_out>; + }; + }; + + port@1 { + reg = <1>; + }; + }; + }; +}; From 82ee91bb7f82ce73c937fa5db61a1bc8d817c2cc Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Wed, 11 Sep 2024 09:57:00 -0300 Subject: [PATCH 04/14] arm: dts: Add device tree for AD4170 on CoraZ7 Signed-off-by: Marcelo Schmitt --- arch/arm/boot/dts/zynq-coraz7s-ad4170.dts | 247 ++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 arch/arm/boot/dts/zynq-coraz7s-ad4170.dts diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad4170.dts b/arch/arm/boot/dts/zynq-coraz7s-ad4170.dts new file mode 100644 index 00000000000000..7a5b9708b57bc1 --- /dev/null +++ b/arch/arm/boot/dts/zynq-coraz7s-ad4170.dts @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4170 + * + * hdl_project: + * Link: https://github.com/analogdevicesinc/hdl/tree/main/projects/ad4170_asdz + * board_revision: + * + * Copyright (C) 2024 Analog Devices Inc. + */ +/dts-v1/; +#include "zynq-coraz7s.dtsi" +#include +#include +#include + +/ { + vref: regulator-vref { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <4096000>; + regulator-max-microvolt = <4096000>; + regulator-always-on; + }; + avdd: avdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval AVDD supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; + iovdd: iovdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval IOVDD supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + }; + refin1p: refin1p-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN+ voltage reference"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; + refin1n: refin1n-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN- voltage reference"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; +}; + +&fpga_axi { + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 16>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = <1>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + spi_engine: spi@0x44a00000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x10000>; + interrupt-parent = <&intc>; + interrupts = <0 55 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15 &spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + num-cs = <1>; + + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4170@0 { + compatible = "adi,ad4170"; + reg = <0>; + spi-max-frequency = <20000000>; + spi-cpol; + spi-cpha; + avdd-supply = <&avdd>; + iovdd-supply = <&iovdd>; + refin1p-supply = <&refin1p>; + refin1n-supply = <&refin1n>; + interrupt-parent = <&gpio0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "adc_rdy"; + dmas = <&rx_dma 0>; + dma-names = "rx"; + adi,dig-aux1 = /bits/ 8 <1>; + adi,dig-aux2 = /bits/ 8 <0>; + adi,sync-option = /bits/ 8 <0>; + + #address-cells = <1>; + #size-cells = <0>; + + // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input range + // Fully differential. If AVSS < 0V, Fully differential true bipolar + channel@0 { + // Feature under test: General functionality + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN0 = square, 1 Hz, 100mV p-p centered at 1V. + // AIN1 = sine, 10 Hz, 100mV p-p centered at 1V, 180º phase shifted. + reg = <0>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <0>; + adi,reference-select = <3>; + adi,burnout-current-nanoamp = <100>; + }; + // Sample AIN2 with respect to DGND throughout AVDD/DGND input range + // Peseudo-differential unipolar (fig. 2a) + channel@1 { + // Feature under test: Pseudo-diff scale + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN2 = sine, 1 kHz, 100mV p-p centered at 2V. + reg = <1>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <1>; + adi,reference-select = <3>; + }; + // Sample AIN3 with respect to REFOUT throughout AVDD/AVSS input range + // Pseudo-differential bipolar (fig. 2b) + channel@2 { + // Feature under test: Channel offset + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN7 = sine 100 mV p-p centered at 2.5V + reg = <2>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <2>; + adi,reference-select = <3>; + }; + // Sample AIN4 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential true bipolar if AVSS < 0V (fig. 2c) + channel@3 { + reg = <3>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <3>; + adi,reference-select = <3>; + }; + // Sample AIN5 with respect to REFOUT throughout AVDD/REFOUT input range + // Pseudo-differential unipolar (AD4170 datasheet page 46 example) + channel@4 { + // Feature under test: Channel offset + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN5 = sine 100 mV p-p centered at 3.6V + reg = <4>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <3>; + }; + // Sample AIN6 with respect to AVSS throughout AVDD/AVSS input range + // Pseudo-differential unipolar + channel@5 { + // Feature under test: Channel scale + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN5 = sine 100 mV p-p centered at 3.6V + reg = <5>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <3>; + }; + // Sample AIN7 with respect to (AVDD-AVSS)/5 throughout REFIN+/REFIN- input range + // Pseudo-differential bipolar + channel@6 { + reg = <6>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <5>; + adi,reference-select = <0>; + }; + // Temperature sensor + channel@7 { + reg = <7>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <6>; + adi,reference-select = <0>; + }; + // Sample AIN8 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential channel + channel@8 { + reg = <8>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <7>; + adi,reference-select = <3>; + }; + + }; + }; + + axi_i2c_0:axi-iic@0x44a40000{ + compatible = "xlnx,axi-iic-1.02.a", "xlnx,xps-iic-2.00.a"; + reg = <0x44a40000 0x1000>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + + #address-cells = <1>; + #size-cells = <0>; + + ltc2606: ltc2606@10 { + compatible = "adi,ltc2606"; + reg = <0x10>; + vref-supply = <&vref>; + }; + }; + + spi_clk: axi-clkgen@0x44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x10000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + }; +}; From 166926a7adc9d2094ca82ffb06002a83301872b5 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Sat, 21 Sep 2024 12:07:08 -0300 Subject: [PATCH 05/14] arm: dts: Add device tree for AD4170 with AVSS on CoraZ7 Signed-off-by: Marcelo Schmitt --- .../arm/boot/dts/zynq-coraz7s-ad4170-avss.dts | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 arch/arm/boot/dts/zynq-coraz7s-ad4170-avss.dts diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad4170-avss.dts b/arch/arm/boot/dts/zynq-coraz7s-ad4170-avss.dts new file mode 100644 index 00000000000000..629f1965e0603f --- /dev/null +++ b/arch/arm/boot/dts/zynq-coraz7s-ad4170-avss.dts @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4170 + * + * hdl_project: + * Link: https://github.com/analogdevicesinc/hdl/tree/main/projects/ad4170_asdz + * board_revision: + * + * Copyright (C) 2024 Analog Devices Inc. + */ +/dts-v1/; +#include "zynq-coraz7s.dtsi" +#include +#include +#include + +/ { + vref: regulator-vref { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <4096000>; + regulator-max-microvolt = <4096000>; + regulator-always-on; + }; + avss: avss-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval AVSS supply"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; + avdd: avdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval AVDD supply"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; + iovdd: iovdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval IOVDD supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + }; + refin1p: refin1p-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN+ voltage reference"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; + refin1n: refin1n-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN- voltage reference"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; +}; + +&fpga_axi { + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 16>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = <1>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + spi_engine: spi@0x44a00000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x10000>; + interrupt-parent = <&intc>; + interrupts = <0 55 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15 &spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + num-cs = <1>; + + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4170@0 { + compatible = "adi,ad4170"; + reg = <0>; + spi-max-frequency = <20000000>; + spi-cpol; + spi-cpha; + avss-supply = <&avss>; + avdd-supply = <&avdd>; + iovdd-supply = <&iovdd>; + refin1p-supply = <&refin1p>; + refin1n-supply = <&refin1n>; + interrupt-parent = <&gpio0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "adc_rdy"; + dmas = <&rx_dma 0>; + dma-names = "rx"; + adi,dig-aux1 = /bits/ 8 <1>; + adi,dig-aux2 = /bits/ 8 <0>; + adi,sync-option = /bits/ 8 <0>; + + #address-cells = <1>; + #size-cells = <0>; + + // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input range + // Fully differential true bipolar. + channel@0 { + reg = <0>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <0>; + adi,reference-select = <3>; + adi,burnout-current-nanoamp = <100>; + }; + // Sample AIN2 with respect to DGND throughout AVDD/DGND input range + // Peseudo-differential unipolar (fig. 2a) + channel@1 { + reg = <1>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <1>; + adi,reference-select = <3>; + }; + // Sample AIN3 with respect to REFOUT throughout AVDD/AVSS input range + // Pseudo-differential true bipolar (AVSS at -2.5V) + channel@2 { + reg = <2>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <2>; + adi,reference-select = <3>; + }; + // Sample AIN4 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential ture bipolar (fig. 2c) (AVSS at -2.5V) + channel@3 { + reg = <3>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <2>; + adi,reference-select = <3>; + }; + // Sample AIN7 with respect to DGND throughout REFIN+/REFIN- input range + // Pseudo-differential bipolar + channel@4 { + reg = <4>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <5>; + adi,reference-select = <0>; + }; + // Temperature sensor + channel@5 { + reg = <5>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <6>; + adi,reference-select = <0>; + }; + // Sample AIN8 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential true bipolar channel (AVSS at -2.5V) + channel@6 { + reg = <6>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <7>; + adi,reference-select = <3>; + }; + + }; + }; + + axi_i2c_0:axi-iic@0x44a40000{ + compatible = "xlnx,axi-iic-1.02.a", "xlnx,xps-iic-2.00.a"; + reg = <0x44a40000 0x1000>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + + #address-cells = <1>; + #size-cells = <0>; + + ltc2606: ltc2606@10 { + compatible = "adi,ltc2606"; + reg = <0x10>; + vref-supply = <&vref>; + }; + }; + + spi_clk: axi-clkgen@0x44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x10000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + }; +}; From 42746cc769df090d17cc92b94355e29338b4ef3a Mon Sep 17 00:00:00 2001 From: Ana-Maria Cusco Date: Mon, 8 Jan 2024 21:04:26 +0200 Subject: [PATCH 06/14] iio: adc: Add support for AD4170 Add support for the AD4170 ADC with the following features: - Single-shot read (read_raw), scale, sampling freq - Multi channel buffer support - Buffered capture in triggered mode - Buffered capture with SPI-Engine - Gain and offset calibration - Support for gpio controller - chop_iexc and chop_adc device configuration - Powerdown switch configuration Signed-off-by: Ana-Maria Cusco Signed-off-by: Dragos Bogdan Signed-off-by: Marcelo Schmitt --- drivers/iio/Kconfig.adi | 1 + drivers/iio/adc/Kconfig | 16 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4170.c | 2227 ++++++++++++++++++++++++++++++++++++++ drivers/iio/adc/ad4170.h | 1025 ++++++++++++++++++ 5 files changed, 3270 insertions(+) create mode 100644 drivers/iio/adc/ad4170.c create mode 100644 drivers/iio/adc/ad4170.h diff --git a/drivers/iio/Kconfig.adi b/drivers/iio/Kconfig.adi index 26036dbfb7cbb3..d7840ff6a49934 100644 --- a/drivers/iio/Kconfig.adi +++ b/drivers/iio/Kconfig.adi @@ -41,6 +41,7 @@ config IIO_ALL_ADI_DRIVERS imply AD4630 imply AD4695 imply AD4130 + imply AD4170 imply AD4134 imply AD6676 imply AD7091R5 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1f7495505c3887..e8aa6bdd5e65f8 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -71,6 +71,22 @@ 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 + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_SPI + depends on COMMON_CLK + 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 AD4630 tristate "Analog Device AD4630 ADC Driver" depends on SPI_MASTER diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 101415a2e31d04..816b0426c766fb 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_AD_PULSAR) += ad_pulsar.o obj-$(CONFIG_AD400X) += ad400x.o obj-$(CONFIG_AD4000) += ad4000.o obj-$(CONFIG_AD4130) += ad4130.o +obj-$(CONFIG_AD4170) += ad4170.o obj-$(CONFIG_AD4134) += ad4134.o obj-$(CONFIG_AD4630) += ad4630.o obj-$(CONFIG_AD6676) += ad6676.o diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c new file mode 100644 index 00000000000000..11bc4966e700f0 --- /dev/null +++ b/drivers/iio/adc/ad4170.c @@ -0,0 +1,2227 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024 Analog Devices, Inc. + * Author: Ana-Maria Cusco + * Author: Marcelo Schmitt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ad4170.h" + +struct ad4170_slot_info { + struct ad4170_setup setup; + unsigned int enabled_channels; +}; + +struct ad4170_chan_info { + int slot; + int input_range_uv; + u32 scale_tbl[10][2]; + int offset_tbl[10]; + bool enabled; +}; + +struct ad4170_state { + struct regmap *regmap; + bool spi_is_dma_mapped; + struct spi_device *spi; + struct clk *mclk; + struct regulator_bulk_data supplies[7]; + struct mutex lock; /* Protect filter, PGA, GPIO, chan read, chan config */ + struct ad4170_chan_info *chan_info; + struct ad4170_slot_info slots_info[AD4170_NUM_SETUPS]; + unsigned int num_channels; + u32 fclk; + enum ad4170_pin_function pins_fn[AD4170_NUM_ANALOG_PINS]; + u32 vbias_pins[AD4170_MAX_ANALOG_PINS]; + u32 num_vbias_pins; + struct completion completion; + struct ad4170_config cfg; + struct iio_trigger *trig; + u32 data[AD4170_NUM_CHANNELS]; + struct gpio_chip gpiochip; + bool pdsw0; + bool pdsw1; + u32 chop_adc; + + struct spi_transfer xfer; + struct spi_message msg; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 reg_write_tx_buf[6]; + u8 reg_read_rx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 reg_read_tx_buf[2]; + //unsigned int rx_data[2] __aligned(IIO_DMA_MINALIGN); + //unsigned int tx_data[2]; + //u8 rx_data[6] __aligned(IIO_DMA_MINALIGN); + //u8 tx_data[2]; +}; + +static const unsigned int ad4170_iexc_chop_tbl[AD4170_IEXC_CHOP_MAX] = { + [AD4170_CHOP_IEXC_OFF] = AD4170_MISC_CHOP_IEXC_OFF, + [AD4170_CHOP_IEXC_AB] = AD4170_MISC_CHOP_IEXC_AB, + [AD4170_CHOP_IEXC_CD] = AD4170_MISC_CHOP_IEXC_CD, + [AD4170_CHOP_IEXC_ABCD] = AD4170_MISC_CHOP_IEXC_ABCD, +}; + +static const unsigned int ad4170_iout_pin_tbl[AD4170_I_OUT_PIN_MAX] = { + [AD4170_I_OUT_AIN0] = AD4170_CURRENT_IOUT_AIN0, + [AD4170_I_OUT_AIN1] = AD4170_CURRENT_IOUT_AIN1, + [AD4170_I_OUT_AIN2] = AD4170_CURRENT_IOUT_AIN2, + [AD4170_I_OUT_AIN3] = AD4170_CURRENT_IOUT_AIN3, + [AD4170_I_OUT_AIN4] = AD4170_CURRENT_IOUT_AIN4, + [AD4170_I_OUT_AIN5] = AD4170_CURRENT_IOUT_AIN5, + [AD4170_I_OUT_AIN6] = AD4170_CURRENT_IOUT_AIN6, + [AD4170_I_OUT_AIN7] = AD4170_CURRENT_IOUT_AIN7, + [AD4170_I_OUT_AIN8] = AD4170_CURRENT_IOUT_AIN8, + [AD4170_I_OUT_GPIO0] = AD4170_CURRENT_IOUT_GPIO0, + [AD4170_I_OUT_GPIO1] = AD4170_CURRENT_IOUT_GPIO1, + [AD4170_I_OUT_GPIO2] = AD4170_CURRENT_IOUT_GPIO2, + [AD4170_I_OUT_GPIO3] = AD4170_CURRENT_IOUT_GPIO3, +}; + +static const unsigned int ad4170_iout_current_ua_tbl[AD4170_I_OUT_MAX] = { + [AD4170_I_OUT_0UA] = 0, + [AD4170_I_OUT_10UA] = 10, + [AD4170_I_OUT_50UA] = 50, + [AD4170_I_OUT_100UA] = 100, + [AD4170_I_OUT_250UA] = 250, + [AD4170_I_OUT_500UA] = 500, + [AD4170_I_OUT_1000UA] = 1000, + [AD4170_I_OUT_1500UA] = 1500, +}; + +static const unsigned int ad4170_burnout_current_na_tbl[AD4170_BURNOUT_MAX] = { + [AD4170_BURNOUT_OFF] = 0, + [AD4170_BURNOUT_100NA] = 100, + [AD4170_BURNOUT_2000NA] = 2000, + [AD4170_BURNOUT_10000NA] = 10000, +}; + +static const char * const ad4170_filter_modes_str[] = { + [AD4170_FILT_SINC5_AVG] = "sinc5+avg", + [AD4170_FILT_SINC5] = "sinc5", + [AD4170_FILT_SINC3] = "sinc3", +}; + +struct ad4170_filter_config { + enum ad4170_filter_type filter_type; + unsigned int odr_div; + unsigned int fs_max; + unsigned int fs_min; + unsigned int shift; + enum iio_available_type samp_freq_avail_type; + int samp_freq_avail_len; + int samp_freq_avail[3][2]; +}; + +/* + * For the moment this structure uses the internal 16MHz clk, needs to be + * adapted to external clk source. + */ +#define AD4170_ODR_CONFIG(_filter_type, _odr_div, _fs_min, _fs_max, _shift) \ +{ \ + .filter_type = (_filter_type), \ + .odr_div = (_odr_div), \ + .fs_min = (_fs_min), \ + .fs_max = (_fs_max), \ + .shift = (_shift), \ + .samp_freq_avail_type = IIO_AVAIL_RANGE, \ + .samp_freq_avail_len = 3, \ + .samp_freq_avail = { \ + { AD4170_INT_FREQ_16MHZ, (_odr_div) * (_fs_max) >> _shift}, \ + { AD4170_INT_FREQ_16MHZ, (_odr_div) * (_fs_max / 2) >> _shift}, \ + { AD4170_INT_FREQ_16MHZ, (_odr_div) * (_fs_min) >> _shift}, \ + }, \ +} + +static const struct ad4170_filter_config ad4170_filter_configs[] = { + [AD4170_FILT_SINC5_AVG] = AD4170_ODR_CONFIG(AD4170_FILT_SINC5_AVG, 128, 4, 65532, 2), + [AD4170_FILT_SINC5] = AD4170_ODR_CONFIG(AD4170_FILT_SINC5, 32, 1, 256, 0), + [AD4170_FILT_SINC3] = AD4170_ODR_CONFIG(AD4170_FILT_SINC3, 32, 4, 65532, 0), +}; + +static int ad4170_get_reg_size(struct ad4170_state *st, unsigned int reg, + unsigned int *size) +{ + if (reg >= ARRAY_SIZE(ad4170_reg_size)) + return -EINVAL; + + if (!ad4170_reg_size[reg]) + return -EINVAL; + + *size = ad4170_reg_size[reg]; + + return 0; +} + +static int ad4170_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4170_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4170_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4170_state *st = context; + unsigned int size, addr; + int ret; + + ret = ad4170_get_reg_size(st, reg, &size); + if (ret) + return ret; + + addr = reg + size - 1; + put_unaligned_be16(addr, &st->reg_write_tx_buf[0]); + + switch (size) { + case 4: + put_unaligned_be32(val, &st->reg_write_tx_buf[2]); + break; + case 3: + put_unaligned_be24(val, &st->reg_write_tx_buf[2]); + break; + case 2: + put_unaligned_be16(val, &st->reg_write_tx_buf[2]); + break; + case 1: + st->reg_write_tx_buf[2] = val; + break; + default: + return -EINVAL; + } + + return spi_write(st->spi, st->reg_write_tx_buf, size + 2); +} + +static int ad4170_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4170_state *st = context; + struct spi_transfer t[] = { + { + .tx_buf = st->reg_read_tx_buf, + .len = ARRAY_SIZE(st->reg_read_tx_buf), + }, + { + .rx_buf = st->reg_read_rx_buf, + }, + }; + unsigned int size, addr; + int ret; + + ret = ad4170_get_reg_size(st, reg, &size); + if (ret) + return ret; + + addr = reg + size - 1; + put_unaligned_be16(AD4170_READ_MASK | addr, &st->reg_read_tx_buf[0]); + + t[1].len = size; + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret) + return ret; + + switch (size) { + case 4: + *val = get_unaligned_be32(st->reg_read_rx_buf); + break; + case 3: + *val = get_unaligned_be24(st->reg_read_rx_buf); + break; + case 2: + *val = get_unaligned_be16(st->reg_read_rx_buf); + break; + case 1: + *val = st->reg_read_rx_buf[0]; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct regmap_config ad4170_regmap_config = { + .reg_bits = 14, + .val_bits = 32, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .reg_read = ad4170_reg_read, + .reg_write = ad4170_reg_write, +}; + +/* 8 possible setups (slots) (0-7)*/ +static int ad4170_write_slot_setup(struct ad4170_state *st, + unsigned int slot, + struct ad4170_setup *setup) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(AD4170_SETUPS_MISC_CHOP_IEXC_MSK, setup->misc.chop_iexc) | + FIELD_PREP(AD4170_SETUPS_MISC_CHOP_ADC_MSK, setup->misc.chop_adc) | + FIELD_PREP(AD4170_SETUPS_MISC_BURNOUT_MSK, setup->misc.burnout); + + ret = regmap_write(st->regmap, AD4170_MISC_REG(slot), val); + if (ret) + return ret; + + val = FIELD_PREP(AD4170_SETUPS_AFE_REF_BUF_M_MSK, setup->afe.ref_buf_m) | + FIELD_PREP(AD4170_SETUPS_AFE_REF_BUF_P_MSK, setup->afe.ref_buf_p) | + FIELD_PREP(AD4170_SETUPS_AFE_REF_SELECT_MSK, setup->afe.ref_select) | + FIELD_PREP(AD4170_SETUPS_AFE_BIPOLAR_MSK, setup->afe.bipolar) | + FIELD_PREP(AD4170_SETUPS_AFE_PGA_GAIN_MSK, setup->afe.pga_gain); + + ret = regmap_write(st->regmap, AD4170_AFE_REG(slot), val); + if (ret) + return ret; + + val = FIELD_PREP(AD4170_SETUPS_POST_FILTER_SEL_MSK, setup->filter.post_filter_sel) | + FIELD_PREP(AD4170_SETUPS_FILTER_TYPE_MSK, setup->filter.filter_type); + + ret = regmap_write(st->regmap, AD4170_FILTER_REG(slot), val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_FILTER_FS_REG(slot), setup->filter_fs); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_OFFSET_REG(slot), setup->offset); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_GAIN_REG(slot), setup->gain); + if (ret) + return ret; + + memcpy(&st->slots_info[slot].setup, setup, sizeof(*setup)); + return 0; +} + +static int ad4170_write_channel_setup(struct ad4170_state *st, + unsigned int channel_addr) +{ + struct ad4170_chan_info *chan_info = &st->chan_info[channel_addr]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + int slot = chan_info->slot; + int ret; + + setup->afe.ref_buf_m = AD4170_REF_BUF_PRE; + setup->afe.ref_buf_p = AD4170_REF_BUF_PRE; + setup->filter.post_filter_sel = AD4170_POST_FILTER_NONE; + setup->gain = AD4170_DEFAULT_ADC_GAIN_COEF; + + ret = ad4170_write_slot_setup(st, slot, setup); + if (ret) + return ret; + + /* Hardcode default setup for channel x and write it */ + ret = regmap_update_bits(st->regmap, AD4170_CHAN_SETUP_REG(slot), + AD4170_CHANNEL_SETUPN_SETUP_N_MSK, + FIELD_PREP(AD4170_CHANNEL_SETUPN_SETUP_N_MSK, + slot)); + if (ret) + return ret; + + return 0; +} + +static void ad4170_freq_to_fs(enum ad4170_filter_type filter_type, + int val, int val2, unsigned int *fs) +{ + const struct ad4170_filter_config *filter_config = + &ad4170_filter_configs[filter_type]; + u64 dividend, divisor; + int temp; + + dividend = (u64)AD4170_INT_FREQ_16MHZ * MICRO; + divisor = filter_config->odr_div * ((u64)val * MICRO + val2); + + temp = DIV64_U64_ROUND_CLOSEST(dividend, divisor); + temp <<= filter_config->shift; + + if (temp < filter_config->fs_min) + temp = filter_config->fs_min; + else if (temp > filter_config->fs_max) + temp = filter_config->fs_max; + + *fs = temp; +} + +static void ad4170_fs_to_freq(enum ad4170_filter_type filter_type, + unsigned int fs, int *val, int *val2) +{ + const struct ad4170_filter_config *filter_config = + &ad4170_filter_configs[filter_type]; + unsigned int dividend, divisor; + u64 temp; + + dividend = AD4170_INT_FREQ_16MHZ; + divisor = (fs >> filter_config->shift) * filter_config->odr_div; + + temp = div_u64((u64)dividend, divisor); + *val = div_u64_rem(temp, 1UL, val2); +} + +static int ad4170_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + enum ad4170_filter_type old_filter_type; + int freq_val, freq_val2; + unsigned int old_fs; + int ret = 0; + + mutex_lock(&st->lock); + if (setup->filter.filter_type == val) + goto out; + + old_fs = setup->filter_fs; + old_filter_type = setup->filter.filter_type; + /* + * When switching between filter modes, try to match the ODR as + * close as possible. To do this, convert the current FS into ODR + * using the old filter mode, then convert it back into FS using + * the new filter mode. + */ + ad4170_fs_to_freq(setup->filter.filter_type, setup->filter_fs, + &freq_val, &freq_val2); + + ad4170_freq_to_fs(val, freq_val, freq_val2, &setup->filter_fs); + + setup->filter.filter_type = val; + + ret = ad4170_write_channel_setup(st, chan->address); + if (ret) { + setup->filter_fs = old_fs; + setup->filter.filter_type = old_filter_type; + } + + out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + enum ad4170_filter_type filter_type; + + mutex_lock(&st->lock); + filter_type = setup->filter.filter_type; + mutex_unlock(&st->lock); + + return filter_type; +} + +static const struct iio_enum ad4170_filter_type_enum = { + .items = ad4170_filter_modes_str, + .num_items = ARRAY_SIZE(ad4170_filter_modes_str), + .set = ad4170_set_filter_type, + .get = ad4170_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad4170_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, &ad4170_filter_type_enum), + { } +}; + +static const struct iio_chan_spec ad4170_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .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_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad4170_filter_type_ext_info, + .scan_type = { + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, +}; + +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_set_mode(struct ad4170_state *st, enum ad4170_mode mode) +{ + return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_REG_CTRL_MODE_MSK, + FIELD_PREP(AD4170_REG_CTRL_MODE_MSK, mode)); +} + +static int ad4170_set_channel_enable(struct ad4170_state *st, + unsigned int channel, bool status) +{ + struct ad4170_chan_info *chan_info = &st->chan_info[channel]; + struct ad4170_slot_info *slot_info; + int ret; + + if (chan_info->enabled == status) + return 0; + + slot_info = &st->slots_info[chan_info->slot]; + + ret = regmap_update_bits(st->regmap, AD4170_CHANNEL_EN_REG, + AD4170_CHANNEL_EN(channel), + status ? AD4170_CHANNEL_EN(channel) : 0); + if (ret) + return ret; + + slot_info->enabled_channels += status ? 1 : -1; + chan_info->enabled = status; + return 0; +} + +static int _ad4170_read_sample(struct iio_dev *indio_dev, unsigned int channel, + int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_info[channel]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + int precision_bits = ad4170_channel_template.scan_type.realbits; + int ret; + + ret = ad4170_set_channel_enable(st, channel, true); + if (ret) + return ret; + + if (!st->spi_is_dma_mapped) + reinit_completion(&st->completion); + + ret = ad4170_set_mode(st, AD4170_MODE_SINGLE); + if (ret) + return ret; + + if (!st->spi_is_dma_mapped) { + ret = wait_for_completion_timeout(&st->completion, HZ); + if (!ret) + goto out; + } + + ret = regmap_read(st->regmap, AD4170_DATA_24b_REG, val); + if (ret) + return ret; + + if (setup->afe.bipolar) + *val = sign_extend32(*val, precision_bits - 1); +out: + ret = ad4170_set_channel_enable(st, channel, false); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4170_read_sample(struct iio_dev *indio_dev, unsigned int channel, + int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + mutex_lock(&st->lock); + ret = _ad4170_read_sample(indio_dev, channel, val); + mutex_unlock(&st->lock); + + iio_device_release_direct_mode(indio_dev); + + return ret; +} + +/* + * Receives the device state, the number of a multiplexed input (AINP_N + * or AIM_N), and stores the voltage (in µV) of the specified input into the + * third argument. If the input number is not one of the special multiplexed + * inputs ((AVDD-AVSS)/5, ..., REFOUT), stores zero to the voltage argument. + * If a voltage regulator required by the special input is unavailable, return + * error code. Return 0 on success. + * + * @st: pointer to device state struct + * @ain_n: number of a multiplexed AD4170 input + * @ain_voltage: pointer to a variable where to store ain_n voltage + */ +static int ad4170_get_AINM_voltage(struct ad4170_state *st, int ain_n, + int *ain_voltage) +{ + int ret; + + *ain_voltage = 0; + switch (ain_n) { + case AD4170_AVDD_AVSS_N: + ret = regulator_get_voltage(st->supplies[AD4170_AVDD_SUP].consumer); + if (ret < 0) + return ret; + + *ain_voltage = ret ? ret / 5 : 0; + return 0; + case AD4170_IOVDD_DGND_N: + ret = regulator_get_voltage(st->supplies[AD4170_IOVDD_SUP].consumer); + if (ret < 0) + return ret; + + *ain_voltage = ret ? ret / 5 : 0; + return 0; + case AD4170_AVSS: + ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer); + if (ret < 0) + ret = 0; /* Assume AVSS at 0V if not provided */ + + /* AVSS is never above 0V, i.e., it can only be negative. */ + *ain_voltage = -ret; /* AVSS is a negative voltage */ + return 0; + case AD4170_DGND: + *ain_voltage = 0; + return 0; + case AD4170_REFIN1_P: + ret = regulator_get_voltage(st->supplies[AD4170_REFIN1P_SUP].consumer); + if (ret < 0) + return ret; + + *ain_voltage = ret; + return 0; + case AD4170_REFIN1_N: + ret = regulator_get_voltage(st->supplies[AD4170_REFIN1N_SUP].consumer); + if (ret < 0) + return ret; + + /* + * Making the assumption negative inputs of voltage references + * are either at GND level or negative with respect to GND. + */ + *ain_voltage = -ret; + return 0; + case AD4170_REFIN2_P: + ret = regulator_get_voltage(st->supplies[AD4170_REFIN2P_SUP].consumer); + if (ret < 0) + return ret; + + *ain_voltage = ret; + return 0; + case AD4170_REFIN2_N: + ret = regulator_get_voltage(st->supplies[AD4170_REFIN2N_SUP].consumer); + if (ret < 0) + return ret; + + /* + * Making the assumption negative inputs of voltage references + * are either at GND level or negative with respect to GND. + */ + *ain_voltage = -ret; + return 0; + case AD4170_REFOUT: + /* REFOUT is 2.5V relative to AVSS so take that into account */ + ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer); + if (ret < 0) + ret = 0; /* Assume AVSS at GND (0V) if not provided */ + + *ain_voltage = AD4170_INT_REF_2_5V - ret; + return 0; + } + return -EINVAL; +} + +static int ad4170_validate_analog_input(struct ad4170_state *st, int pin) +{ + if (pin <= AD4170_MAX_ANALOG_PINS) { + if (st->pins_fn[pin] != AD4170_PIN_UNASIGNED) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Pin %d has been previously assigned.\n", + pin); + + st->pins_fn[pin] = AD4170_PIN_ANALOG_IN; + } + return 0; +} + +static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool com) +{ + /* Check common-mode input pin is mapped to a special input. */ + if (com && (pin < AD4170_MAP_AVDD_AVSS_P || pin > AD4170_MAP_REFOUT)) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid common-mode input pin number. %d\n", + pin); + + /* Check differential input pin is mapped to a analog input pin. */ + if (!com && pin > AD4170_MAX_ANALOG_PINS) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid analog input pin number. %d\n", + pin); + + return ad4170_validate_analog_input(st, pin); +} + +/* + * Verifies whether the channel input configuration is valid by checking the + * provided input type and input numbers. + * Returns 0 on valid channel input configuration. -EINVAl otherwise. + * + * @st: pointer to device state struct + * @chan: pointer to IIO channel spec struct + * @ref_sel: voltage reference selection number + */ +static int ad4170_validate_channel(struct ad4170_state *st, + struct iio_chan_spec const *chan, + enum ad4170_ref_select ref_sel) +{ + int ret; + + /* Check temperature channel mapping. */ + if (chan->channel == AD4170_MAP_TEMP_SENSOR_P) { + if (chan->channel2 != AD4170_MAP_TEMP_SENSOR_N) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid temperature channel pin. %d\n", + chan->channel2); + + return 0; + } + + ret = ad4170_validate_channel_input(st, chan->channel, false); + if (ret < 0) + return ret; + + ret = ad4170_validate_channel_input(st, chan->channel2, !chan->differential); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Receives the device state, the channel spec, a reference selection, and + * returns the magnitude of the allowed input range in µV. + * Verifies whether the channel configuration is valid by checking the provided + * input type, polarity, and voltage references result in a sane input range. + * Returns negative error code on failure. + */ +static int ad4170_get_input_range(struct ad4170_state *st, + struct iio_chan_spec const *chan, + enum ad4170_ref_select ref_sel) +{ + struct ad4170_chan_info *chan_info = &st->chan_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + bool bipolar = setup->afe.bipolar; + int refp, refn, ain_voltage, ret; + + switch (ref_sel) { + case AD4170_REFIN_REFIN1: + refp = regulator_get_voltage(st->supplies[AD4170_REFIN1P_SUP].consumer); + refn = regulator_get_voltage(st->supplies[AD4170_REFIN1N_SUP].consumer); + break; + case AD4170_REFIN_REFIN2: + refp = regulator_get_voltage(st->supplies[AD4170_REFIN2P_SUP].consumer); + refn = regulator_get_voltage(st->supplies[AD4170_REFIN2N_SUP].consumer); + break; + case AD4170_REFIN_AVDD: + refp = regulator_get_voltage(st->supplies[AD4170_AVDD_SUP].consumer); + ret = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer); + /* + * TODO AVSS is actually optional. + * Should we handle -EPROBE_DEFER here? + */ + if (ret < 0) + ret = 0; /* Assume AVSS at GND if not provided */ + + refn = ret; + break; + case AD4170_REFIN_REFOUT: + refn = regulator_get_voltage(st->supplies[AD4170_AVSS_SUP].consumer); + if (refn < 0) + refn = 0; + + /* REFOUT is 2.5 V relative to AVSS */ + /* avss-supply is never above 0V. */ + refp = AD4170_INT_REF_2_5V - refn; + break; + default: + return -EINVAL; + } + if (refp < 0) + return refp; + + if (refn < 0) + return refn; + + /* + * Find out the analog input range from the channel type, polarity, and + * voltage reference selection. + * AD4170 channels are either differential or pseudo-differential. + */ + /* Differential Input Voltage Range: −VREF/gain to +VREF/gain (datasheet page 6) */ + /* Single-Ended Input Voltage Range: 0 to VREF/gain (datasheet page 6) */ + if (chan->differential) { + if (!bipolar) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid channel %lu setup.\n", + chan->address); + + /* Differential bipolar channel */ + /* avss-supply is never above 0V. */ + /* Assuming refin1n-supply not above 0V. */ + /* Assuming refin2n-supply not above 0V. */ + return refp + refn; + } + /* + * Some configurations can lead to invalid setups. + * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS), + * and single-ended 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 (bipolar) { + /* Pseudo-differential bipolar channel */ + /* Input allowed to swing from GND to +VREF */ + if (refp <= 0) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid setup for channel %lu.\n", + chan->address); + + return refp; + } + + /* Pseudo-differential unipolar channel */ + /* Input allowed to swing from IN- to +VREF */ + if (refp <= 0) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid setup for channel %lu.\n", + chan->address); + + ret = ad4170_get_AINM_voltage(st, chan->channel2, &ain_voltage); + if (ret < 0) + return ret; + + if (refp - ain_voltage <= 0) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid setup for channel %lu.\n", + chan->address); + + return refp - ain_voltage; +} + +static void ad4170_channel_scale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + + *val = chan_info->scale_tbl[setup->afe.pga_gain][0]; + *val2 = chan_info->scale_tbl[setup->afe.pga_gain][1]; +} + +static int ad4170_channel_offset(struct ad4170_chan_info *chan_info, + struct ad4170_setup *setup) +{ + return chan_info->offset_tbl[setup->afe.pga_gain]; +} + +static int ad4170_get_offset(struct iio_dev *indio_dev, int addr, int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_read(st->regmap, AD4170_OFFSET_REG(addr), val); + if (ret < 0) + return ret; + + return 0; +} + +static int ad4170_get_gain(struct iio_dev *indio_dev, int addr, int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_read(st->regmap, AD4170_GAIN_REG(addr), val); + if (ret < 0) + return ret; + + return 0; +} + +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); + unsigned int channel = chan->scan_index; + struct ad4170_chan_info *chan_info = &st->chan_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad4170_read_sample(indio_dev, channel, val); + case IIO_CHAN_INFO_SCALE: + mutex_lock(&st->lock); + ad4170_channel_scale(indio_dev, chan, val, val2); + mutex_unlock(&st->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = ad4170_channel_offset(chan_info, setup); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&st->lock); + ad4170_fs_to_freq(setup->filter.filter_type, setup->filter_fs, + val, val2); + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + ad4170_get_offset(indio_dev, channel, val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ad4170_get_gain(indio_dev, channel, val); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev, int channel) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_info[channel]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + const struct iio_chan_spec *chan = &indio_dev->channels[channel]; + int ch_resolution = chan->scan_type.realbits - setup->afe.bipolar; + int pga, ainm_voltage, ret; + unsigned long long offset; + + ainm_voltage = 0; + if (chan->channel2 > AD4170_MAP_TEMP_SENSOR_N) { + ret = ad4170_get_AINM_voltage(st, chan->channel2, &ainm_voltage); + if (ret < 0) + return dev_err_probe(&st->spi->dev, ret, + "Failed to fill scale tbl: %d\n", + ret); + } + + for (pga = 0; pga < AD4170_PGA_GAIN_MAX; pga++) { + u64 nv; + unsigned int lshift, rshift; + + /* + * The scale factor to get ADC output codes to values in mV + * units is given by: + * _scale = (input_range / gain) / 2^precision + * AD4170 gain is a power of 2 so the above can be written as + * _scale = input_range / 2^(precision + gain) + * Keep the input range in µV before right shift to preserve + * scale precision. + */ + nv = (u64)chan_info->input_range_uv * NANO; + lshift = (pga >> 3 & 1); /* handle cases 8 and 9 */ + rshift = ch_resolution + (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. + * For _raw + _offset to be relative to GND, the value provided + * as _offset is of opposite signal than the real offset. + * If the negative input is not fixed, there is no offset. + */ + offset = ((unsigned long long)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 */ + 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_info[chan->address]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + const struct ad4170_filter_config *filter_config; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)chan_info->scale_tbl; + *length = ARRAY_SIZE(chan_info->scale_tbl) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&st->lock); + filter_config = &ad4170_filter_configs[setup->filter.filter_type]; + mutex_unlock(&st->lock); + + *vals = (int *)filter_config->samp_freq_avail; + *length = filter_config->samp_freq_avail_len; + *type = IIO_VAL_FRACTIONAL; + + return filter_config->samp_freq_avail_type; + default: + return -EINVAL; + } +} + +static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4170_set_channel_pga(struct iio_dev *indio_dev, + struct ad4170_state *st, + unsigned int channel_addr, int val, int val2) +{ + struct ad4170_chan_info *chan_info = &st->chan_info[channel_addr]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + unsigned int pga, old_pga; + int ret = 0; + + for (pga = 0; pga < AD4170_PGA_GAIN_MAX; pga++) { + if (val == chan_info->scale_tbl[pga][0] && + val2 == chan_info->scale_tbl[pga][1]) + break; + } + + if (pga == AD4170_PGA_GAIN_MAX) + return -EINVAL; + + mutex_lock(&st->lock); + if (pga == setup->afe.pga_gain) + goto out; + + old_pga = setup->afe.pga_gain; + setup->afe.pga_gain = pga; + + ret = ad4170_write_channel_setup(st, channel_addr); + if (ret) + setup->afe.pga_gain = old_pga; + +out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_set_channel_freq(struct ad4170_state *st, + unsigned int channel_addr, int val, int val2) +{ + struct ad4170_chan_info *chan_info = &st->chan_info[channel_addr]; + struct ad4170_setup *setup = &st->slots_info[chan_info->slot].setup; + unsigned int fs, old_fs; + int ret = 0; + + mutex_lock(&st->lock); + old_fs = setup->filter_fs; + + ad4170_freq_to_fs(setup->filter.filter_type, val, val2, &fs); + + if (fs == setup->filter_fs) + goto out; + + setup->filter_fs = fs; + + ret = ad4170_write_channel_setup(st, channel_addr); + if (ret) + setup->filter_fs = old_fs; + +out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_set_gain(struct iio_dev *indio_dev, int addr, int val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + /* + * When writing to the GAIN registers, the ADC must be placed in + * standby mode or idle mode. + */ + ret = ad4170_set_mode(st, AD4170_MODE_IDLE); + if (ret) + return ret; + + val &= AD4170_GAIN_MSK; + return regmap_write(st->regmap, AD4170_GAIN_REG(addr), val); +} + +static int ad4170_set_offset(struct iio_dev *indio_dev, int addr, int val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + /* + * When writing to the OFFSET_X registers, the ADC must be placed in + * standby mode or idle mode. + */ + ret = ad4170_set_mode(st, AD4170_MODE_IDLE); + if (ret) + return ret; + + val &= AD4170_OFFSET_MSK; + return regmap_write(st->regmap, AD4170_OFFSET_REG(addr), val); +} + +static int ad4170_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int channel = chan->address; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4170_set_channel_pga(indio_dev, st, channel, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4170_set_channel_freq(st, channel, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad4170_set_offset(indio_dev, channel, val); + case IIO_CHAN_INFO_CALIBSCALE: + return ad4170_set_gain(indio_dev, channel, val); + default: + return -EINVAL; + } +} + +static int ad4170_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int channel; + int ret; + + mutex_lock(&st->lock); + + for_each_set_bit(channel, scan_mask, indio_dev->num_channels) { + ret = ad4170_set_channel_enable(st, channel, true); + if (ret) + goto out; + } +out: + mutex_unlock(&st->lock); + return 0; +} + +static const struct iio_info ad4170_info = { + .read_raw = ad4170_read_raw, + .read_avail = ad4170_read_avail, + .write_raw = ad4170_write_raw, + .write_raw_get_fmt = ad4170_write_raw_get_fmt, + .update_scan_mode = ad4170_update_scan_mode, + .debugfs_reg_access = ad4170_reg_access, +}; + +static int ad4170_soft_reset(struct ad4170_state *st) +{ + int ret; + unsigned int reg = AD4170_INTERFACE_CONFIG_A_REG; + + ret = regmap_write(st->regmap, reg, AD4170_SW_RESET_MSK); + if (ret) + return ret; + + /* + * AD4170-4 requires a minimum of 1 ms between any reset event and a + * register read/write transaction. + */ + fsleep(AD4170_RESET_SLEEP_US); + + return 0; +} + +static void ad4170_clk_disable_unprepare(void *clk) +{ + clk_disable_unprepare(clk); +} + +static inline bool ad4170_valid_external_frequency(u32 freq) +{ + return (freq >= AD4170_EXT_FREQ_MHZ_MIN && + freq <= AD4170_EXT_FREQ_MHZ_MAX); +} + +static int ad4170_of_clock_select(struct ad4170_state *st) +{ + struct device_node *np = st->spi->dev.of_node; + unsigned int clocksel; + + clocksel = AD4170_INTERNAL_OSC; + + /* use internal clock */ + if (PTR_ERR(st->mclk) == -ENOENT) { + if (of_property_read_bool(np, "adi,int-clock-output-enable")) + clocksel = AD4170_INTERNAL_OSC_OUTPUT; + } else { + if (of_property_read_bool(np, "adi,clock-xtal")) + clocksel = AD4170_EXTERNAL_XTAL; + else + clocksel = AD4170_EXTERNAL_OSC; + } + return clocksel; +} + +static int ad4170_parse_digif_fw(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + /* + * Optional adi,dig-aux1 defaults to 0, DIG_AUX1 pin disabled. + */ + st->cfg.pin_muxing.dig_aux1_ctrl = AD4170_DIG_AUX1_DISABLED; + ret = device_property_read_u8(dev, "adi,dig-aux1", + &st->cfg.pin_muxing.dig_aux1_ctrl); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read adi,dig-aux1 property\n"); + + if (st->cfg.pin_muxing.dig_aux1_ctrl < AD4170_DIG_AUX1_DISABLED || + st->cfg.pin_muxing.dig_aux1_ctrl > AD4170_DIG_AUX1_SYNC) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,dig-aux1 value: %u\n", + st->cfg.pin_muxing.dig_aux1_ctrl); + + /* + * Optional adi,dig-aux2 defaults to 0, DIG_AUX2 pin disabled. + */ + st->cfg.pin_muxing.dig_aux2_ctrl = AD4170_DIG_AUX2_DISABLED; + ret = device_property_read_u8(dev, "adi,dig-aux2", + &st->cfg.pin_muxing.dig_aux2_ctrl); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read adi,dig-aux2 property\n"); + + if (st->cfg.pin_muxing.dig_aux2_ctrl < AD4170_DIG_AUX2_DISABLED || + st->cfg.pin_muxing.dig_aux2_ctrl > AD4170_DIG_AUX2_SYNC) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,dig-aux2 value: %u\n", + st->cfg.pin_muxing.dig_aux2_ctrl); + + /* + * Optional adi,sync-option defaults to 1, standard sync functionality. + */ + st->cfg.pin_muxing.sync_ctrl = AD4170_SYNC_STANDARD; + ret = device_property_read_u8(dev, "adi,sync-option", + &st->cfg.pin_muxing.sync_ctrl); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read adi,sync-option property\n"); + + if (st->cfg.pin_muxing.sync_ctrl < AD4170_SYNC_DISABLED || + st->cfg.pin_muxing.sync_ctrl > AD4170_SYNC_ALTERNATE) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,sync-option value: %u\n", + st->cfg.pin_muxing.sync_ctrl); + + return 0; +} + +static int ad4170_parse_fw_setup(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup) +{ + struct device *dev = &st->spi->dev; + u32 tmp; + int ret; + + tmp = 0; + fwnode_property_read_u32(child, "adi,chop-adc", &tmp); + setup->misc.chop_adc = tmp; + + st->chop_adc = tmp > st->chop_adc ? tmp : st->chop_adc; + + tmp = 0; + fwnode_property_read_u32(child, "adi,burnout-current-nanoamp", &tmp); + ret = ad4170_find_table_index(ad4170_burnout_current_na_tbl, tmp); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid burnout current %unA\n", tmp); + setup->misc.burnout = ret; + + setup->afe.ref_buf_p = fwnode_property_read_bool(child, + "adi,buffered-positive"); + setup->afe.ref_buf_m = fwnode_property_read_bool(child, + "adi,buffered-negative"); + + setup->afe.ref_select = AD4170_REFIN_REFOUT; + fwnode_property_read_u32(child, "adi,reference-select", + &setup->afe.ref_select); + if (setup->afe.ref_select >= AD4170_REFIN_MAX) + return dev_err_probe(dev, -EINVAL, + "Invalid reference selected %u\n", + setup->afe.ref_select); + + return 0; +} + +static int ad4170_parse_fw_channel_type(struct device *dev, + struct fwnode_handle *child, + struct iio_chan_spec *chan) +{ + u32 pins[2]; + int ret; + + ret = fwnode_property_read_u32_array(child, "diff-channels", pins, + ARRAY_SIZE(pins)); + if (!ret) { + chan->differential = true; + chan->channel = pins[0]; + chan->channel2 = pins[1]; + return 0; + } + ret = fwnode_property_read_u32(child, "single-channel", &pins[0]); + if (!ret) { + chan->differential = false; + chan->channel = pins[0]; + + ret = fwnode_property_read_u32(child, "common-mode-channel", + &pins[1]); + if (ret) + return dev_err_probe(dev, ret, + "single-ended channels must define common-mode-channel\n"); + + chan->channel2 = pins[1]; + return 0; + } + return dev_err_probe(dev, ret, + "Channel must define one of diff-channels or single-channel.\n"); +} + +static int ad4170_parse_fw_channel(struct iio_dev *indio_dev, + struct iio_chan_spec *chan_array, + struct fwnode_handle *child) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int index, setup_slot = 0; + struct device *dev = &st->spi->dev; + struct ad4170_chan_info *chan_info; + struct ad4170_setup *setup; + struct iio_chan_spec *chan; + int ret; + + ret = fwnode_property_read_u32(child, "reg", &index); + if (ret) + return ret; + + if (index >= indio_dev->num_channels) + return dev_err_probe(dev, -EINVAL, + "Channel idx greater than no of channels\n"); + + chan = chan_array + index; + chan_info = &st->chan_info[index]; + + *chan = ad4170_channel_template; + chan->address = index; + chan->scan_index = index; + + chan_info->slot = AD4170_INVALID_SLOT; + ret = fwnode_property_read_u32(child, "adi,config-setup-slot", &setup_slot); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read adi,config-setup-slot\n"); + + chan_info->slot = setup_slot; + if (chan_info->slot >= AD4170_NUM_SETUPS) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,config-setup-slot: %d\n", + chan_info->slot); + + ret = ad4170_parse_fw_channel_type(dev, child, chan); + if (ret < 0) + return ret; + + setup = &st->slots_info[chan_info->slot].setup; + setup->filter.filter_type = AD4170_FILT_SINC5_AVG; + setup->filter_fs = 0x4; + + setup->afe.bipolar = fwnode_property_read_bool(child, "bipolar"); + if (setup->afe.bipolar) + chan->scan_type.sign = 's'; + else + chan->scan_type.sign = 'u'; + + ret = ad4170_parse_fw_setup(st, child, setup); + if (ret) + return ret; + + ret = ad4170_validate_channel(st, chan, setup->afe.ref_select); + if (ret < 0) + return ret; + + ret = ad4170_get_input_range(st, chan, setup->afe.ref_select); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot use reference %u\n", + setup->afe.ref_select); + + chan_info->input_range_uv = ret; + return 0; +} + +static int ad4170_parse_fw_children(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct fwnode_handle *child; + struct ad4170_chan_info *chan_info; + struct iio_chan_spec *chan_array; + unsigned int num_channels; + int ret; + + num_channels = device_get_child_node_count(dev); + if (!num_channels) + return dev_err_probe(&indio_dev->dev, -ENODEV, + "no channels defined\n"); + + indio_dev->num_channels = num_channels; + + chan_array = devm_kcalloc(dev, num_channels, sizeof(*chan_array), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + chan_info = devm_kcalloc(dev, num_channels, + sizeof(*chan_info), GFP_KERNEL); + if (!chan_info) + return -ENOMEM; + + st->chan_info = chan_info; + + device_for_each_child_node(dev, child) { + ret = ad4170_parse_fw_channel(indio_dev, chan_array, child); + if (ret) { + fwnode_handle_put(child); + return ret; + } + } + + indio_dev->channels = chan_array; + return 0; +} + +/* + * Parses firmware data describing output current source setup. + * There are 4 excitation currents (IOUT0 to IOUT3) that can be configured + * independently. Excitation currents are added if they are output on the same + * pin. + */ +static int ad4170_parse_fw_exc_current(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + /* IOUT0 pin */ + st->cfg.current_src[0].i_out_pin = AD4170_I_OUT_AIN0; + ret = fwnode_property_read_u32(dev->fwnode, "adi,excitation-pin-0", + &st->cfg.current_src[0].i_out_pin); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_pin_tbl, + st->cfg.current_src[0].i_out_pin); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid adi,excitation-pin-0: %u\n", + st->cfg.current_src[0].i_out_pin); + } + + /* IOUT1 pin */ + st->cfg.current_src[1].i_out_pin = AD4170_I_OUT_AIN0; + ret = fwnode_property_read_u32(dev->fwnode, "adi,excitation-pin-1", + &st->cfg.current_src[1].i_out_pin); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_pin_tbl, + st->cfg.current_src[1].i_out_pin); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid adi,excitation-pin-1: %u\n", + st->cfg.current_src[1].i_out_pin); + } + + /* IOUT2 pin */ + st->cfg.current_src[2].i_out_pin = AD4170_I_OUT_AIN0; + ret = fwnode_property_read_u32(dev->fwnode, "adi,excitation-pin-2", + &st->cfg.current_src[2].i_out_pin); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_pin_tbl, + st->cfg.current_src[2].i_out_pin); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid adi,excitation-pin-2: %u\n", + st->cfg.current_src[2].i_out_pin); + } + + /* IOUT3 pin */ + st->cfg.current_src[3].i_out_pin = AD4170_I_OUT_AIN0; + ret = fwnode_property_read_u32(dev->fwnode, "adi,excitation-pin-3", + &st->cfg.current_src[3].i_out_pin); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_pin_tbl, + st->cfg.current_src[3].i_out_pin); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid adi,excitation-pin-3: %u\n", + st->cfg.current_src[3].i_out_pin); + } + + /* IOUT0 current */ + st->cfg.current_src[0].i_out_val = AD4170_I_OUT_0UA; + ret = fwnode_property_read_u32(dev->fwnode, + "adi,excitation-current-0-microamp", + &st->cfg.current_src[0].i_out_val); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl, + st->cfg.current_src[0].i_out_val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %uuA\n", + st->cfg.current_src[0].i_out_val); + } + if (ad4170_iout_current_ua_tbl[st->cfg.current_src[0].i_out_val] > 0 && + st->cfg.current_src[0].i_out_pin <= AD4170_I_OUT_AIN8) + st->pins_fn[st->cfg.current_src[0].i_out_pin] = AD4170_PIN_CURRENT_OUT; + + /* IOUT1 current */ + st->cfg.current_src[1].i_out_val = AD4170_I_OUT_0UA; + ret = fwnode_property_read_u32(dev->fwnode, + "adi,excitation-current-1-microamp", + &st->cfg.current_src[1].i_out_val); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl, + st->cfg.current_src[1].i_out_val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %uuA\n", + st->cfg.current_src[1].i_out_val); + } + if (ad4170_iout_current_ua_tbl[st->cfg.current_src[1].i_out_val] > 0 && + st->cfg.current_src[1].i_out_pin <= AD4170_I_OUT_AIN8) + st->pins_fn[st->cfg.current_src[1].i_out_pin] = AD4170_PIN_CURRENT_OUT; + + /* IOUT2 current */ + st->cfg.current_src[2].i_out_val = AD4170_I_OUT_0UA; + ret = fwnode_property_read_u32(dev->fwnode, + "adi,excitation-current-2-microamp", + &st->cfg.current_src[2].i_out_val); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl, + st->cfg.current_src[2].i_out_val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %uuA\n", + st->cfg.current_src[2].i_out_val); + } + if (ad4170_iout_current_ua_tbl[st->cfg.current_src[2].i_out_val] > 0 && + st->cfg.current_src[2].i_out_pin <= AD4170_I_OUT_AIN8) + st->pins_fn[st->cfg.current_src[2].i_out_pin] = AD4170_PIN_CURRENT_OUT; + + /* IOUT3 current */ + st->cfg.current_src[3].i_out_val = AD4170_I_OUT_0UA; + ret = fwnode_property_read_u32(dev->fwnode, + "adi,excitation-current-3-microamp", + &st->cfg.current_src[3].i_out_val); + if (!ret) { + ret = ad4170_find_table_index(ad4170_iout_current_ua_tbl, + st->cfg.current_src[3].i_out_val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %uuA\n", + st->cfg.current_src[3].i_out_val); + } + if (ad4170_iout_current_ua_tbl[st->cfg.current_src[3].i_out_val] > 0 && + st->cfg.current_src[3].i_out_pin <= AD4170_I_OUT_AIN8) + st->pins_fn[st->cfg.current_src[3].i_out_pin] = AD4170_PIN_CURRENT_OUT; + + return 0; +} + +static int ad4170_parse_fw(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret, i; + u8 tmp; + + st->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(st->mclk) && PTR_ERR(st->mclk) != -ENOENT) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get mclk\n"); + + st->cfg.clock_ctrl.clocksel = ad4170_of_clock_select(st); + ret = ad4170_parse_digif_fw(indio_dev); + if (ret < 0) + return ret; + + st->pdsw0 = fwnode_property_read_bool(dev->fwnode, + "adi,gpio0-power-down-switch"); + st->pdsw1 = fwnode_property_read_bool(dev->fwnode, + "adi,gpio1-power-down-switch"); + + ret = device_property_count_u32(dev, "adi,vbias-pins"); + if (ret > 0) { + if (ret > AD4170_MAX_ANALOG_PINS) + return dev_err_probe(dev, -EINVAL, + "Too many vbias pins %u\n", ret); + + st->num_vbias_pins = ret; + ret = device_property_read_u32_array(dev, "adi,vbias-pins", + st->vbias_pins, + st->num_vbias_pins); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read vbias pins\n"); + } + + for (i = 0; i < AD4170_NUM_ANALOG_PINS; i++) + st->pins_fn[i] = AD4170_PIN_UNASIGNED; + + ret = ad4170_parse_fw_exc_current(indio_dev); + if (ret) + return ret; + + ret = ad4170_parse_fw_children(indio_dev); + if (ret) + return ret; + + tmp = 0; + device_property_read_u8(dev, "adi,chop-iexc", &tmp); + ret = ad4170_find_table_index(ad4170_iexc_chop_tbl, tmp); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid adi,chop-iexc config: %u\n", tmp); + + /* Set excitation current chop config to first channel setup config */ + st->slots_info[indio_dev->channels[0].address].setup.misc.chop_iexc = tmp; + return 0; +} + +static void ad4170_disable_supplies(void *data) +{ + struct ad4170_state *st = data; + + regulator_bulk_disable(ARRAY_SIZE(st->supplies), st->supplies); +} + +static int ad4170_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int i, val; + int ret; + + st->fclk = AD4170_INT_FREQ_16MHZ; + if (st->cfg.clock_ctrl.clocksel == AD4170_EXTERNAL_OSC || + st->cfg.clock_ctrl.clocksel == AD4170_EXTERNAL_XTAL) { + ret = clk_prepare_enable(st->mclk); + if (ret) + return ret; + + st->fclk = clk_get_rate(st->mclk); + if (!ad4170_valid_external_frequency(st->fclk)) { + dev_warn(dev, "Invalid external clock frequency %u\n", + st->fclk); + return -EINVAL; + } + } + + ret = devm_add_action_or_reset(dev, ad4170_clk_disable_unprepare, + st->mclk); + if (ret) + return ret; + + val = FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, + st->cfg.pin_muxing.dig_aux1_ctrl); + val |= FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX2_CTRL_MSK, + st->cfg.pin_muxing.dig_aux2_ctrl); + val |= FIELD_PREP(AD4170_PIN_MUXING_SYNC_CTRL_MSK, + st->cfg.pin_muxing.sync_ctrl); + + ret = regmap_write(st->regmap, AD4170_PIN_MUXING_REG, val); + if (ret) + return ret; + + val = FIELD_PREP(AD4170_POWER_DOWN_SW_PDSW0_MSK, st->pdsw0) | + FIELD_PREP(AD4170_POWER_DOWN_SW_PDSW1_MSK, st->pdsw1); + + ret = regmap_write(st->regmap, AD4170_POWER_DOWN_SW_REG, val); + if (ret) + return ret; + + /* Put ADC in IDLE mode */ + ret = ad4170_set_mode(st, AD4170_MODE_IDLE); + if (ret) + return ret; + + /* Setup channels. */ + for (i = 0; i < indio_dev->num_channels; i++) { + struct iio_chan_spec const *chan = &indio_dev->channels[i]; + unsigned int val; + + ret = ad4170_write_channel_setup(st, chan->address); + if (ret) + return ret; + + val = FIELD_PREP(AD4170_CHANNEL_MAPN_AINP_MSK, chan->channel) | + FIELD_PREP(AD4170_CHANNEL_MAPN_AINM_MSK, chan->channel2); + + ret = regmap_write(st->regmap, AD4170_CHAN_MAP_REG(i), val); + if (ret) + return ret; + + ad4170_set_channel_freq(st, chan->address, AD4170_MAX_SAMP_RATE, 0); + ad4170_fill_scale_tbl(indio_dev, i); + if (ret) + return ret; + } + + val = 0; + for (i = 0; i < st->num_vbias_pins; i++) + val |= BIT(st->vbias_pins[i]); + + ret = regmap_write(st->regmap, AD4170_V_BIAS_REG, val); + if (ret) + return ret; + + for (i = 0; i < AD4170_NUM_CURRENT_SOURCE; i++) { + val = FIELD_PREP(AD4170_CURRENT_SOURCE_I_OUT_PIN_MSK, + st->cfg.current_src[i].i_out_pin) | + FIELD_PREP(AD4170_CURRENT_SOURCE_I_OUT_VAL_MSK, + st->cfg.current_src[i].i_out_val); + + ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(i), val); + if (ret) + return ret; + } + + ret = regmap_write(st->regmap, AD4170_CHANNEL_EN_REG, 0); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_STATUS_REG, 0xffff); + if (ret) + return ret; + + return 0; +} + +static const struct iio_trigger_ops ad4170_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static irqreturn_t ad4170_interrupt(int irq, void *dev_id) +{ + /* Top half of the interrupt, cannot sleep, should return asap*/ + struct iio_dev *indio_dev = dev_id; + struct ad4170_state *st = iio_priv(indio_dev); + + /* Acknowledge the interrupt and call trig handler*/ + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static void ad4170_prepare_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->reg_read_rx_buf; + st->xfer.bits_per_word = ad4170_channel_template.scan_type.storagebits; + st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.storagebits); + + spi_message_init_with_transfers(&st->msg, &st->xfer, 1); +} + +static int ad4170_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = ad4170_set_mode(st, AD4170_MODE_CONT); + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret, i; + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = ad4170_set_channel_enable(st, i, false); + if (ret) + return ret; + } + + return ad4170_set_mode(st, AD4170_MODE_IDLE); +} + +static const struct iio_buffer_setup_ops ad4170_buffer_ops = { + .postenable = ad4170_buffer_postenable, + .predisable = ad4170_buffer_predisable, +}; + +static int ad4170_hw_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + + ret = ad4170_set_mode(st, AD4170_MODE_CONT); + if (ret) + goto out; + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_REG_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_REG_CTRL_CONT_READ_MSK, + AD4170_CONT_READ_ON)); + if (ret < 0) + goto out; + + ret = spi_optimize_message(st->spi, &st->msg); + if (ret < 0) + goto out; + + spi_bus_lock(st->spi->master); + ret = spi_engine_ex_offload_load_msg(st->spi, &st->msg); + if (ret < 0) + goto out; + + spi_engine_ex_offload_enable(st->spi, true); + +out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_hw_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret, i; + + spi_engine_ex_offload_enable(st->spi, false); + spi_bus_unlock(st->spi->master); + spi_unoptimize_message(&st->msg); + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = ad4170_set_channel_enable(st, i, false); + if (ret) + return ret; + } + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_REG_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_REG_CTRL_CONT_READ_MSK, + AD4170_CONT_READ_OFF)); + if (ret < 0) + return ret; + + return ad4170_set_mode(st, AD4170_MODE_IDLE); +} + +static const struct iio_buffer_setup_ops ad4170_hw_buffer_ops = { + .postenable = ad4170_hw_buffer_postenable, + .predisable = ad4170_hw_buffer_predisable, +}; + +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 ret, i = 0; + int scan_index; + + mutex_lock(&st->lock); + for_each_set_bit(scan_index, indio_dev->active_scan_mask, + indio_dev->masklength) { + /* Read register data */ + ret = regmap_read(st->regmap, AD4170_DATA_24b_REG, &st->data[i]); + if (ret) + goto out; + i++; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &st->data, + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&st->lock); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ad4170_triggered_buffer_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + st->trig = devm_iio_trigger_alloc(indio_dev->dev.parent, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad4170_trigger_ops; + st->trig->dev.parent = indio_dev->dev.parent; + + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(indio_dev->dev.parent, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + init_completion(&st->completion); + + ret = request_irq(st->spi->irq, + &ad4170_interrupt, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + &iio_pollfunc_store_time, + &ad4170_trigger_handler, + &ad4170_buffer_ops); +} + +static int ad4170_hardware_buffer_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + + ad4170_prepare_message(st); + indio_dev->setup_ops = &ad4170_hw_buffer_ops; + return devm_iio_dmaengine_buffer_setup(indio_dev->dev.parent, + indio_dev, "rx", + IIO_BUFFER_DIRECTION_IN); +} + +static int ad4170_input_gpio(struct gpio_chip *chip, unsigned int offset) +{ + struct ad4170_state *st = gpiochip_get_data(chip); + unsigned int mask; + int ret; + + mutex_lock(&st->lock); + mask = AD4170_GPIO_MODE_REG_MSK << 2 * offset; + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, mask, + (AD4170_GPIO_INPUT << 2 * offset)); + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_output_gpio(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ad4170_state *st = gpiochip_get_data(chip); + unsigned int mask; + int ret, val; + + mutex_lock(&st->lock); + mask = AD4170_GPIO_MODE_REG_MSK << 2 * offset; + ret = regmap_update_bits(st->regmap, + AD4170_GPIO_MODE_REG, + mask, + (AD4170_GPIO_OUTPUT << 2 * offset)); + if (ret < 0) + goto out; + ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val); + if (ret < 0) + goto out; + + ret = regmap_update_bits(st->regmap, + AD4170_OUTPUT_DATA_REG, + BIT(offset), + (value << offset)); +out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad4170_get_gpio(struct gpio_chip *chip, unsigned int offset) +{ + struct ad4170_state *st = gpiochip_get_data(chip); + unsigned int val, mask; + int ret; + + mutex_lock(&st->lock); + ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val); + if (ret < 0) + goto out; + + mask = AD4170_GPIO_MODE_REG_MSK << 2 * offset; + switch (val & mask) { + case AD4170_GPIO_INPUT: + ret = regmap_read(st->regmap, AD4170_INPUT_DATA_REG, &val); + break; + case AD4170_GPIO_OUTPUT: + ret = regmap_read(st->regmap, AD4170_OUTPUT_DATA_REG, &val); + break; + default: + ret = -EINVAL; + } + + if (ret < 0) + goto out; + + ret = !!(val & BIT(offset)); + +out: + mutex_unlock(&st->lock); + return ret; +} + +static void ad4170_set_gpio(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct ad4170_state *st = gpiochip_get_data(chip); + unsigned int val, mask; + int ret; + + mutex_lock(&st->lock); + mask = AD4170_GPIO_MODE_REG_MSK << 2 * offset; + ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val); + if (ret < 0) + goto out; + + if (((val & mask) >> 2 * offset) == AD4170_GPIO_OUTPUT) + regmap_update_bits(st->regmap, AD4170_OUTPUT_DATA_REG, + BIT(offset), (value << offset)); +out: + mutex_unlock(&st->lock); +} + +static int ad4170_gpio_setup(struct ad4170_state *st) +{ + unsigned long valid_mask = 0x0; + int i = 0; + + st->gpiochip.owner = THIS_MODULE; + st->gpiochip.label = AD4170_NAME; + st->gpiochip.base = -1; + st->gpiochip.ngpio = 4; + st->gpiochip.parent = &st->spi->dev; + st->gpiochip.can_sleep = true; + st->gpiochip.direction_input = ad4170_input_gpio; + st->gpiochip.direction_output = ad4170_output_gpio; + st->gpiochip.get = ad4170_get_gpio; + st->gpiochip.set = ad4170_set_gpio; + + for (i = 0; i < 4; i++) + __assign_bit(i, &valid_mask, true); + + if (st->pdsw0) + __assign_bit(0, &valid_mask, false); + + if (st->pdsw1) + __assign_bit(1, &valid_mask, false); + + if (st->chop_adc == AD4170_CHOP_ACX_4PIN) + valid_mask = 0x0; + + if (st->chop_adc == AD4170_CHOP_ACX_2PIN) { + __assign_bit(2, &valid_mask, false); + __assign_bit(3, &valid_mask, false); + } + st->gpiochip.valid_mask = &valid_mask; + + return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, st); +} + +static int ad4170_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4170_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->spi = spi; + + indio_dev->name = AD4170_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad4170_info; + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4170_regmap_config); + + st->supplies[AD4170_AVDD_SUP].supply = "avdd"; + st->supplies[AD4170_AVSS_SUP].supply = "avss"; + st->supplies[AD4170_IOVDD_SUP].supply = "iovdd"; + st->supplies[AD4170_REFIN1P_SUP].supply = "refin1p"; + st->supplies[AD4170_REFIN1N_SUP].supply = "refin1n"; + st->supplies[AD4170_REFIN2P_SUP].supply = "refin2p"; + st->supplies[AD4170_REFIN2N_SUP].supply = "refin2n"; + + /* + * If a regulator is not available, it will be set to a dummy regulator. + * Each channel reference is checked with regulator_get_voltage() before + * setting attributes so if any channel uses a dummy supply the driver + * probe will fail. + */ + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->supplies), + st->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get supplies\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(st->supplies), st->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable supplies\n"); + + ret = devm_add_action_or_reset(dev, ad4170_disable_supplies, st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add supplies disable action\n"); + + ret = ad4170_soft_reset(st); + if (ret) + return ret; + + ret = ad4170_parse_fw(indio_dev); + if (ret) + return ret; + + ret = ad4170_setup(indio_dev); + if (ret) + return ret; + + ret = ad4170_gpio_setup(st); + if (ret) + return ret; + + st->spi_is_dma_mapped = spi_engine_ex_offload_supported(spi); + if (st->spi_is_dma_mapped) + ret = ad4170_hardware_buffer_setup(indio_dev); + else + ret = ad4170_triggered_buffer_setup(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup read buffer\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad4170_of_match[] = { + { + .compatible = "adi,ad4170", + }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4170_of_match); + +static struct spi_driver ad4170_driver = { + .driver = { + .name = AD4170_NAME, + .of_match_table = ad4170_of_match, + }, + .probe = ad4170_probe, +}; +module_spi_driver(ad4170_driver); + +MODULE_AUTHOR("Ana-Maria Cusco "); +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4170 SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad4170.h b/drivers/iio/adc/ad4170.h new file mode 100644 index 00000000000000..d6921f4597f7d1 --- /dev/null +++ b/drivers/iio/adc/ad4170.h @@ -0,0 +1,1025 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * AD4170 ADC driver + * + * Copyright 2023 Analog Devices Inc. + */ + +#include + +/* In development */ +#define AD4170_NAME "ad4170" +/* ADC Register Lengths*/ + +#define AD4170_READ_MASK BIT(14) +/* AD4170 registers */ +#define AD4170_INTERFACE_CONFIG_A_REG 0x00 +#define AD4170_INTERFACE_CONFIG_B_REG 0x01 +#define AD4170_DEVICE_CONFIG_REG 0x02 +#define AD4170_CHIP_TYPE_REG 0x03 +#define AD4170_PRODUCT_ID_L_REG 0x04 +#define AD4170_PRODUCT_ID_H_REG 0x05 +#define AD4170_CHIP_GRADE_REG 0x06 +#define AD4170_SCRATCH_PAD_REG 0x0a +#define AD4170_SPI_REVISION_REG 0x0b +#define AD4170_VENDOR_L_REG 0x0c +#define AD4170_VENDOR_H_REG 0x0d +#define AD4170_INTERFACE_CONFIG_C_REG 0x10 +#define AD4170_INTERFACE_STATUS_A_REG 0x11 +#define AD4170_STATUS_REG 0x14 +#define AD4170_DATA_16b_REG 0x16 +#define AD4170_DATA_16b_STATUS_REG 0x18 +#define AD4170_DATA_24b_REG 0x1c +#define AD4170_DATA_24b_STATUS_REG 0x20 +//#define AD4170_DATA_32b_REG 0x24 +#define AD4170_DATA_PER_CHAN_REG(x) (0x28 + 4 * (x)) +#define AD4170_PIN_MUXING_REG 0x68 +#define AD4170_CLOCK_CTRL_REG 0x6a +#define AD4170_STANDBY_CTRL_REG 0x6c +#define AD4170_POWER_DOWN_SW_REG 0x6e +#define AD4170_ADC_CTRL_REG 0x70 +#define AD4170_ERROR_EN_REG 0x72 +#define AD4170_ERROR_REG 0x74 +//#define AD4170_INFO1 0x76 /* TODO: implement this when it's specified in doc. */ +#define AD4170_CHANNEL_EN_REG 0x78 +#define AD4170_CHAN_SETUP_REG(x) (0x80 + 4 * (x)) +#define AD4170_CHAN_MAP_REG(x) (0x82 + 4 * (x)) +#define AD4170_MISC_REG(x) (0xc0 + 14 * (x)) +#define AD4170_AFE_REG(x) (0xc2 + 14 * (x)) +#define AD4170_FILTER_REG(x) (0xc4 + 14 * (x)) +#define AD4170_FILTER_FS_REG(x) (0xc6 + 14 * (x)) +#define AD4170_OFFSET_REG(x) (0xc8 + 14 * (x)) +#define AD4170_GAIN_REG(x) (0xcb + 14 * (x)) +#define AD4170_REF_CONTROL_REG 0x130 +#define AD4170_V_BIAS_REG 0x134 +#define AD4170_I_PULLUP_REG 0x136 +#define AD4170_CURRENT_SRC_REG(x) (0x138 + 2 * (x)) +#define AD4170_FIR_CONTROL_REG 0x140 +#define AD4170_COEFF_WRITE_DATA_REG 0x144 +#define AD4170_COEFF_READ_DATA_REG 0x147 +#define AD4170_COEFF_ADDRESS_REG 0x148 +#define AD4170_COEFF_WRRD_STB_REG 0x14d +#define AD4170_DAC_SPAN_REG 0x150 +#define AD4170_DAC_CHANNEL_EN_REG 0x152 +#define AD4170_DAC_HW_TOGGLE_MASK_REG 0x154 +#define AD4170_DAC_HW_LDAC_MASK_REG 0x156 +#define AD4170_DAC_DATA_REG(x) (0x158 + 2 * (x)) +#define AD4170_DAC_SW_TOGGLE_TRIGGERS_REG 0x168 +#define AD4170_DAC_SW_LDAC_TRIGGERS_REG 0x16a +#define AD4170_DAC_INPUTA_REG(x) (0x16c + 2 * (x)) +#define AD4170_DAC_INPUTB_REG(x) (0x17c + 2 * (x)) +#define AD4170_GPIO_MODE_REG 0x190 +#define AD4170_OUTPUT_DATA_REG 0x192 +#define AD4170_INPUT_DATA_REG 0x194 + +/* AD4170_REG_INTERFACE_CONFIG_A */ +#define AD4170_SW_RESET_MSK (BIT(7) | BIT(0)) +#define AD4170_RESET_SLEEP_US 1000 +#define AD4170_ADDR_ASCENSION_MSK BIT(5) +#define AD4170_SDO_ENABLE_MSK BIT(4) +#define AD4170_INT_REF_2_5V 2500000 + +/* AD4170_REG_INTERFACE_CONFIG_B */ +#define AD4170_INTERFACE_CONFIG_B_SINGLE_INST_MSK BIT(7) +#define AD4170_INTERFACE_CONFIG_B_SHORT_INSTRUCTION_MSK BIT(3) + +/* AD4170_REG_DATA_STATUS */ +#define AD4170_DATA_STATUS_MASTER_ERR_S_MSK BIT(7) +#define AD4170_DATA_STATUS_POR_FLAG_S_MSK BIT(6) +#define AD4170_DATA_STATUS_RDYB_MSK BIT(5) +#define AD4170_DATA_STATUS_SETTLED_FIR_MSK BIT(4) +#define AD4170_DATA_STATUS_CH_ACTIVE_MSK GENMASK(3, 0) + +/* AD4170_REG_PIN_MUXING */ +#define AD4170_PIN_MUXING_CHAN_TO_GPIO_MSK BIT(14) +#define AD4170_PIN_MUXING_DIG_AUX2_CTRL_MSK GENMASK(7, 6) +#define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK GENMASK(5, 4) +#define AD4170_PIN_MUXING_SYNC_CTRL_MSK GENMASK(3, 2) +#define AD4170_PIN_MUXING_DIG_OUT_STR_MSK BIT(1) +#define AD4170_PIN_MUXING_SDO_RDBY_DLY_MSK BIT(0) + +/* AD4170_REG_CLOCK_CTRL */ +#define AD4170_CLOCK_CTRL_DCLK_DIVIDE_MSK GENMASK(7, 6) +#define AD4170_CLOCK_CTRL_CLOCKDIV_MSK GENMASK(5, 4) +#define AD4170_CLOCK_CTRL_CLOCKSEL_MSK GENMASK(1, 0) +#define AD4170_INT_FREQ_16MHZ 16000000 +#define AD4170_EXT_FREQ_MHZ_MIN 1000000 +#define AD4170_EXT_FREQ_MHZ_MAX 17000000 + +/* AD4170_REG_STANDBY_CTRL */ +#define AD4170_STANDBY_CTRL_STB_EN_CLOCK_MSK BIT(8) +#define AD4170_STANDBY_CTRL_STB_EN_IPULLUP_MSK BIT(7) +#define AD4170_STANDBY_CTRL_STB_EN_DIAGNOSTICS_MSK BIT(6) +#define AD4170_STANDBY_CTRL_STB_EN_DAC_MSK BIT(5) +#define AD4170_STANDBY_CTRL_STB_EN_PDSW1_MSK BIT(4) +#define AD4170_STANDBY_CTRL_STB_EN_PDSW0_MSK BIT(3) +#define AD4170_STANDBY_CTRL_STB_EN_VBIAS_MSK BIT(2) +#define AD4170_STANDBY_CTRL_STB_EN_IEXC_MSK BIT(1) +#define AD4170_STANDBY_CTRL_STB_EN_REFERENCE_MSK BIT(0) + +/* AD4170_REG_POWER_DOWN_SW */ +#define AD4170_POWER_DOWN_SW_PDSW1_MSK BIT(1) +#define AD4170_POWER_DOWN_SW_PDSW0_MSK BIT(0) + +/* AD4170_REG_ADC_CTRL */ +#define AD4170_ADC_CTRL_PARALLEL_FILT_EN_MSK BIT(8) +#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7) +#define AD4170_ADC_CTRL_CONT_READ_STATUS_EN_MSK BIT(6) +#define AD4170_REG_CTRL_CONT_READ_MSK GENMASK(5, 4) +#define AD4170_REG_CTRL_MODE_MSK GENMASK(3, 0) + +/* AD4170_REG_ERROR_EN and AD4170_REG_ERROR */ +#define AD4170_ERROR_DEVICE_ERROR_MSK BIT(15) +#define AD4170_ERROR_DLDO_PSM_ERR_MSK BIT(13) +#define AD4170_ERROR_ALDO_PSM_ERR_MSK BIT(12) +#define AD4170_ERROR_IOUT3_COMP_ERR_MSK BIT(11) +#define AD4170_ERROR_IOUT2_COMP_ERR_MSK BIT(10) +#define AD4170_ERROR_IOUT1_COMP_ERR_MSK BIT(9) +#define AD4170_ERROR_IOUT0_COMP_ERR_MSK BIT(8) +#define AD4170_ERROR_REF_DIFF_MIN_ERR_MSK BIT(7) +#define AD4170_ERROR_REF_OV_UV_ERR_MSK BIT(6) +#define AD4170_ERROR_AINM_OV_UV_ERR_MSK BIT(5) +#define AD4170_ERROR_AINP_OV_UV_ERR_MSK BIT(4) +#define AD4170_ERROR_ADC_CONV_ERR_MSK BIT(3) +#define AD4170_ERROR_MM_CRC_ERR_MSK BIT(1) +#define AD4170_ERROR_ROM_CRC_ERR_MSK BIT(0) + +/* AD4170_REG_CHANNEL_EN */ +#define AD4170_CHANNEL_EN(ch) BIT(ch) + +/* AD4170_REG_ADC_CHANNEL_SETUP */ +#define AD4170_CHANNEL_SETUPN_REPEAT_N_MSK GENMASK(15, 8) +#define AD4170_CHANNEL_SETUPN_DELAY_N_MSK GENMASK(6, 4) +#define AD4170_CHANNEL_SETUPN_SETUP_N_MSK GENMASK(2, 0) + +/* AD4170_REG_ADC_CHANNEL_MAP */ +#define AD4170_CHANNEL_MAPN_AINP_MSK GENMASK(12, 8) +#define AD4170_CHANNEL_MAPN_AINM_MSK GENMASK(4, 0) + +/* AD4170_REG_ADC_SETUPS_MISC */ +#define AD4170_SETUPS_MISC_CHOP_IEXC_MSK GENMASK(15, 14) +#define AD4170_SETUPS_MISC_CHOP_ADC_MSK GENMASK(9, 8) +#define AD4170_SETUPS_MISC_BURNOUT_MSK GENMASK(1, 0) + +/* AD4170_REG_ADC_SETUPS_AFE */ +#define AD4170_SETUPS_AFE_REF_BUF_M_MSK GENMASK(11, 10) +#define AD4170_SETUPS_AFE_REF_BUF_P_MSK GENMASK(9, 8) +#define AD4170_SETUPS_AFE_REF_SELECT_MSK GENMASK(6, 5) +#define AD4170_SETUPS_AFE_BIPOLAR_MSK BIT(4) +#define AD4170_SETUPS_AFE_PGA_GAIN_MSK GENMASK(3, 0) + +/* AD4170_REG_ADC_SETUPS_FILTER */ +#define AD4170_SETUPS_POST_FILTER_SEL_MSK GENMASK(7, 4) +#define AD4170_SETUPS_FILTER_TYPE_MSK GENMASK(3, 0) + +/* AD4170 REG_OFFSET*/ +#define AD4170_OFFSET_MSK GENMASK(23, 0) + +/* AD4170 REG_GAIN*/ +#define AD4170_GAIN_MSK GENMASK(23, 0) + +/* AD4170_REG_CURRENT_SOURCE */ +#define AD4170_CURRENT_SOURCE_I_OUT_PIN_MSK GENMASK(12, 8) +#define AD4170_CURRENT_SOURCE_I_OUT_VAL_MSK GENMASK(2, 0) + +/* AD4170_REG_REF_CONTROL */ +#define AD4170_REF_CONTROL_REF_EN_MSK BIT(0) + +/* AD4170_REG_FIR_CONTROL */ +#define AD4170_FIR_CONTROL_IIR_MODE_MSK BIT(15) +#define AD4170_FIR_CONTROL_FIR_MODE_MSK GENMASK(14, 12) +#define AD4170_FIR_CONTROL_COEFF_SET_MSK BIT(10) +#define AD4170_FIR_CONTROL_FIR_LENGTH_MSK GENMASK(6, 0) + +/* AD4170_REG_DAC_SPAN */ +#define AD4170_REG_DAC_SPAN_DAC_GAIN_MSK BIT(0) + +/* AD4170_REG_DAC_CHANNEL_EN */ +#define AD4170_REG_DAC_CHANNEL_EN_DAC_EN_MSK BIT(0) + +/* AD4170_REG_DAC_HW_TOGGLE_MASK */ +#define AD4170_REG_DAC_HW_TOGGLE_MASK_HW_TOGGLE_EN_MSK BIT(0) + +/* AD4170_REG_DAC_HW_LDAC_MASK */ +#define AD4170_REG_DAC_HW_LDAC_MASK_HW_LDAC_EN_MSK BIT(0) + +/* AD4170_REG_DAC_DATA */ +#define AD4170_REG_DAC_DATA_MSK GENMASK(11, 0) + +/* AD4170_REG_DAC_SW_TOGGLE_TRIGGERS */ +#define AD4170_REG_DAC_SW_TOGGLE_TRIGGERS_SW_TOGGLE_MSK BIT(0) + +/* AD4170_REG_DAC_SW_LDAC_TRIGGERS */ +#define AD4170_REG_DAC_SW_LDAC_TRIGGERS_SW_LDAC_EN_MSK BIT(0) + +#define AD4170_GPIO_MODE_REG_MSK GENMASK(1, 0) + +#define AD4170_NUM_CHANNELS 16 +#define AD4170_CHANNEL_INDEX_MAX 8 +#define AD4170_MAX_ANALOG_PINS 8 +#define AD4170_NUM_SETUPS 8 +#define AD4170_NUM_CURRENT_SOURCE 4 +#define AD4170_FIR_COEFF_MAX_LENGTH 72 +#define AD4170_MAX_DIFF_INPUTS 4 +#define AD4170_MAX_SAMP_RATE 125000 +#define AD4170_INVALID_SLOT -1 +#define AD4170_NUM_ANALOG_PINS 9 +#define AD4170_DEFAULT_ADC_GAIN_COEF 0x555555 + +#define AD4170_DIG_AUX1_DISABLED 0 +#define AD4170_DIG_AUX1_RDY 1 +#define AD4170_DIG_AUX1_SYNC 2 + +#define AD4170_DIG_AUX2_DISABLED 0 +#define AD4170_DIG_AUX2_LDAC 1 +#define AD4170_DIG_AUX2_SYNC 2 + +#define AD4170_SYNC_DISABLED 0 +#define AD4170_SYNC_STANDARD 1 +#define AD4170_SYNC_ALTERNATE 2 + +enum ad4170_pin_function { + AD4170_PIN_UNASIGNED, + AD4170_PIN_ANALOG_IN, + AD4170_PIN_CURRENT_OUT +}; + +enum ad4170_gpio_mode { + AD4170_GPIO_DISABLED, + AD4170_GPIO_INPUT, + AD4170_GPIO_OUTPUT +}; + +/** + * @enum ad4170_chan_to_gpio + * @brief Enables Current Channel Number Be Output to GPIO Pins. + */ +enum ad4170_chan_to_gpio { + /** Active Channel is Not Output to GPIO Pins. */ + AD4170_CHANNEL_NOT_TO_GPIO, + /** Active Channel is Output to GPIO Pins. */ + AD4170_CHANNEL_TO_GPIO +}; + +/** + * @enum ad4170_dig_out_str + * @brief Configures the drive strength of the Digital Outputs. + */ +enum ad4170_dig_out_str { + /** Default Drive Strength. Recommended for higher IOVDD voltages. */ + AD4170_DIG_STR_DEFAULT, + /** Increased Drive Strength. */ + AD4170_DIG_STR_HIGH +}; + +/** + * @enum ad4170_sdo_rdby_dly + * @brief Reset Interface on CS or SCLK. + */ +enum ad4170_sdo_rdby_dly { + /** Reset on Last SCLK. */ + AD4170_SDO_RDY_SCLK, + /** Reset on /CS Edge. */ + AD4170_SDO_RDY_CSB +}; + +/** + * @struct ad4170_pin_muxing + * @brief Pin_Muxing register settings. + */ +struct ad4170_pin_muxing { + /** Configures Functionality of DIG_AUX2 Pin. */ + u8 dig_aux2_ctrl; + /** Configures Functionality of DIG_AUX1 Pin. */ + u8 dig_aux1_ctrl; + /** Configures SYNC_IN Pin for ADC Synchronization. */ + u8 sync_ctrl; +}; + +/** + * @enum ad4170_dclk_div + * @brief Continuous Transmit Data Clock Divider. + */ +enum ad4170_dclk_div { + /** DCLK Equals Master Clock Divide by 1. */ + AD4170_DCLKDIVBY1, + /** DCLK Equals Master Clock Divide by 2. */ + AD4170_DCLKDIVBY2, + /** DCLK Equals Master Clock Divide by 4. */ + AD4170_DCLKDIVBY4, + /** DCLK Equals Master Clock Divide by 8. */ + AD4170_DCLKDIVBY8 +}; + +/** + * @enum ad4170_mclk_div + * @brief Master Clock Divider. + */ +enum ad4170_mclk_div { + /** Divide by 1. */ + AD4170_CLKDIVBY1, + /** Divide by 2. */ + AD4170_CLKDIVBY2, + /** Divide by 4. */ + AD4170_CLKDIVBY4, + /** Divide by 8. */ + AD4170_CLKDIVBY8 +}; + +/** + * @enum ad4170_clocksel + * @brief ADC Clock Select. + */ +enum ad4170_clocksel { + /** Internal Oscillator. */ + AD4170_INTERNAL_OSC, + /** Internal Oscillator, Output to XTAL2/CLKIO Pin. */ + AD4170_INTERNAL_OSC_OUTPUT, + /** External Clock Input on XTAL2/CLKIO Pin. */ + AD4170_EXTERNAL_OSC, + /** External Crystal on XTAL1 and XTAL2/CLKIO Pins. */ + AD4170_EXTERNAL_XTAL +}; + +/** + * @struct ad4170_clock_ctrl + * @brief Clock_Ctrl register settings. + */ +struct ad4170_clock_ctrl { + /** Continuous Transmit Data Clock Divider. */ + enum ad4170_dclk_div dclk_divide; + /** Master Clock Divider. */ + enum ad4170_mclk_div clockdiv; + /** ADC Clock Select. */ + enum ad4170_clocksel clocksel; +}; + +/** + * @enum ad4170_cont_read + * @brief Configures continuous Data Register Read/Transmit. + */ +enum ad4170_cont_read { + /* Disable Continuous Read/Transmit. */ + AD4170_CONT_READ_OFF, + /* + * Enable Continuous Read. + * This enables continuous read of the ADC Data register over SPI. + */ + AD4170_CONT_READ_ON, + /* + * Enable Continuous Transmit. + * This enables continuous transmit of the ADC Data register over TDM. + */ + AD4170_CONT_TRANSMIT_ON +}; + +/** + * @enum ad4170_mode + * @brief ADC Operating Mode. + */ +enum ad4170_mode { + /* + * Continuous Conversion Mode. + * ADC converts continuously on the enabled channel(s) using Sinc-based + * filters. + */ + AD4170_MODE_CONT, + /* + * Continuous Conversion Mode with FIR filter. + * ADC converts continuously on one channel using the FIR Filter. + */ + AD4170_MODE_CONT_FIR, + /* Continuous Conversion Mode with IIR filter. + * ADC converts continuously on one channel using the IIR Filter. + */ + AD4170_MODE_CONT_IIR, + /* Single conversion mode. + * ADC performs a single conversion (possibly repeated) on each enabled + * channel(s) using Sinc based filters. + */ + AD4170_MODE_SINGLE = 0x4, + /* Standby Mode. Part enters Standby Mode. */ + AD4170_MODE_STANDBY, + /* Power-Down Mode. + * All blocks are disabled in Power-Down Mode, including the LDO + * regulators and the serial interface. + */ + AD4170_MODE_POWER_DOWN, + /* Idle Mode. ADC enters idle mode, part remains powered on. */ + AD4170_MODE_IDLE, + /* System Offset Calibration Mode. */ + AD4170_MODE_SYS_OFFSET_CAL, + /* System Gain Calibration Mode. */ + AD4170_MODE_SYS_GAIN_CAL, + /* Self Offset Calibration Mode. */ + AD4170_MODE_SELF_OFFSET_CAL, + /* Self Gain Calibration Mode. */ + AD4170_MODE_SELF_GAIN_CAL +}; + +/** + * @struct ad4170_adc_ctrl + * @brief ADC_Ctrl register settings. + */ +struct ad4170_adc_ctrl { + /** Enables Multiple Channels to Be Converted in Parallel. */ + bool parallel_filt_en; + /** Selects Between One or Multiple Data Registers. */ + bool multi_data_reg_sel; + /** Enables Status Output in Continuous Read/Transmit. */ + bool cont_read_status_en; + /** Continuous Data Register Read/Transmit Enable. */ + enum ad4170_cont_read cont_read; + /** ADC Operating Mode. */ + enum ad4170_mode mode; +}; + +/** + * @enum ad4170_delay_n + * @brief Delay to Add After Channel Switch. + */ +enum ad4170_delay_n { + /** 0 Delay. */ + AD4170_DLY_0, + /** Delay 16 * Mod_Clk. */ + AD4170_DLY_16, + /** Delay 256 * Mod_Clk. */ + AD4170_DLY_256, + /** Delay 1024 * Mod_Clk. */ + AD4170_DLY_1024, + /** Delay 2048 * Mod_Clk. */ + AD4170_DLY_2048, + /** Delay 4096 * Mod_Clk. */ + AD4170_DLY_4096, + /** Delay 8192 * Mod_Clk. */ + AD4170_DLY_8192, + /** Delay 16384 * Mod_Clk. */ + AD4170_DLY_16384 +}; + +/** + * @enum ad4170_ain + * @brief Multiplexer Positive/Negative Input for This Channel. + */ +enum ad4170_ain { + AD4170_AIN0 = AD4170_MAP_AIN0, + AD4170_AIN1 = AD4170_MAP_AIN1, + AD4170_AIN2 = AD4170_MAP_AIN2, + AD4170_AIN3 = AD4170_MAP_AIN3, + AD4170_AIN4 = AD4170_MAP_AIN4, + AD4170_AIN5 = AD4170_MAP_AIN5, + AD4170_AIN6 = AD4170_MAP_AIN6, + AD4170_AIN7 = AD4170_MAP_AIN7, + AD4170_AIN8 = AD4170_MAP_AIN8, + AD4170_TEMP_SENSOR_P = AD4170_MAP_TEMP_SENSOR_P, + AD4170_TEMP_SENSOR_N = AD4170_MAP_TEMP_SENSOR_N, + AD4170_AVDD_AVSS_P = AD4170_MAP_AVDD_AVSS_P, + AD4170_AVDD_AVSS_N = AD4170_MAP_AVDD_AVSS_N, + AD4170_IOVDD_DGND_P = AD4170_MAP_IOVDD_DGND_P, + AD4170_IOVDD_DGND_N = AD4170_MAP_IOVDD_DGND_N, + AD4170_DAC_P = AD4170_MAP_DAC_P, + AD4170_DAC_N = AD4170_MAP_DAC_N, + AD4170_ALDO = AD4170_MAP_ALDO, + AD4170_DLDO = AD4170_MAP_DLDO, + AD4170_AVSS = AD4170_MAP_AVSS, + AD4170_DGND = AD4170_MAP_DGND, + AD4170_REFIN1_P = AD4170_MAP_REFIN1_P, + AD4170_REFIN1_N = AD4170_MAP_REFIN1_N, + AD4170_REFIN2_P = AD4170_MAP_REFIN2_P, + AD4170_REFIN2_N = AD4170_MAP_REFIN2_N, + AD4170_REFOUT = AD4170_MAP_REFOUT, +}; + +/** + * @struct ad4170_channel_setup + * @brief Channel_Setup register settings. + */ +struct ad4170_channel_setup { + /** Number of Times to Repeat This Channel. */ + u8 repeat_n; + /** Delay to Add After Channel Switch. */ + enum ad4170_delay_n delay_n; + /* Setup to Use for This Channel. + * A "Setup" includes the configuration for the AFE and the Digital + * Filter. + */ + u8 setup_n; +}; + +/** + * @enum ad4170_chop_iexc + * @brief Excitation Current Chopping Control. + */ +enum ad4170_chop_iexc { + /* No Chopping of Excitation Currents. */ + AD4170_CHOP_IEXC_OFF = AD4170_MISC_CHOP_IEXC_OFF, + /* Chopping of Iout_A and Iout_B Excitation Currents. */ + AD4170_CHOP_IEXC_AB = AD4170_MISC_CHOP_IEXC_AB, + /* Chopping of Iout_C and Iout_D Excitation Currents. */ + AD4170_CHOP_IEXC_CD = AD4170_MISC_CHOP_IEXC_CD, + /* Chopping of Both Pairs of Excitation Currents. */ + AD4170_CHOP_IEXC_ABCD = AD4170_MISC_CHOP_IEXC_ABCD, + /* Enum max value */ + AD4170_IEXC_CHOP_MAX +}; + +/** + * @enum ad4170_chop_adc + * @brief ADC/Mux Chopping Control. + */ +enum ad4170_chop_adc { + /** No Chopping. */ + AD4170_CHOP_OFF = AD4170_MISC_CHOP_ADC_OFF, + /** Chops Internal Mux. */ + AD4170_CHOP_MUX = AD4170_MISC_CHOP_ADC_MUX, + /** Chops AC Excitation Using 4 GPIO Pins. */ + AD4170_CHOP_ACX_4PIN = AD4170_MISC_CHOP_ADC_ACX_4PIN, + /** Chops AC Excitation Using 2 GPIO Pins. */ + AD4170_CHOP_ACX_2PIN = AD4170_MISC_CHOP_ADC_ACX_2PIN +}; + +/** + * @enum ad4170_burnout + * @brief Burnout Current Values. + */ +enum ad4170_burnout { + AD4170_BURNOUT_OFF, + AD4170_BURNOUT_100NA, + AD4170_BURNOUT_2000NA, + AD4170_BURNOUT_10000NA, + AD4170_BURNOUT_MAX +}; + +/** + * @struct ad4170_misc + * @brief Misc register settings. + */ +struct ad4170_misc { + /** Excitation Current Chopping Control. */ + enum ad4170_chop_iexc chop_iexc; + /** ADC/Mux Chopping Control. */ + enum ad4170_chop_adc chop_adc; + /** Burnout Current Values. */ + enum ad4170_burnout burnout; +}; + +/** + * @enum ad4170_ref_buf + * @brief REFIN Buffer Enable. + */ +enum ad4170_ref_buf { + /** Pre-charge Buffer. */ + AD4170_REF_BUF_PRE, + /** Full Buffer.*/ + AD4170_REF_BUF_FULL, + /** Bypass */ + AD4170_REF_BUF_BYPASS +}; + +enum ad4170_regulator { + AD4170_AVDD_SUP, + AD4170_AVSS_SUP, + AD4170_IOVDD_SUP, + AD4170_REFIN1P_SUP, + AD4170_REFIN1N_SUP, + AD4170_REFIN2P_SUP, + AD4170_REFIN2N_SUP, +}; + +/** + * @enum ad4170_ref_select + * @brief ADC Reference Selection. + */ +enum ad4170_ref_select { + AD4170_REFIN_REFIN1 = AD4170_AFE_REFIN_REFIN1, + AD4170_REFIN_REFIN2 = AD4170_AFE_REFIN_REFIN2, + AD4170_REFIN_REFOUT = AD4170_AFE_REFIN_REFOUT, + AD4170_REFIN_AVDD = AD4170_AFE_REFIN_AVDD, + AD4170_REFIN_MAX +}; + +/** + * @enum ad4170_pga_gain + * @brief PGA Gain Selection. + */ +enum ad4170_pga_gain { + /** PGA Gain = 1 */ + AD4170_PGA_GAIN_1, + /** PGA Gain = 2 */ + AD4170_PGA_GAIN_2, + /** PGA Gain = 4 */ + AD4170_PGA_GAIN_4, + /** PGA Gain = 8 */ + AD4170_PGA_GAIN_8, + /** PGA Gain = 16 */ + AD4170_PGA_GAIN_16, + /** PGA Gain = 32 */ + AD4170_PGA_GAIN_32, + /** PGA Gain = 64 */ + AD4170_PGA_GAIN_64, + /** PGA Gain = 128 */ + AD4170_PGA_GAIN_128, + /** PGA Gain = 0.5 */ + AD4170_PGA_GAIN_0P5, + /* + * PGA Gain = 1 Pre-charge Buffer. + * Input currents may increase when the pre-charge-buffer is used. + */ + AD4170_PGA_GAIN_1_PRECHARGE, + AD4170_PGA_GAIN_MAX +}; + +/** + * @struct ad4170_afe + * @brief AFE register settings. + */ +struct ad4170_afe { + /* REFIN Buffer- Enable. */ + enum ad4170_ref_buf ref_buf_m; + /* REFIN Buffer+ Enable. */ + enum ad4170_ref_buf ref_buf_p; + /* ADC Reference Selection. */ + enum ad4170_ref_select ref_select; + /* Select Bipolar or Unipolar ADC Span. */ + bool bipolar; + /* PGA Gain Selection. */ + enum ad4170_pga_gain pga_gain; +}; + +/** + * @enum ad4170_post_filter + * @brief Optional Post-Filter configuration. + */ +enum ad4170_post_filter { + /* No Post Filter. */ + AD4170_POST_FILTER_NONE, + /* Post Filter for 50/60Hz Rejection with 40ms Settling. */ + AD4170_POST_FILTER_40MS, + /* Post Filter for 50/60Hz Rejection with 50ms Settling. */ + AD4170_POST_FILTER_50MS, + /* Post Filter for 50/60Hz Rejection with 60ms Settling. */ + AD4170_POST_FILTER_60MS, + /* Post Filter for AC Excitation with 5ms Settling. */ + AD4170_POST_FILTER_FAST_AC, + /* Post Filter for Average-By-16. */ + AD4170_POST_FILTER_AVG16, + /* Post Filter for 60Hz Rejection with 16.7ms Settling. */ + AD4170_POST_FILTER_AVG20, + /* Post Filter for 50Hz Rejection with 20ms Settling. */ + AD4170_POST_FILTER_AVG24 +}; + +/** + * @enum ad4170_filter_type + * @brief Filter Mode for Sinc-Based Filters. + */ +enum ad4170_filter_type { + /** Sinc5 Plus Average. */ + AD4170_FILT_SINC5_AVG = 0x0, + /** Sinc5 */ + AD4170_FILT_SINC5 = 0x4, + /** Sinc3 */ + AD4170_FILT_SINC3 = 0x6, +}; + +/** + * @struct ad4170_filter + * @brief Filter register settings. + */ +struct ad4170_filter { + /* Optional Post-Filter configuration. */ + enum ad4170_post_filter post_filter_sel; + /* Filter Mode for Sinc-Based Filters. */ + enum ad4170_filter_type filter_type; +}; + +/** + * @struct ad4170_setup + * @brief Sequencer Setup register settings. + */ +struct ad4170_setup { + struct ad4170_misc misc; + /* Configures Analog Front End - PGA, Reference, Buffers. */ + struct ad4170_afe afe; + /* Selects Digital Filter Type. */ + struct ad4170_filter filter; + /* Filter select word for Digital Filters. */ + unsigned int filter_fs; + /* Digital Offset Adjustment Value. */ + u32 offset; + /* Digital Gain Adjustment Value. */ + u32 gain; +}; + +/** + * @struct ad4170_ref_control + * @brief Ref_Control register settings. + */ +struct ad4170_ref_control { + /** Internal Reference Enable. */ + bool ref_en; +}; + +/** + * @enum ad4170_i_out_pin + * @brief Current Source Destination. + */ +enum ad4170_i_out_pin { + /* I_OUT is Available on AIN0. */ + AD4170_I_OUT_AIN0, + /* I_OUT is Available on AIN1. */ + AD4170_I_OUT_AIN1, + /* I_OUT is Available on AIN2. */ + AD4170_I_OUT_AIN2, + /* I_OUT is Available on AIN3. */ + AD4170_I_OUT_AIN3, + /* I_OUT is Available on AIN4. */ + AD4170_I_OUT_AIN4, + /* I_OUT is Available on AIN5. */ + AD4170_I_OUT_AIN5, + /* I_OUT is Available on AIN6. */ + AD4170_I_OUT_AIN6, + /* I_OUT is Available on AIN7. */ + AD4170_I_OUT_AIN7, + /* I_OUT is Available on AIN8. */ + AD4170_I_OUT_AIN8, + /* I_OUT is Available on GPIO0. */ + AD4170_I_OUT_GPIO0, + /* I_OUT is Available on GPIO1. */ + AD4170_I_OUT_GPIO1, + /* I_OUT is Available on GPIO2. */ + AD4170_I_OUT_GPIO2, + /* I_OUT is Available on GPIO3. */ + AD4170_I_OUT_GPIO3, + /* */ + AD4170_I_OUT_PIN_MAX +}; + +/** + * @enum ad4170_i_out_val + * @brief Current Source Value. + */ +enum ad4170_i_out_val { + AD4170_I_OUT_0UA, + AD4170_I_OUT_10UA, + AD4170_I_OUT_50UA, + AD4170_I_OUT_100UA, + AD4170_I_OUT_250UA, + AD4170_I_OUT_500UA, + AD4170_I_OUT_1000UA, + AD4170_I_OUT_1500UA, + AD4170_I_OUT_MAX +}; + +/** + * @struct ad4170_current_source + * @brief Current_Source register settings. + */ +struct ad4170_current_source { + enum ad4170_i_out_pin i_out_pin; + enum ad4170_i_out_val i_out_val; +}; + +/** + * @enum ad4170_fir_mode + * @brief Selects FIR Type. + */ +enum ad4170_fir_mode { + /* + * FIR Default. + * Selects the default FIR filter and ignores any programmed FIR_Length + * and FIR Coefficient values. + */ + AD4170_FIR_DEFAULT, + /** FIR Programmable with Odd Symmetric Coefficients. */ + AD4170_FIR_SYM_ODD, + /** FIR Programmable with Even Symmetric Coefficients. */ + AD4170_FIR_SYM_EVEN, + /** FIR Programmable with Odd Anti-Symmetric Coefficients. */ + AD4170_FIR_ANTISYM_ODD, + /** FIR Programmable with Even Anti-Symmetric Coefficients. */ + AD4170_FIR_ANTISYM_EVEN, + /** FIR Programmable with Asymmetric Coefficients. */ + AD4170_FIR_ASYM +}; + +/** + * @enum ad4170_fir_coefficient set + * @brief Selects FIR coefficient set. + */ +enum ad4170_fir_coeff_set { + /* FIR set 0. Use coefficient addresses 0 to FIR_LENGTH-1 */ + AD4170_FIR_COEFF_SET0, + /* FIR set 1. Use coefficient addresses 72 to 72 + FIR_LENGTH-1 */ + AD4170_FIR_COEFF_SET1 +}; + +/** + * @enum ad4170_fir_control + * @brief FIR_Control register settings. + */ +struct ad4170_fir_control { + /** Selects FIR Type. */ + enum ad4170_fir_mode fir_mode; + /** Selects Which Set of FIR Coefficients to Use. */ + enum ad4170_fir_coeff_set coeff_set; + /** Number of Programmed Coefficients, Max: 72. */ + u8 fir_length; + /** Pointer to array holding the FIR coefficients */ + s32 *fir_coefficients; +}; + +/** + * @enum ad4170_dac_gain + * @brief DAC Output Span. + */ +enum ad4170_dac_gain { + /** DAC Output Range is 0V to REFOUT. */ + AD4170_DAC_GAIN_1, + /** DAC Output Range is 0V to 2*REFOUT. */ + AD4170_DAC_GAIN_2 +}; + +/** + * @struct ad4170_dac_config + * @brief DAC Config settings (registers HW_LDAC_Mask, HW_Toggle_Mask, Channel_En and DAC_Span) + */ +struct ad4170_dac_config { + /** Selects DAC0 Enabled/Disabled. */ + bool enabled; + /** Select DAC0 Gain. */ + enum ad4170_dac_gain gain; + /** Select DAC0 HW Toggle. */ + bool hw_toggle; + /** Select DAC0 LDAC Toggle. */ + bool hw_ldac; +}; + +/** + * @struct ad4170_config + * @brief AD4170 configuration. + */ +struct ad4170_config { + /** Pin_Muxing register settings. */ + struct ad4170_pin_muxing pin_muxing; + /** Clock_Ctrl register settings. */ + struct ad4170_clock_ctrl clock_ctrl; + /** Standby_Ctrl register settings. */ + u16 standby_ctrl; + /** Power_Down_Sw register settings. */ + u16 powerdown_sw; + /** Error_En register settings. */ + u16 error_en; + /** ADC_Ctrl register settings. */ + struct ad4170_adc_ctrl adc_ctrl; + /** Channel_En register settings. */ + u16 channel_en; + /** Channel_Setup register settings. */ + struct ad4170_channel_setup ch_setup[AD4170_NUM_CHANNELS]; + /** Setups settings. */ + struct ad4170_setup setups[AD4170_NUM_SETUPS]; + /** Ref_Control register settings. */ + struct ad4170_ref_control ref_control; + /** V_Bias register settings. */ + u16 v_bias; + /** I_Pullup register settings. */ + u16 i_pullup; + /** Current_Source register settings */ + struct ad4170_current_source current_src[AD4170_NUM_CURRENT_SOURCE]; + /** FIR_Control register settings. */ + struct ad4170_fir_control fir_control; + /** DAC settings (registers HW_LDAC_Mask, HW_Toggle_Mask, Channel_En and DAC_Span). */ + struct ad4170_dac_config dac; +}; + +/** + * @struct ad4170_spi_settings + * @brief AD4170 SPI settings. + */ +struct ad4170_spi_settings { + /** Select short instruction, 7-bit Addressing, instead of the default 15-bit Addressing. */ + bool short_instruction; + /** Enable communication CRC generation and checking. */ + bool crc_enabled; + /** Enable sync pattern loss detection and recovery. */ + bool sync_loss_detect; +}; + +static const unsigned int ad4170_reg_size[] = { + [AD4170_INTERFACE_CONFIG_A_REG] = 1, + [AD4170_INTERFACE_CONFIG_B_REG] = 1, + [AD4170_DEVICE_CONFIG_REG] = 1, + [AD4170_CHIP_TYPE_REG] = 1, + [AD4170_PRODUCT_ID_L_REG] = 1, + [AD4170_PRODUCT_ID_H_REG] = 1, + [AD4170_CHIP_GRADE_REG] = 1, + [AD4170_SCRATCH_PAD_REG] = 1, + [AD4170_SPI_REVISION_REG] = 1, + [AD4170_VENDOR_L_REG] = 1, + [AD4170_VENDOR_H_REG] = 1, + [AD4170_INTERFACE_CONFIG_C_REG] = 1, + [AD4170_INTERFACE_STATUS_A_REG] = 1, + [AD4170_STATUS_REG] = 2, + [AD4170_DATA_16b_REG] = 2, + [AD4170_DATA_16b_STATUS_REG] = 3, + [AD4170_DATA_24b_REG] = 3, + [AD4170_DATA_24b_STATUS_REG] = 4, + [AD4170_DATA_PER_CHAN_REG(0) ... AD4170_DATA_PER_CHAN_REG(AD4170_NUM_CHANNELS - 1)] = 3, + [AD4170_PIN_MUXING_REG] = 2, + [AD4170_CLOCK_CTRL_REG] = 2, + [AD4170_STANDBY_CTRL_REG] = 2, + [AD4170_POWER_DOWN_SW_REG] = 2, + [AD4170_ADC_CTRL_REG] = 2, + [AD4170_ERROR_EN_REG] = 2, + [AD4170_ERROR_REG] = 2, + [AD4170_CHANNEL_EN_REG] = 2, + /* + * CHANNEL_SETUP and CHANNEL_MAP register are all 2 byte size each and + * their addresses are interleaved such that we have CHANNEL_SETUP0 + * address followed by CHANNEL_MAP0 address, followed by CHANNEL_SETUP1, + * and so on until CHANNEL_MAP15. + * Thus, initialize the register size for them only once. + */ + [AD4170_CHAN_SETUP_REG(0) ... AD4170_CHAN_MAP_REG(AD4170_NUM_CHANNELS - 1)] = 2, + /* + * MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN register addresses are + * also interleaved but MISC, AFE, FILTER, FILTER_FS, OFFSET are 16-bit + * while OFFSET, GAIN are 24-bit registers so we can't init them all to + * the same size. + */ + /* Init MISC register size */ + [AD4170_MISC_REG(0)] = 2, + [AD4170_MISC_REG(1)] = 2, + [AD4170_MISC_REG(2)] = 2, + [AD4170_MISC_REG(3)] = 2, + [AD4170_MISC_REG(4)] = 2, + [AD4170_MISC_REG(5)] = 2, + [AD4170_MISC_REG(6)] = 2, + [AD4170_MISC_REG(7)] = 2, + /* Init AFE register size */ + [AD4170_AFE_REG(0)] = 2, + [AD4170_AFE_REG(1)] = 2, + [AD4170_AFE_REG(2)] = 2, + [AD4170_AFE_REG(3)] = 2, + [AD4170_AFE_REG(4)] = 2, + [AD4170_AFE_REG(5)] = 2, + [AD4170_AFE_REG(6)] = 2, + [AD4170_AFE_REG(7)] = 2, + /* Init FILTER register size */ + [AD4170_FILTER_REG(0)] = 2, + [AD4170_FILTER_REG(1)] = 2, + [AD4170_FILTER_REG(2)] = 2, + [AD4170_FILTER_REG(3)] = 2, + [AD4170_FILTER_REG(4)] = 2, + [AD4170_FILTER_REG(5)] = 2, + [AD4170_FILTER_REG(6)] = 2, + [AD4170_FILTER_REG(7)] = 2, + /* Init FILTER_FS register size */ + [AD4170_FILTER_FS_REG(0)] = 2, + [AD4170_FILTER_FS_REG(1)] = 2, + [AD4170_FILTER_FS_REG(2)] = 2, + [AD4170_FILTER_FS_REG(3)] = 2, + [AD4170_FILTER_FS_REG(4)] = 2, + [AD4170_FILTER_FS_REG(5)] = 2, + [AD4170_FILTER_FS_REG(6)] = 2, + [AD4170_FILTER_FS_REG(7)] = 2, + /* Init OFFSET register size */ + [AD4170_OFFSET_REG(0)] = 3, + [AD4170_OFFSET_REG(1)] = 3, + [AD4170_OFFSET_REG(2)] = 3, + [AD4170_OFFSET_REG(3)] = 3, + [AD4170_OFFSET_REG(4)] = 3, + [AD4170_OFFSET_REG(5)] = 3, + [AD4170_OFFSET_REG(6)] = 3, + [AD4170_OFFSET_REG(7)] = 3, + /* Init GAIN register size */ + [AD4170_GAIN_REG(0)] = 3, + [AD4170_GAIN_REG(1)] = 3, + [AD4170_GAIN_REG(2)] = 3, + [AD4170_GAIN_REG(3)] = 3, + [AD4170_GAIN_REG(4)] = 3, + [AD4170_GAIN_REG(5)] = 3, + [AD4170_GAIN_REG(6)] = 3, + [AD4170_GAIN_REG(7)] = 3, + [AD4170_REF_CONTROL_REG] = 2, + [AD4170_V_BIAS_REG] = 2, + [AD4170_I_PULLUP_REG] = 2, + [AD4170_CURRENT_SRC_REG(0) ... AD4170_CURRENT_SRC_REG(AD4170_NUM_CURRENT_SOURCE - 1)] = 2, + [AD4170_FIR_CONTROL_REG] = 2, + [AD4170_COEFF_WRITE_DATA_REG] = 4, + [AD4170_COEFF_READ_DATA_REG] = 4, + [AD4170_COEFF_ADDRESS_REG] = 2, + [AD4170_COEFF_WRRD_STB_REG] = 2, + [AD4170_DAC_SPAN_REG] = 2, + [AD4170_DAC_CHANNEL_EN_REG] = 2, + [AD4170_DAC_HW_TOGGLE_MASK_REG] = 2, + [AD4170_DAC_HW_LDAC_MASK_REG] = 2, + [AD4170_DAC_DATA_REG(0)] = 2, + [AD4170_DAC_SW_TOGGLE_TRIGGERS_REG] = 2, + [AD4170_DAC_SW_LDAC_TRIGGERS_REG] = 2, + [AD4170_DAC_INPUTA_REG(0)] = 2, + [AD4170_DAC_INPUTB_REG(0)] = 2, + [AD4170_GPIO_MODE_REG] = 2, + [AD4170_OUTPUT_DATA_REG] = 2, + [AD4170_INPUT_DATA_REG] = 2, +}; From 3f85fe0da1e4bb0fbf416f3b0224674c134ab0af Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Thu, 26 Sep 2024 11:29:20 -0300 Subject: [PATCH 07/14] Documentation: iio: Add ADC documentation ADCs can have different input configurations such that developers can get confused when trying to model some of them into IIO channels. For example, some differential ADCs can have their channels configured as pseudo-differential channels. In that configuration, only one input connects to the signal of interest as opposed to using two inputs of a differential input configuration. Datasheets sometimes also refer to pseudo-differential inputs as single-ended inputs even though they have distinct physical configuration and measurement procedure. There has been some previous discussion in the mailing list about pseudo-differential and single-ended channels [1]. Documenting the many possible ADC channel configurations should provide two benefits: A) Consolidate the knowledge from [2] and from [1], and hopefully reduce the reviewing time of forthcoming ADC drivers. B) Help Linux developers figure out quicker how to better support differential ADCs, specially those that can have channels configured as pseudo-differential inputs. Add documentation about common ADC characteristics and IIO support for them. [1]: https://lore.kernel.org/linux-iio/0fef36f8-a7db-40cc-86bd-9449cb4ab46e@gmail.com/ [2]: https://www.analog.com/en/resources/technical-articles/sar-adc-input-types.html. Signed-off-by: Marcelo Schmitt --- Documentation/iio/iio_adc.rst | 280 ++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + 2 files changed, 281 insertions(+) create mode 100644 Documentation/iio/iio_adc.rst diff --git a/Documentation/iio/iio_adc.rst b/Documentation/iio/iio_adc.rst new file mode 100644 index 00000000000000..43b8cad547c9fa --- /dev/null +++ b/Documentation/iio/iio_adc.rst @@ -0,0 +1,280 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================= +IIO Abstractions for ADCs +========================= + +1. Overview +=========== + +The IIO subsystem supports many Analog to Digital Converters (ADCs). Some ADCs +have features and characteristics that are supported in specific ways by IIO +device drivers. This documentation describes common ADC features and explains +how they are (should be?) supported by the IIO subsystem. + +1. ADC Channel Types +==================== + +ADCs can have distinct types of inputs, each of them measuring analog voltages +in a slightly different way. An ADC digitizes the analog input voltage over a +span given by the provided voltage reference, the input type, and the input +polarity. The input range allowed to an ADC channel is needed to determine the +scale factor and offset needed to obtain the measured value in real-world +units (millivolts for voltage measurement, milliamps for current measurement, +etc.). + +There are three types of ADC inputs (single-ended, differential, +pseudo-differential) and two possible polarities (unipolar, bipolar). The input +type (single-ended, differential, pseudo-differential) is one channel +characteristic, and is completely independent of the polarity (unipolar, +bipolar) aspect. A comprehensive article about ADC input types (on which this +doc is heavily based on) can be found at +https://www.analog.com/en/resources/technical-articles/sar-adc-input-types.html. + +1.1 Single-ended channels +------------------------- + +Single-ended channels digitize the analog input voltage relative to ground and +can be either unipolar or bipolar. + +1.1.1 Single-ended Unipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + ---------- VREF ------------- + ´ ` ´ ` _____________ + / \ / \ / | + / \ / \ --- < IN ADC | + \ / \ / \ | + `-´ `-´ \ VREF | + -------- GND (0V) ----------- +-----------+ + ^ + | + External VREF + +The input voltage to a **single-ended unipolar** channel is allowed to swing +from GND to VREF (where VREF is a voltage reference with electrical potential +higher than system ground). The maximum input voltage is also called VFS +(full-scale input voltage), with VFS being determined by VREF. The voltage +reference may be provided from an external supply or derived from the chip power +source. + +A single-ended unipolar channel could be described in device tree like the +following example:: + + adc@0 { + ... + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + }; + }; + +See ``Documentation/devicetree/bindings/iio/adc/adc.yaml`` for the complete +documentation of ADC specific device tree properties. + + +1.1.2 Single-ended Bipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + ---------- +VREF ------------ + ´ ` ´ ` _____________________ + / \ / \ / | + / \ / \ --- < IN ADC | + \ / \ / \ | + `-´ `-´ \ +VREF -VREF | + ---------- -VREF ------------ +-------------------+ + ^ ^ + | | + External +VREF ------+ External -VREF + +For a **single-ended bipolar** channel, the analog voltage input can go from +-VREF to +VREF (where -VREF is the voltage reference that has the lower +electrical potential while +VREF is the reference with the higher one). Some ADC +chips derive the lower reference from +VREF, others get it from a separate +input. Often, +VREF and -VREF are symmetric but they don't need to be so. When +-VREF is lower than system ground, these inputs are also called single-ended +true bipolar. + +Here's an example device tree description of a single-ended bipolar channel. +:: + + adc@0 { + ... + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + bipolar; + }; + }; + +1.2 Differential channels +------------------------- + +A differential voltage measurement digitizes the voltage level at the positive +input (IN+) relative to the negative input (IN-) over the -VREF to +VREF span. +In other words, a differential channel measures how many volts IN+ is away from +IN- (IN+ - IN-). + +1.2.1 Differential Bipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + -------- +VREF ------ + ´ ` ´ ` +-------------------+ + / \ / \ / / | + `-´ `-´ --- < IN+ | + -------- -VREF ------ | | + | ADC | + -------- +VREF ------ | | + ´ ` ´ ` --- < IN- | + \ / \ / \ \ +VREF -VREF | + `-´ `-´ +-------------------+ + -------- -VREF ------ ^ ^ + | +---- External -VREF + External +VREF + +The analog signals to **differential bipolar** inputs are also allowed to swing +from -VREF to +VREF. If -VREF is below system GND, these are also called +differential true bipolar inputs. + +Device tree example of a differential bipolar channel:: + + adc@0 { + ... + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + bipolar; + diff-channels = <0 1>; + }; + }; + +In the ADC driver, `differential = 1` is set into `struct iio_chan_spec` for the +channel. See ``include/linux/iio/iio.h`` for more information. + +1.2.2 Differential Unipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For **differential unipolar** channels, the analog voltage at the positive input +must also be higher than the voltage at the negative input. Thus, the actual +input range allowed to a differential unipolar channel is IN- to +VREF. Because +IN+ is allowed to swing with the measured analog signal and the input setup must +guarantee IN+ will not go below IN- (nor IN- will raise above IN+), most +differential unipolar channel setups have IN- fixed to a known voltage that does +not fall within the voltage range expected for the measured signal. This leads +to a setup that is equivalent to a pseudo-differential channel. Thus, +differential unipolar channels are actually pseudo-differential unipolar +channels. + +1.3 Pseudo-differential Channels +-------------------------------- + +There is a third ADC input type which is called pseudo-differential or +single-ended to differential configuration. A pseudo-differential channel is +similar to a differential channel in that it also measures IN+ relative to IN-. +However, unlike differential channels, the negative input is limited to a narrow +voltage range while only IN+ is allowed to swing. A pseudo-differential channel +can be made out from a differential pair of inputs by restricting the negative +input to a known voltage while allowing only the positive input to swing. Aside +from that, some parts have a COM pin that allows single-ended inputs to be +referenced to a common-mode voltage, making them pseudo-differential channels. + +1.3.1 Pseudo-differential Unipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + -------- +VREF ------ +-------------------+ + ´ ` ´ ` / | + / \ / \ / --- < IN+ | + `-´ `-´ | | + --------- IN- ------- | ADC | + | | + Common-mode voltage --> --- < IN- | + \ +VREF -VREF | + +-------------------+ + ^ ^ + | +---- External -VREF + External +VREF + +A **pseudo-differential unipolar** input has the limitations a differential +unipolar channel would have, meaning the analog voltage to the positive input +IN+ must stay within IN- to +VREF. The fixed voltage to IN- is sometimes called +common-mode voltage and it must be within -VREF to +VREF as would be expected +from the signal to any differential channel negative input. + +In pseudo-differential configuration, the voltage measured from IN+ is not +relative to GND (as it would be for a single-ended channel) but to IN-, which +causes the measurement to always be offset by IN- volts. To allow applications +to calculate IN+ voltage with respect to system ground, the IIO channel may +provide an `_offset` attribute to report the channel offset to user space. + +Device tree example for pseudo-differential unipolar channel:: + + adc@0 { + ... + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + single-channel = <0>; + common-mode-channel = <1>; + }; + }; + +Do not set `differential` in the channel `iio_chan_spec` struct of +pseudo-differential channels. + +1.3.2 Pseudo-differential Bipolar Channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + -------- +VREF ------ +-------------------+ + ´ ` ´ ` / | + / \ / \ / --- < IN+ | + `-´ `-´ | | + -------- -VREF ------ | ADC | + | | + Common-mode voltage --> --- < IN- | + \ +VREF -VREF | + +-------------------+ + ^ ^ + | +---- External -VREF + External +VREF + +A **pseudo-differential bipolar** input is not limited by the level at IN- but +it will be limited to -VREF or to GND on the lower end of the input range +depending on the particular ADC. Similar to their unipolar counter parts, +pseudo-differential bipolar channels may define an `_offset` attribute to +provide the read offset relative to GND. + +Device tree example for pseudo-differential bipolar channel:: + + adc@0 { + ... + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + bipolar; + single-channel = <0>; + common-mode-channel = <1>; + }; + }; + +Again, the `differential` field of `struct iio_chan_spec` is not set for +pseudo-differential channels. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index c49f6f2252eca1..5dab836130ef7c 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -7,6 +7,7 @@ Industrial I/O .. toctree:: :maxdepth: 1 + iio_adc iio_configfs iio_devbuf iio_tools From 00d0c892647ccc3f8bd08f4165d48e7180759cf7 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Mon, 27 Jan 2025 10:58:07 -0300 Subject: [PATCH 08/14] arm: dts: socfpga_cyclone5_de10_nano_ad4170: Add label for SPI-Engine node Add a lable for the SPI-Engine controller node so it can be referenced by other dts that include socfpga_cyclone5_de10_nano_ad4170.dts in follow up patches. Signed-off-by: Marcelo Schmitt --- arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts index 4f3401e692007c..ce6e909f6b75cc 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts +++ b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4170.dts @@ -48,7 +48,7 @@ }; }; - spi@0x00030000 { + spi_engine: spi@0x00030000 { compatible = "adi-ex,axi-spi-engine-1.00.a"; reg = <0x00030000 0x00010000>; interrupt-parent = <&intc>; From a8e4a001d366c713afa7431608c7f3ddbbd346a6 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Fri, 6 Dec 2024 09:59:30 -0300 Subject: [PATCH 09/14] arm: dts: Add device tree for AD4190 on Cyclone5 DE10 nano Copied from socfpga_cyclone5_de10_nano_ad4190.dts. Only compatible string change. Signed-off-by: Marcelo Schmitt --- .../dts/socfpga_cyclone5_de10_nano_ad4190.dts | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4190.dts diff --git a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4190.dts b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4190.dts new file mode 100644 index 00000000000000..c1b0d107efbd7c --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4190.dts @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices CN0540 + * https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/cn0540.html + * https://www.analog.com/en/products/ad4170.html + * https://wiki.analog.com/resources/tools-software/linux-build/generic/socfpga + * + * hdl_project: + * board_revision: + * + * Copyright (C) 2024 Analog Devices Inc. + */ +/dts-v1/; +#include "socfpga_cyclone5_de10_nano_hps_pio21.dtsi" +#include +#include + +/ { + avdd: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; +}; + +&fpga_axi { + axi_dmac_0: rx-dma@0x00020000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x00020000 0x00000800>; + interrupt-parent = <&intc>; + interrupts = <0 44 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + clocks = <&h2f_user0_clk>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = <1>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + spi@0x00030000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x00030000 0x00010000>; + interrupt-parent = <&intc>; + interrupts = <0 45 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&sys_clk &h2f_user0_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + num-cs = <1>; + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4170@0 { + compatible = "adi,ad4190"; + reg = <0>; + spi-cpol; + spi-cpha; + avdd-supply = <&avdd>; + #address-cells = <1>; + #size-cells = <0>; + + dmas = <&axi_dmac_0 0>; + dma-names = "rx"; + + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio_in>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "adc_rdy"; + adi,dig-aux1-function = "rdy"; + + channel@0 { + reg = <0>; + adi,reference-select = <2>; + /* AIN8, DGND */ + diff-channels = <8 24>; + }; + + channel@1 { + reg = <1>; + adi,reference-select = <2>; + /* TEMP_SENSOR+, TEMP_SENSOR- */ + diff-channels = <17 17>; + }; + }; + }; +}; From 687b76c06a43f5d8ae176cbf21be0ae82070a5cb Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Fri, 6 Dec 2024 10:01:25 -0300 Subject: [PATCH 10/14] arm: dts: Add device tree for AD4190 on CoraZ7 Copied from zynq-coraz7s-ad4170.dts. Only compatible string change. Signed-off-by: Marcelo Schmitt --- arch/arm/boot/dts/zynq-coraz7s-ad4190.dts | 247 ++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 arch/arm/boot/dts/zynq-coraz7s-ad4190.dts diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad4190.dts b/arch/arm/boot/dts/zynq-coraz7s-ad4190.dts new file mode 100644 index 00000000000000..efdfc4f1e86f0f --- /dev/null +++ b/arch/arm/boot/dts/zynq-coraz7s-ad4190.dts @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4170 + * + * hdl_project: + * Link: https://github.com/analogdevicesinc/hdl/tree/main/projects/ad4170_asdz + * board_revision: + * + * Copyright (C) 2024 Analog Devices Inc. + */ +/dts-v1/; +#include "zynq-coraz7s.dtsi" +#include +#include +#include + +/ { + vref: regulator-vref { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <4096000>; + regulator-max-microvolt = <4096000>; + regulator-always-on; + }; + avdd: avdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval AVDD supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; + iovdd: iovdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval IOVDD supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + }; + refin1p: refin1p-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN+ voltage reference"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; + refin1n: refin1n-regulator { + compatible = "regulator-fixed"; + regulator-name = "Eval REFIN- voltage reference"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; +}; + +&fpga_axi { + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 16>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = <1>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + spi_engine: spi@0x44a00000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x10000>; + interrupt-parent = <&intc>; + interrupts = <0 55 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15 &spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + num-cs = <1>; + + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4170@0 { + compatible = "adi,ad4190"; + reg = <0>; + spi-max-frequency = <20000000>; + spi-cpol; + spi-cpha; + avdd-supply = <&avdd>; + iovdd-supply = <&iovdd>; + refin1p-supply = <&refin1p>; + refin1n-supply = <&refin1n>; + interrupt-parent = <&gpio0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "adc_rdy"; + dmas = <&rx_dma 0>; + dma-names = "rx"; + adi,dig-aux1 = /bits/ 8 <1>; + adi,dig-aux2 = /bits/ 8 <0>; + adi,sync-option = /bits/ 8 <0>; + + #address-cells = <1>; + #size-cells = <0>; + + // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input range + // Fully differential. If AVSS < 0V, Fully differential true bipolar + channel@0 { + // Feature under test: General functionality + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN0 = square, 1 Hz, 100mV p-p centered at 1V. + // AIN1 = sine, 10 Hz, 100mV p-p centered at 1V, 180º phase shifted. + reg = <0>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <0>; + adi,reference-select = <3>; + adi,burnout-current-nanoamp = <100>; + }; + // Sample AIN2 with respect to DGND throughout AVDD/DGND input range + // Peseudo-differential unipolar (fig. 2a) + channel@1 { + // Feature under test: Pseudo-diff scale + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN2 = sine, 1 kHz, 100mV p-p centered at 2V. + reg = <1>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <1>; + adi,reference-select = <3>; + }; + // Sample AIN3 with respect to REFOUT throughout AVDD/AVSS input range + // Pseudo-differential bipolar (fig. 2b) + channel@2 { + // Feature under test: Channel offset + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN7 = sine 100 mV p-p centered at 2.5V + reg = <2>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <2>; + adi,reference-select = <3>; + }; + // Sample AIN4 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential true bipolar if AVSS < 0V (fig. 2c) + channel@3 { + reg = <3>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <3>; + adi,reference-select = <3>; + }; + // Sample AIN5 with respect to REFOUT throughout AVDD/REFOUT input range + // Pseudo-differential unipolar (AD4170 datasheet page 46 example) + channel@4 { + // Feature under test: Channel offset + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN5 = sine 100 mV p-p centered at 3.6V + reg = <4>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <3>; + }; + // Sample AIN6 with respect to AVSS throughout AVDD/AVSS input range + // Pseudo-differential unipolar + channel@5 { + // Feature under test: Channel scale + // Test setup + // AVDD = 5V; AVSS = GND (0V) + // AIN5 = sine 100 mV p-p centered at 3.6V + reg = <5>; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <4>; + adi,reference-select = <3>; + }; + // Sample AIN7 with respect to (AVDD-AVSS)/5 throughout REFIN+/REFIN- input range + // Pseudo-differential bipolar + channel@6 { + reg = <6>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <5>; + adi,reference-select = <0>; + }; + // Temperature sensor + channel@7 { + reg = <7>; + bipolar; + diff-channels = ; + adi,config-setup-slot = <6>; + adi,reference-select = <0>; + }; + // Sample AIN8 with respect to DGND throughout AVDD/AVSS input range + // Pseudo-differential channel + channel@8 { + reg = <8>; + bipolar; + single-channel = ; + common-mode-channel = ; + adi,config-setup-slot = <7>; + adi,reference-select = <3>; + }; + + }; + }; + + axi_i2c_0:axi-iic@0x44a40000{ + compatible = "xlnx,axi-iic-1.02.a", "xlnx,xps-iic-2.00.a"; + reg = <0x44a40000 0x1000>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + + #address-cells = <1>; + #size-cells = <0>; + + ltc2606: ltc2606@10 { + compatible = "adi,ltc2606"; + reg = <0x10>; + vref-supply = <&vref>; + }; + }; + + spi_clk: axi-clkgen@0x44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x10000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + }; +}; From 7f9eaa249b21dcf532379be2f00cc880bd27c07b Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Fri, 6 Dec 2024 10:03:39 -0300 Subject: [PATCH 11/14] iio: adc: ad4170: Initial support for AD4190 Initial support for AD4190. Same functionality as AD4170 but displays different name. Signed-off-by: Marcelo Schmitt --- drivers/iio/adc/ad4170.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index 11bc4966e700f0..a4238237375122 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -2133,6 +2133,7 @@ static int ad4170_probe(struct spi_device *spi) struct device *dev = &spi->dev; struct iio_dev *indio_dev; struct ad4170_state *st; + const char *compat; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); @@ -2143,7 +2144,11 @@ static int ad4170_probe(struct spi_device *spi) mutex_init(&st->lock); st->spi = spi; - indio_dev->name = AD4170_NAME; + ret = device_property_read_string(dev, "compatible", &compat); + if (ret) + return dev_err_probe(dev, ret, "Failed read device name\n"); + + indio_dev->name = compat; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad4170_info; @@ -2207,6 +2212,7 @@ static int ad4170_probe(struct spi_device *spi) static const struct of_device_id ad4170_of_match[] = { { .compatible = "adi,ad4170", + .compatible = "adi,ad4190", }, { } }; From 8959b1fe392a54aa4b44d7b0cfac07637b98c916 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Mon, 27 Jan 2025 10:03:42 -0300 Subject: [PATCH 12/14] arm: dts: Add device tree for AD4195 on Cyclone5 DE10 nano AD4195 is similar to AD4170. Signed-off-by: Marcelo Schmitt --- .../dts/socfpga_cyclone5_de10_nano_ad4195.dts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4195.dts diff --git a/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4195.dts b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4195.dts new file mode 100644 index 00000000000000..da68b228225d0c --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_de10_nano_ad4195.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices CN0540 + * https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/cn0540.html + * https://www.analog.com/en/products/ad4170.html + * https://wiki.analog.com/resources/tools-software/linux-build/generic/socfpga + * + * hdl_project: + * board_revision: + * + * Copyright (C) 2025 Analog Devices Inc. + */ +/dts-v1/; + +#include "socfpga_cyclone5_de10_nano_ad4170.dts" + +&spi_engine { + status = "okay"; + + ad4195_4: ad4170@0 { + compatible = "adi,ad4195"; + }; +}; From 1b8366fcadba7c36a83febc91888252d76f9c4fa Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Mon, 27 Jan 2025 09:29:47 -0300 Subject: [PATCH 13/14] arm: dts: Add device tree for AD4195 on CoraZ7 AD4195 is similar to AD4170. Signed-off-by: Marcelo Schmitt --- arch/arm/boot/dts/zynq-coraz7s-ad4195.dts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 arch/arm/boot/dts/zynq-coraz7s-ad4195.dts diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad4195.dts b/arch/arm/boot/dts/zynq-coraz7s-ad4195.dts new file mode 100644 index 00000000000000..f6924d67f73a45 --- /dev/null +++ b/arch/arm/boot/dts/zynq-coraz7s-ad4195.dts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4195-4 + * + * hdl_project: + * Link: https://github.com/analogdevicesinc/hdl/tree/main/projects/ad4170_asdz + * board_revision: + * + * Copyright (C) 2025 Analog Devices Inc. + */ +/dts-v1/; + +#include "zynq-coraz7s-ad4170.dts" + +&spi_engine { + status = "okay"; + + ad4195_4: ad4170@0 { + compatible = "adi,ad4195"; + }; +}; + From 899521234528894892f4e8a0837997386441a24c Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Mon, 27 Jan 2025 09:10:41 -0300 Subject: [PATCH 14/14] iio: adc: ad4170: Add initial support for AD4195 Signed-off-by: Marcelo Schmitt --- drivers/iio/adc/ad4170.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index a4238237375122..ef10926754c8d5 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -2213,6 +2213,7 @@ static const struct of_device_id ad4170_of_match[] = { { .compatible = "adi,ad4170", .compatible = "adi,ad4190", + .compatible = "adi,ad4195", }, { } };