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>; + }; + }; + }; +... + 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 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..ce6e909f6b75cc --- /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_engine: 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_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>; + }; + }; + }; +}; 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"; + }; +}; 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>; + }; + }; + }; +}; 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"; + }; +}; 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"; + }; +}; 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"; + }; +}; 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"; + }; +}; + 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..ef10926754c8d5 --- /dev/null +++ b/drivers/iio/adc/ad4170.c @@ -0,0 +1,2234 @@ +// 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; + const char *compat; + 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; + + 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; + + 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", + .compatible = "adi,ad4190", + .compatible = "adi,ad4195", + }, + { } +}; +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, +}; 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_ */