From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 780EC266565; Wed, 9 Apr 2025 12:24:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201492; cv=none; b=b9yP4Dhk6xvgsDSfExWvyTooBHnPUFGZBO3b6jyoy4O3zsxisosW0RJQP43/h3hK7Q4d9zi2637QSBit49nqiKKHQkpGEQTnVj/Q5Bo0hFccP0isr7DP3puH4hABMGrsKDPIvIoISvTYeZnAmOUppROFwMSALr7ejZ+QE/LHTxY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201492; c=relaxed/simple; bh=QQpdP/15cOmBrlm28nL8N9uPhriBMqt+oEMMzljbTsU=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RElQFZZXGZLaUiw6DFWMetlxUF7wx9fnapcaGTUvwcoaC7B5Wqpv3H9CoTCQvEOnFOEsx0amC8DJ30fVyOrCZ4Db6TRSLU3JlL6y8V29Wa9eK47EK75ZimG+sQj6R8irZFWO39axoanNcolDpCTASgOsyzajCPKDkcBytuHjjwU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=nwoShZpF; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="nwoShZpF" Received: from pps.filterd (m0167088.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5399CPra010114; Wed, 9 Apr 2025 08:24:34 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=ZiFY7 m5mtzeMBrTBaTEl3Iv0dVHft9MAxEOZxhRG08A=; b=nwoShZpF4N6tvaDs5B35Z sUk0+LBHLVl+01mJgb8VudeWfxR1Pskn9auiTyu/RETon9J1HyHQ0JmPceCeucUF 9hweH7bKAReubUhtgMtRukKqge1evD4nAbJvvO1MhjiAGNri/lKztenbnnY34lja f7Ss9vkeOiVdItdgE0vY82mDiBPFroypJK6PQ0ove9R9VOWy0Lvho6MSpPv2FPxI 3pcVOzpabvjiKUpOv0bktUosX6bEsLCeabJuVNt/WO4Ey6c4YJrr9EmNfF6ao47h A28oISrDbad55xYaPdamilMFbrqVoYwKARislVmFZngIxHDvTp2syWOxa3hbDD6Z w== Received: from nwd2mta3.analog.com ([137.71.173.56]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45txc66a92-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:24:34 -0400 (EDT) Received: from ASHBMBX9.ad.analog.com (ASHBMBX9.ad.analog.com [10.64.17.10]) by nwd2mta3.analog.com (8.14.7/8.14.7) with ESMTP id 539COXWk052208 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:24:33 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:24:32 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:24:32 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539COJLO016900; Wed, 9 Apr 2025 08:24:22 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 1/7] dt-bindings: iio: adc: Add AD4170 Date: Wed, 9 Apr 2025 09:24:18 -0300 Message-ID: X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-GUID: gjdeHZ1icUa02N4HoeNGR7_H0kNM5EHK X-Proofpoint-ORIG-GUID: gjdeHZ1icUa02N4HoeNGR7_H0kNM5EHK X-Authority-Analysis: v=2.4 cv=KePSsRYD c=1 sm=1 tr=0 ts=67f66702 cx=c_pps a=PpDZqlmH/M8setHirZLBMw==:117 a=PpDZqlmH/M8setHirZLBMw==:17 a=IkcTkHD0fZMA:10 a=XR8D0OoHHMoA:10 a=gEfo2CItAAAA:8 a=gAnH3GRIAAAA:8 a=VwQbUJbxAAAA:8 a=WNIpHu94SUlyTeZhJcUA:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=sptkURWiP4Gy88Gu7hUp:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 bulkscore=0 impostorscore=0 suspectscore=0 spamscore=0 lowpriorityscore=0 clxscore=1015 mlxlogscore=999 adultscore=0 phishscore=0 mlxscore=0 priorityscore=1501 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090074 Add device tree documentation for AD4170 and similar sigma-delta ADCs. The AD4170 is a 24-bit, multichannel, sigma-delta ADC. Signed-off-by: Marcelo Schmitt --- The AD4170 design has features to aid interfacing with weigh scale and RTD sensors that are expected to be setup with external circuitry for proper sensor operation. A key characteristic of those sensors is that the circuit they are in must be excited with a pair of signals. The external circuit can be excited either by voltage supply or by AD4170 excitation signals. The sensor can then be read through a different pair of lines that are connected to AD4170 ADC. .../bindings/iio/adc/adi,ad4170.yaml | 527 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 534 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4170.ya= ml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml b/Do= cumentation/devicetree/bindings/iio/adc/adi,ad4170.yaml new file mode 100644 index 000000000000..93fe3b4648a0 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml @@ -0,0 +1,527 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4170.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4170 and similar Analog to Digital Converters + +maintainers: + - Marcelo Schmitt + +description: | + Analog Devices AD4170 series of Sigma-delta Analog to Digital Converters. + Specifications can be found at: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad= 4170-4.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ad= 4190-4.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ad= 4195-4.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +$defs: + sensor-node: + type: object + description: | + Common properties of external sensor circuitry connected to the ADC. + + properties: + reg: + description: + Channel number. Connects the sensor to the channel with this num= ber + of the device. + minimum: 1 + maximum: 16 + + diff-channels: + $ref: /schemas/types.yaml#/definitions/uint32-array + maxItems: 2 + minItems: 2 + description: | + ADC analog input pins to which the sensor circuit is connected. + The first value specifies the positive input pin, the second + specifies the negative input pin. See adc.yaml for details. + + bipolar: + $ref: /schemas/types.yaml#/definitions/flag + description: If provided, the channel is to be used in bipolar mod= e. + + adi,sensor-type: + description: Type of sensor connected to the device. + $ref: /schemas/types.yaml#/definitions/uint8 + + adi,ac-excited: + type: boolean + description: | + Whether the external circuit has to be AC or DC excited. + + adi,excitation-pins: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: | + ADC pins used for external circuit excitation. Some applications + require optimum matching between excitation currents. Using exci= tation + current pairs minimizes the excitation current mismatch and the + excitation current drift matching on the ADC. Must describe eith= er 1 + or 2 pairs of pins. E.g. <0 1>; <2 3>; <0 1>, <2 3>. + + adi,excitation-current-microamp: + description: | + Excitation current in microamperes to be output to each excitati= on pin + specified by adi,excitation-pins property. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + default: 0 + + adi,reference-select: + description: | + Select the reference source to use when converting on the specif= ic + channel. Valid values are: + 0: Differential reference voltage REFIN+ - REFIN=E2=88=92. + 1: Differential reference voltage REFIN2+ - REFIN2=E2=88=92. + 2: Internal 2.5V referece (REFOUT) relative to AVSS. + 3: Analog supply voltage (AVDD) relative AVSS. + If this field is left empty, the first external reference is sel= ected. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2, 3] + default: 0 + + required: + - reg + - diff-channels + - bipolar + - adi,sensor-type + - adi,excitation-pins + - adi,reference-select + +properties: + compatible: + enum: + - adi,ad4170 + - adi,ad4190 + - adi,ad4195 + + avss-supply: + description: + Referece voltage supply for AVSS. If provided, describes the magnitu= de + (absolute value) of the negative voltage supplied to the AVSS pin. S= ince + AVSS must be =E2=88=922.625V minimum and 0V maximum, the declared su= pply voltage + must be between 0 and 2.65V. If not provided, AVSS is assumed to be = at + system ground (0V). + + avdd-supply: + description: + A supply of 4.75V to 5.25V relative to AVSS that powers the chip (AV= DD). + + iovdd-supply: + description: 1.7V to 5.25V reference supply to the serial interface (I= OVDD). + + refin1p-supply: + description: REFIN+ supply that can be used as reference for conversio= n. + + refin1n-supply: + description: REFIN- supply that can be used as reference for conversio= n. If + provided, describes the magnitude (absolute value) of the negative v= oltage + supplied to the REFIN- pin. + + refin2p-supply: + description: REFIN2+ supply that can be used as reference for conversi= on. + + refin2n-supply: + description: REFIN2- supply that can be used as reference for conversi= on. If + provided, describes the magnitude (absolute value) of the negative v= oltage + supplied to the REFIN2- pin. + + spi-cpol: true + + spi-cpha: true + + interrupts: + maxItems: 1 + + interrupt-names: + description: | + Specify which pin should be configured as Data Ready interrupt. + Default if not supplied is sdo. + enum: + - sdo + - dig_aux1 + + clocks: + maxItems: 1 + description: + Optional external clock source. Can specify either an external clock= or + external crystal. + + clock-names: + enum: + - ext-clk + - xtal + + '#clock-cells': + const: 0 + + gpio-controller: true + + "#gpio-cells": + const: 2 + description: | + The first cell is for the GPIO number: 0 to 3. + The second cell takes standard GPIO flags. + + ldac-gpios: + description: + GPIO connected to DIG_AUX2 pin to be used as LDAC toggle to control = the + transfer of data from the DAC_INPUT_A register to the DAC. + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + "^channel@[0-9a-f]$": + $ref: adc.yaml + type: object + unevaluatedProperties: false + description: | + Represents the external channels which are connected to the ADC. + + properties: + reg: + description: | + The channel number. + 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 s= econd + value is the negative input of the channel. + + Besides the analog input pins AIN0 to AIN8, there are special in= puts + that can be selected with the following values: + 17: Internal temperature sensor + 18: (AVDD-AVSS)/5 + 19: (IOVDD-DGND)/5 + 20: DAC output + 21: ALDO + 22: DLDO + 23: AVSS + 24: DGND + 25: REFIN+ + 26: REFIN- + 27: REFIN2+ + 28: REFIN2- + 29: REFOUT + For the internal temperature sensor, use the input number for bo= th + inputs (i.e. diff-channels =3D <17 17>). + items: + enum: [0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 19, 20, 21, 22, 23, 24= , 25, + 26, 27, 28, 29] + + single-channel: true + + common-mode-channel: true + + bipolar: true + + adi,sensor-type: + description: Sensor type for direct ADC sensors. + $ref: /schemas/types.yaml#/definitions/uint8 + const: 0 + + adi,buffered-positive: + description: | + Enable precharge buffer, full buffer, or skip reference bufferin= g of + the positive voltage reference. Because the output impedance of = the + source driving the voltage reference inputs may be dynamic, RC + combinations of those inputs can cause DC gain errors if the ref= erence + inputs go unbuffered into the ADC. Enable reference buffering if= the + provided reference source has dynamic high impedance output. Not= e the + absolute voltage allowed on positive reference inputs (REFIN+, + REFIN2+) is from AVSS =E2=88=92 50 mV to AVDD + 50 mV when the r= eference + buffers are disabled but narrows to AVSS to AVDD when reference + buffering is enabled or in precharge mode. + 0: Reference precharge buffer. + 1: Full Buffer. + 2: Bypass reference buffers (buffering disabled). + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2] + default: 0 + + adi,buffered-negative: + description: | + Enable precharge buffer, full buffer, or skip reference bufferin= g of + the negative voltage reference. Because the output impedance of = the + source driving the voltage reference inputs may be dynamic, RC + combinations of those inputs can cause DC gain errors if the ref= erence + inputs go unbuffered into the ADC. Enable reference buffering if= the + provided reference source has dynamic high impedance output. Not= e the + absolute voltage allowed on negative reference inputs (REFIN-, + REFIN2-) is from AVSS =E2=88=92 50 mV to AVDD + 50 mV when the r= eference + buffers are disabled but narrows to AVSS to AVDD when reference + buffering is enabled or in precharge mode. + 0: Reference precharge buffer. + 1: Full Buffer. + 2: Bypass reference buffers (buffering disabled). + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2] + default: 0 + + adi,reference-select: + description: | + Select the reference source to use when converting on the specif= ic + channel. Valid values are: + 0: Differential reference voltage REFIN+ - REFIN=E2=88=92. + 1: Differential reference voltage REFIN2+ - REFIN2=E2=88=92. + 2: Internal 2.5V referece (REFOUT) relative to AVSS. + 3: Analog supply voltage (AVDD) relative AVSS. + If this field is left empty, the internal reference is selected. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1, 2, 3] + default: 2 + + required: + - reg + + allOf: + - oneOf: + - required: [single-channel] + properties: + diff-channels: false + - required: [diff-channels] + properties: + single-channel: false + common-mode-channel: false + + "^weighscale@": + $ref: '#/$defs/sensor-node' + unevaluatedProperties: false + + properties: + diff-channels: true + bipolar: true + + adi,sensor-type: + description: Weigh scale sensor. + $ref: /schemas/types.yaml#/definitions/uint8 + const: 1 + + adi,excitation-pins: + description: | + ADC pins to use for weigh scale bridge circuit excitation. Must + describe either 1 or 2 pairs of pins. E.g. <0 1>; <2 3>; <0 1>, = <2 3>. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 4 + items: + minimum: 0 + maximum: 20 + + adi,excitation-current-microamp: + description: | + Excitation current in microamperes to be output to each excitati= on pin + specified by adi,excitation-pins property. If not provided and + adi,ac-excited is true, use predefined ACX1, ACX1 negated, ACX2,= and + ACX2 negated signals to AC excite the weigh scale bridge. Those + singals are output on GPIO2, GPIO0, GPIO3, and GPIO1, respective= ly. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + + adi,power-down-switch-pin: + description: | + Number of the GPIO used as power-down switch for the bridge circ= uit. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1] + + "^thermocouple@": + $ref: '#/$defs/sensor-node' + unevaluatedProperties: false + + properties: + diff-channels: true + bipolar: true + + adi,sensor-type: + description: Thermocouple sensor. + $ref: /schemas/types.yaml#/definitions/uint8 + const: 2 + + adi,excitation-pins: + description: | + ADC pins to use for bridge circuit excitation. Must describe eit= her 1 + or 2 pairs of pins. E.g. <0 1>; <2 3>; <0 1>, <2 3>. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 4 + items: + minimum: 0 + maximum: 20 + + adi,excitation-current-microamp: + description: | + Excitation current in microamperes to be output to each excitati= on pin + specified by adi,excitation-pins property. If not provided and + adi,ac-excited is true, use predefined ACX1, ACX1 negated, ACX2,= and + ACX2 negated signals to AC excite the bridge circuit. Those sing= als + are output on GPIO2, GPIO0, GPIO3, and GPIO1, respectively. + enum: [0, 10, 50, 100, 250, 500, 1000, 1500] + + adi,vbias: + type: boolean + description: | + For unbiased thermocouple applications, the voltage generated by= the + thermocouple must be biased around some DC voltage. When present= , this + property specifies a bias voltage of (AVDD + AVSS)/2 to be appli= ed as + common-mode voltage for the sensor. + + adi,power-down-switch-pin: + description: | + Number of the GPIO used as power-down switch for the bridge circ= uit. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1] + + "^rtd@": + $ref: '#/$defs/sensor-node' + unevaluatedProperties: false + + properties: + diff-channels: true + bipolar: true + + adi,sensor-type: + description: RTD sensor. + $ref: /schemas/types.yaml#/definitions/uint8 + const: 3 + + adi,excitation-pins: + description: | + ADC pins to use for RTD circuit excitation. Must describe a pair= of + pins. E.g. <0 1>; <2 3>. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 2 + items: + minimum: 0 + maximum: 20 + + adi,excitation-current-microamp: true + + required: + - adi,excitation-current-microamp + +required: + - compatible + - reg + - avdd-supply + - iovdd-supply + - spi-cpol + - spi-cpha + +allOf: + # Some devices don't have integrated DAC + - if: + properties: + compatible: + contains: + enum: + - adi,ad4190 + - adi,ad4195 + then: + properties: + ldac-gpios: false + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + adc@0 { + compatible =3D "adi,ad4170"; + reg =3D <0>; + spi-max-frequency =3D <20000000>; + spi-cpol; + spi-cpha; + avdd-supply =3D <&avdd>; + iovdd-supply =3D <&iovdd>; + interrupt-parent =3D <&gpio_in>; + interrupts =3D <0 IRQ_TYPE_EDGE_FALLING>; + #address-cells =3D <1>; + #size-cells =3D <0>; + + // Sample AIN0 with respect to AIN1 throughout AVDD/AVSS input= range + // Differential bipolar. If AVSS < 0V, differential true bipol= ar + channel@0 { + reg =3D <0>; + bipolar; + diff-channels =3D <0 1>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <3>; + }; + // Sample AIN2 with respect to DGND throughout AVDD/DGND input= range + // Pseudo-differential unipolar (fig. 2a) + channel@1 { + reg =3D <1>; + single-channel =3D <2>; + common-mode-channel =3D <24>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <3>; + }; + // Sample AIN3 with respect to 2.5V throughout AVDD/AVSS input= range + // Pseudo-differential bipolar (fig. 2b) + channel@2 { + reg =3D <2>; + bipolar; + single-channel =3D <3>; + common-mode-channel =3D <29>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <3>; + }; + // Sample AIN4 with respect to DGND throughout AVDD/AVSS input= range + // Pseudo-differential bipolar (fig. 2c) + channel@3 { + reg =3D <3>; + bipolar; + single-channel =3D <4>; + common-mode-channel =3D <24>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <3>; + }; + // Sample AIN5 with respect to 2.5V throughout AVDD/AVSS input= range + // Pseudo-differential unipolar (AD4170 datasheet page 46 exam= ple) + channel@4 { + reg =3D <4>; + single-channel =3D <5>; + common-mode-channel =3D <29>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <3>; + }; + // Sample AIN6 with respect to 2.5V throughout REFIN+/REFIN- i= nput range + // Pseudo-differential bipolar + channel@5 { + reg =3D <5>; + bipolar; + single-channel =3D <6>; + common-mode-channel =3D <29>; + adi,sensor-type =3D /bits/ 8 <0>; + adi,reference-select =3D /bits/ 8 <0>; + }; + // Weigh scale sensor + weighscale@6 { + reg =3D <6>; + bipolar; + diff-channels =3D <7 8>; + adi,sensor-type =3D /bits/ 8 <1>; + adi,ac-excited; + adi,excitation-pins =3D <17 18>, <19 20>; + adi,reference-select =3D /bits/ 8 <0>; + }; + }; + }; +... + diff --git a/MAINTAINERS b/MAINTAINERS index 030d90d38341..991b6e2e373a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1337,6 +1337,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-ad4130 F: Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml F: drivers/iio/adc/ad4130.c =20 +ANALOG DEVICES INC AD4170 DRIVER +M: Marcelo Schmitt +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml + ANALOG DEVICES INC AD4695 DRIVER M: Michael Hennerich M: Nuno S=C3=A1 --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F028C25DCEC; Wed, 9 Apr 2025 12:25:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201514; cv=none; b=dSgPufgIiePgPBTzEfsRZPkgC9A+lsfBUKosVOiNSs9dUviY9eejecfT4KS3cl8QtO8IxSOtsDHUgAL8k4ZVj8gnlCMgVvneulIje+IwJazuO/uUsTr1NB9v4e0xUitMWVPnQr8Wfa6haExcSU1ff3kN5UDtFwfGZZ1NifFnMUw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201514; c=relaxed/simple; bh=uhBmUVX0AYwE4hMIedYNF8Pm5cKtjvdQRdaGuN3jakM=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=HZPqEX7DADILOPj69E+MmPET1Pegq2dIkVWxWSXYTMBPw/ZLIDUfEpZTk3uDzWVofhB83K15LaYkjBNkPMmJ+AnBU8LjscD+crWH1O6AsmyCTAU+F8bSZ/af19FClOTt7ANXw2803qmYY87eOZsEgitRaGDkXAbbMvWXe2xkyfo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=1c3luJFg; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="1c3luJFg" Received: from pps.filterd (m0167089.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5398xUcv002664; Wed, 9 Apr 2025 08:24:53 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=iLuJW bQtbv81DewzJuR6JygT18UBAvnVPsK/+Vx7sX8=; b=1c3luJFgv9L3jiwhQ3bxU 0XamjhPP3Vg7uoUUPo856PLP9GLAbbk40eWQvWxj21Q2E5Qwa1kKmdoV8EOTAiiH r/s2k2Yr65xMLXM06+CWDB2ID7eUhKeya2pYCpzD+faS+cUBHh1efQrhdy4fEthV EmYeiqD0D4hRgreMzkoj+4VgbLk3/nss+ZCtVRFvfdSjLjkvr4Vf28pUjJ8NCAI9 bshqC6m4txKcyrf0h2j1UNG6A5YpRQvvI7ftQeRX+Uf6lGot1gKECV9oF4EKS/j4 HgFnBFyPfIH8lWmN1V62NeV4i3/vVSSzjc4XW22/snXIP3lWDERbOI35cDktAPfH g== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45u1e6dqqw-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:24:52 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 539COpx6041835 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:24:51 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:24:51 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:24:51 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CObcs016906; Wed, 9 Apr 2025 08:24:39 -0400 From: Marcelo Schmitt To: , , CC: Ana-Maria Cusco , , , , , , , , , , Subject: [PATCH v1 2/7] iio: adc: Add basic support for AD4170 Date: Wed, 9 Apr 2025 09:24:35 -0300 Message-ID: <5f79007f0b9f9f67360d04fb904b6a59111a4ebe.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-ORIG-GUID: gEQSAuoTH85ll8nIaBIWZJ4gr7X-4Ohj X-Authority-Analysis: v=2.4 cv=cdjSrmDM c=1 sm=1 tr=0 ts=67f66714 cx=c_pps a=3WNzaoukacrqR9RwcOSAdA==:117 a=3WNzaoukacrqR9RwcOSAdA==:17 a=IkcTkHD0fZMA:10 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=VwQbUJbxAAAA:8 a=Aqqe9QBHYUSGchtk9PwA:9 a=adnMvLaHktd4d8bb:21 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 X-Proofpoint-GUID: gEQSAuoTH85ll8nIaBIWZJ4gr7X-4Ohj X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 lowpriorityscore=0 impostorscore=0 spamscore=0 malwarescore=0 priorityscore=1501 adultscore=0 mlxscore=0 clxscore=1015 mlxlogscore=999 phishscore=0 bulkscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090073 From: Ana-Maria Cusco Add support for the AD4170 ADC with the following features: - Single-shot read. - Analog front end PGA configuration. - Digital filter and sampling frequency configuration. - Calibration gain and offset configuration. - Differential and pseudo-differential input configuration. Signed-off-by: Ana-Maria Cusco Co-developed-by: Marcelo Schmitt Signed-off-by: Marcelo Schmitt --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 16 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4170.c | 1950 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1968 insertions(+) create mode 100644 drivers/iio/adc/ad4170.c diff --git a/MAINTAINERS b/MAINTAINERS index 991b6e2e373a..56cd87028dfd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1343,6 +1343,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4170.yaml +F: drivers/iio/adc/ad4170.c =20 ANALOG DEVICES INC AD4695 DRIVER M: Michael Hennerich diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 636469392945..de7139fc2a1f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -70,6 +70,22 @@ config AD4130 To compile this driver as a module, choose M here: the module will be called ad4130. =20 + +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 AD4695 tristate "Analog Device AD4695 ADC Driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 07d4b832c42e..d3a1376d1f96 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_AD_SIGMA_DELTA) +=3D ad_sigma_delta.o obj-$(CONFIG_AD4000) +=3D ad4000.o obj-$(CONFIG_AD4030) +=3D ad4030.o obj-$(CONFIG_AD4130) +=3D ad4130.o +obj-$(CONFIG_AD4170) +=3D ad4170.o obj-$(CONFIG_AD4695) +=3D ad4695.o obj-$(CONFIG_AD4851) +=3D ad4851.o obj-$(CONFIG_AD7091R) +=3D ad7091r-base.o diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c new file mode 100644 index 000000000000..0d24286ac2ab --- /dev/null +++ b/drivers/iio/adc/ad4170.c @@ -0,0 +1,1950 @@ +// 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 + +/* + * AD4170 registers + * Multibyte register addresses point to the most significant byte which i= s the + * address to use to get the most significant byte first (address accessed= is + * decremented by one for each data byte) + * + * Each register address define follows the AD4170__REG form. + * Each mask follows the AD4170__ form. + * E.g. AD4170_PIN_MUXING_DIG_AUX2_CTRL_MSK is for accessing DIG_AUX2_CTRL= field + * of PIN_MUXING_REG. + * Each constant follows the AD4170___ for= m. + * E.g. AD4170_PIN_MUXING_DIG_AUX2_DISABLED is the value written to + * DIG_AUX2_CTRL field of PIN_MUXING register to disable DIG_AUX2 pin. + * Some register names and register field names are shortened versions of + * their datasheet counterpart names to provide better code readability. + */ +#define AD4170_CONFIG_A_REG 0x00 +#define AD4170_DEV_CONFIG_REG 0x02 +#define AD4170_CHIP_TYPE_REG 0x03 +#define AD4170_PROD_ID_L_REG 0x04 +#define AD4170_PROD_ID_H_REG 0x05 +#define AD4170_CHIP_GRADE_REG 0x06 +#define AD4170_SPI_REV_REG 0x07 +#define AD4170_VENDOR_L_REG 0x08 +#define AD4170_VENDOR_H_REG 0x09 +#define AD4170_SCRATCH_PAD_REG 0x0A +#define AD4170_CONFIG_C_REG 0x0A +#define AD4170_IF_STATUS_A_REG 0x11 +#define AD4170_STATUS_REG 0x15 +#define AD4170_DATA_16B_REG 0x17 +#define AD4170_DATA_16B_STATUS_REG 0x1A +#define AD4170_DATA_24B_REG 0x1E +#define AD4170_PIN_MUXING_REG 0x69 +#define AD4170_ADC_CTRL_REG 0x71 +#define AD4170_CHAN_EN_REG 0x79 +#define AD4170_CHAN_SETUP_REG(x) (0x81 + 4 * (x)) +#define AD4170_CHAN_MAP_REG(x) (0x83 + 4 * (x)) +#define AD4170_MISC_REG(x) (0xC1 + 14 * (x)) +#define AD4170_AFE_REG(x) (0xC3 + 14 * (x)) +#define AD4170_FILTER_REG(x) (0xC5 + 14 * (x)) +#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x)) +#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x)) +#define AD4170_GAIN_REG(x) (0xCD + 14 * (x)) +#define AD4170_V_BIAS_REG 0x135 +#define AD4170_FIR_CTRL 0x141 +#define AD4170_COEFF_DATA_REG 0x14A +#define AD4170_COEFF_ADDR_REG 0x14C +#define AD4170_GPIO_OUTPUT_REG 0x193 +#define AD4170_GPIO_INPUT_REG 0x195 + +#define AD4170_REG_READ_MASK BIT(14) + +/* AD4170_CONFIG_A_REG - INTERFACE_CONFIG_A REGISTER */ +#define AD4170_SW_RESET_MSK (BIT(7) | BIT(0)) + +/* AD4170_PIN_MUXING_REG */ +#define AD4170_PIN_MUXING_DIG_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) + +/* AD4170_ADC_CTRL_REG */ +#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7) +#define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4) +#define AD4170_ADC_CTRL_MODE_MSK GENMASK(3, 0) + +/* AD4170_CHAN_EN_REG */ +#define AD4170_CHAN_EN(ch) BIT(ch) + +/* AD4170_CHAN_SETUP_REG */ +#define AD4170_CHAN_SETUP_SETUP_MSK GENMASK(2, 0) + +/* AD4170_CHAN_MAP_REG */ +#define AD4170_CHAN_MAP_AINP_MSK GENMASK(12, 8) +#define AD4170_CHAN_MAP_AINM_MSK GENMASK(4, 0) + +/* AD4170_MISC_REG */ +#define AD4170_MISC_CHOP_IEXC_MSK GENMASK(15, 14) +#define AD4170_MISC_CHOP_ADC_MSK GENMASK(9, 8) +#define AD4170_MISC_BURNOUT_MSK GENMASK(1, 0) + +/* AD4170_AFE_REG */ +#define AD4170_AFE_REF_BUF_M_MSK GENMASK(11, 10) +#define AD4170_AFE_REF_BUF_P_MSK GENMASK(9, 8) +#define AD4170_AFE_REF_SELECT_MSK GENMASK(6, 5) +#define AD4170_AFE_BIPOLAR_MSK BIT(4) +#define AD4170_AFE_PGA_GAIN_MSK GENMASK(3, 0) + +/* AD4170_FILTER_REG */ +#define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0) + +/* AD4170 register constants */ + +/* AD4170_CHAN_MAP_REG constants */ +#define AD4170_CHAN_MAP_AIN0 0 +#define AD4170_CHAN_MAP_AIN1 1 +#define AD4170_CHAN_MAP_AIN2 2 +#define AD4170_CHAN_MAP_AIN3 3 +#define AD4170_CHAN_MAP_AIN4 4 +#define AD4170_CHAN_MAP_AIN5 5 +#define AD4170_CHAN_MAP_AIN6 6 +#define AD4170_CHAN_MAP_AIN7 7 +#define AD4170_CHAN_MAP_AIN8 8 +#define AD4170_CHAN_MAP_TEMP_SENSOR 17 +#define AD4170_CHAN_MAP_AVDD_AVSS_P 18 +#define AD4170_CHAN_MAP_AVDD_AVSS_N 18 +#define AD4170_CHAN_MAP_IOVDD_DGND_P 19 +#define AD4170_CHAN_MAP_IOVDD_DGND_N 19 +#define AD4170_CHAN_MAP_DAC_P 20 +#define AD4170_CHAN_MAP_DAC_N 20 +#define AD4170_CHAN_MAP_ALDO 21 +#define AD4170_CHAN_MAP_DLDO 22 +#define AD4170_CHAN_MAP_AVSS 23 +#define AD4170_CHAN_MAP_DGND 24 +#define AD4170_CHAN_MAP_REFIN1_P 25 +#define AD4170_CHAN_MAP_REFIN1_N 26 +#define AD4170_CHAN_MAP_REFIN2_P 27 +#define AD4170_CHAN_MAP_REFIN2_N 28 +#define AD4170_CHAN_MAP_REFOUT 29 + +/* AD4170_PIN_MUXING_REG constants */ +#define AD4170_PIN_MUXING_DIG_AUX1_DISABLED 0x0 +#define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1 +#define AD4170_PIN_MUXING_DIG_AUX1_SYNC 0x2 + +#define AD4170_PIN_MUXING_DIG_AUX2_DISABLED 0x0 +#define AD4170_PIN_MUXING_DIG_AUX2_LDAC 0x1 +#define AD4170_PIN_MUXING_DIG_AUX2_SYNC 0x2 + +#define AD4170_PIN_MUXING_SYNC_DISABLED 0x0 +#define AD4170_PIN_MUXING_SYNC_STANDARD 0x1 +#define AD4170_PIN_MUXING_SYNC_ALTERNATE 0x2 + +/* AD4170_ADC_CTRL_REG constants */ +#define AD4170_ADC_CTRL_CONT_READ_DISABLE 0x0 +#define AD4170_ADC_CTRL_CONT_READ_ENABLE 0x1 + +#define AD4170_ADC_CTRL_MODE_CONT 0x0 +#define AD4170_ADC_CTRL_MODE_SINGLE 0x4 +#define AD4170_ADC_CTRL_MODE_IDLE 0x7 + +/* AD4170_FILTER_REG constants */ +#define AD4170_FILTER_FILTER_TYPE_SINC5_AVG 0x0 +#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4 +#define AD4170_FILTER_FILTER_TYPE_SINC3 0x6 + +/* Device properties and auxiliary constants */ + +#define AD4170_NUM_ANALOG_PINS 9 +#define AD4170_MAX_CHANNELS 16 +#define AD4170_MAX_ANALOG_PINS 8 +#define AD4170_MAX_SETUPS 8 +#define AD4170_INVALID_SETUP 9 +#define AD4170_NUM_CURRENT_SRC 4 +#define AD4170_DEFAULT_SAMP_RATE (125 * KILO) + +#define AD4170_INT_REF_2_5V (2500 * MILLI) + +/* Internal and external clock properties */ +#define AD4170_INT_CLOCK_16MHZ (16 * MEGA) +#define AD4170_EXT_CLOCK_MHZ_MIN (1 * MEGA) +#define AD4170_EXT_CLOCK_MHZ_MAX (17 * MEGA) + +#define AD4170_NUM_PGA_OPTIONS 10 + +/* Digital filter properties */ +#define AD4170_SINC3_MIN_FS 4 +#define AD4170_SINC3_MAX_FS 65532 +#define AD4170_SINC5_MIN_FS 1 +#define AD4170_SINC5_MAX_FS 256 + +#define AD4170_GAIN_REG_DEFAULT 0x555555 + +#define AD4170_ADC_CTRL_CONT_READ_EXIT 0xA5 + +/* Analog pin functions */ +#define AD4170_PIN_UNASIGNED 0x00 +#define AD4170_PIN_ANALOG_IN 0x01 +#define AD4170_PIN_CURRENT_OUT 0x02 + +enum ad4170_ref_buf { + AD4170_REF_BUF_PRE, /* Pre-charge referrence buffer */ + AD4170_REF_BUF_FULL, /* Full referrence buffering */ + AD4170_REF_BUF_BYPASS /* Bypass referrence buffering */ +}; + +enum ad4170_ref_select { + AD4170_REF_REFIN1, + AD4170_REF_REFIN2, + AD4170_REF_REFOUT, + AD4170_REF_AVDD +}; + +enum ad4170_filter_type { + AD4170_SINC5_AVG, + AD4170_SINC5, + AD4170_SINC3, +}; + +enum ad4170_regulator { + AD4170_AVDD_SUP, + AD4170_AVSS_SUP, + AD4170_IOVDD_SUP, + AD4170_REFIN1P_SUP, + AD4170_REFIN1N_SUP, + AD4170_REFIN2P_SUP, + AD4170_REFIN2N_SUP, + AD4170_MAX_SUP +}; + +enum ad4170_int_pin_sel { + AD4170_INT_PIN_SDO, + AD4170_INT_PIN_DIG_AUX1, +}; + +static const char * const ad4170_int_pin_names[] =3D { + [AD4170_INT_PIN_SDO] =3D "sdo", + [AD4170_INT_PIN_DIG_AUX1] =3D "dig_aux1", +}; + +static const unsigned int ad4170_sinc3_filt_fs_tbl[] =3D { + 4, 8, 12, 16, 20, 40, 48, 80, 100, 256, 500, 1000, 5000, 8332, 10000, + 25000, 50000, 65532 +}; + +#define AD4170_MAX_FS_TBL_SIZE ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) + +static const unsigned int ad4170_sinc5_filt_fs_tbl[] =3D { + 1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256 +}; + +static const char * const ad4170_chip_names[] =3D { + "ad4170", + "ad4190", + "ad4195" +}; + +/* + * There are 8 of each MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN + * configuration registers. That is, there are 8 miscellaneous registers, = MISC0 + * to MISC7. Each MISC register is associated with a setup; MISCN is assoc= iated + * with setup number N. The other 5 above mentioned types of registers have + * analogous structure. A setup is a set of those registers. For example, + * setup 1 comprises of MISC1, AFE1, FILTER1, FILTER_FS1, OFFSET1, and GAI= N1 + * registers. Also, there are 16 CHANNEL_SETUP registers (CHANNEL_SETUP0 to + * CHANNEL_SETUP15). Each channel setup is associated with one of the 8 po= ssible + * setups. Thus, AD4170 can support up to 16 channels but, since there are= only + * 8 available setups, channels must share settings if more than 8 channel= s are + * configured. + */ +struct ad4170_setup { + u16 misc; + u16 afe; + u16 filter; + u16 filter_fs; + u32 offset; /* For calibration purposes */ + u32 gain; /* For calibration purposes */ +}; + +struct ad4170_setup_info { + struct ad4170_setup setup; + unsigned int enabled_channels; + unsigned int channels; +}; + +struct ad4170_chan_info { + int setup_num; /* Index to access state setup_infos array */ + struct ad4170_setup setup; /* cached setup */ + int input_range_uv; + u32 scale_tbl[10][2]; + int offset_tbl[10]; + bool initialized; + bool enabled; +}; + +static const char * const ad4170_filt_names[] =3D { + [AD4170_SINC5_AVG] =3D "sinc5+avg", + [AD4170_SINC5] =3D "sinc5", + [AD4170_SINC3] =3D "sinc3", +}; + +struct ad4170_state { + struct regmap *regmap8; + struct regmap *regmap16; + struct regmap *regmap24; + struct spi_device *spi; + int vrefs_uv[AD4170_MAX_SUP]; + struct mutex lock; /* Protect read-modify-write and multi write sequences= */ + struct iio_chan_spec chans[AD4170_MAX_CHANNELS]; + struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS]; + struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS]; + u32 mclk_hz; + int pins_fn[AD4170_NUM_ANALOG_PINS]; + u32 int_pin_sel; + int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; + struct completion completion; + struct iio_trigger *trig; +}; + +static void ad4170_fill_sps_tbl(struct ad4170_state *st) +{ + unsigned int tmp0, tmp1, i; + + /* + * The ODR can be calculated the same way for sinc5+avg, sinc5, and + * sinc3 filter types with the exception that sinc5 filter has a + * narrowed range of allowed FILTER_FS values. + */ + for (i =3D 0; i < ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); i++) { + tmp0 =3D div_u64_rem(st->mclk_hz, 32 * ad4170_sinc3_filt_fs_tbl[i], + &tmp1); + tmp1 =3D mult_frac(tmp1, MICRO, 32 * ad4170_sinc3_filt_fs_tbl[i]); + /* Fill sinc5+avg filter SPS table */ + st->sps_tbl[AD4170_SINC5_AVG][i][0] =3D tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC5_AVG][i][1] =3D tmp1; /* Fractional part */ + + /* Fill sinc3 filter SPS table */ + st->sps_tbl[AD4170_SINC3][i][0] =3D tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC3][i][1] =3D tmp1; /* Fractional part */ + } + /* Sinc5 filter ODR doesn't use all FILTER_FS bits */ + for (i =3D 0; i < ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); i++) { + tmp0 =3D div_u64_rem(st->mclk_hz, 32 * ad4170_sinc5_filt_fs_tbl[i], + &tmp1); + tmp1 =3D mult_frac(tmp1, MICRO, 32 * ad4170_sinc5_filt_fs_tbl[i]); + /* Fill sinc5 filter SPS table */ + st->sps_tbl[AD4170_SINC5][i][0] =3D tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC5][i][1] =3D tmp1; /* Fractional part */ + } +} + +static const struct regmap_range ad4170_8bit_rd_reg_range[] =3D { + regmap_reg_range(AD4170_CONFIG_A_REG, AD4170_IF_STATUS_A_REG), +}; + +static const struct regmap_access_table ad4170_regmap8_rd_table =3D { + .yes_ranges =3D ad4170_8bit_rd_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_8bit_rd_reg_range), +}; + +static const struct regmap_range ad4170_8bit_wr_reg_range[] =3D { + regmap_reg_range(AD4170_CONFIG_A_REG, AD4170_DEV_CONFIG_REG), + regmap_reg_range(AD4170_SCRATCH_PAD_REG, AD4170_SCRATCH_PAD_REG), + regmap_reg_range(AD4170_CONFIG_C_REG, AD4170_IF_STATUS_A_REG), +}; + +static const struct regmap_access_table ad4170_regmap8_wr_table =3D { + .yes_ranges =3D ad4170_8bit_wr_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_8bit_wr_reg_range), +}; + +static const struct regmap_range ad4170_16bit_rd_reg_range[] =3D { + regmap_reg_range(AD4170_STATUS_REG, AD4170_DATA_16B_REG), + regmap_reg_range(AD4170_PIN_MUXING_REG, + AD4170_CHAN_MAP_REG(AD4170_MAX_CHANNELS)), + regmap_reg_range(AD4170_MISC_REG(0), AD4170_FILTER_FS_REG(0)), + regmap_reg_range(AD4170_MISC_REG(1), AD4170_FILTER_FS_REG(1)), + regmap_reg_range(AD4170_MISC_REG(2), AD4170_FILTER_FS_REG(2)), + regmap_reg_range(AD4170_MISC_REG(3), AD4170_FILTER_FS_REG(3)), + regmap_reg_range(AD4170_MISC_REG(4), AD4170_FILTER_FS_REG(4)), + regmap_reg_range(AD4170_MISC_REG(5), AD4170_FILTER_FS_REG(5)), + regmap_reg_range(AD4170_MISC_REG(6), AD4170_FILTER_FS_REG(6)), + regmap_reg_range(AD4170_MISC_REG(7), AD4170_FILTER_FS_REG(7)), + regmap_reg_range(AD4170_V_BIAS_REG, AD4170_FIR_CTRL), + regmap_reg_range(AD4170_COEFF_ADDR_REG, AD4170_GPIO_INPUT_REG), +}; + +static const struct regmap_access_table ad4170_regmap16_rd_table =3D { + .yes_ranges =3D ad4170_16bit_rd_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_16bit_rd_reg_range), +}; + +static const struct regmap_range ad4170_16bit_wr_reg_range[] =3D { + regmap_reg_range(AD4170_PIN_MUXING_REG, + AD4170_CHAN_MAP_REG(AD4170_MAX_CHANNELS)), + regmap_reg_range(AD4170_MISC_REG(0), AD4170_FILTER_FS_REG(0)), + regmap_reg_range(AD4170_MISC_REG(1), AD4170_FILTER_FS_REG(1)), + regmap_reg_range(AD4170_MISC_REG(2), AD4170_FILTER_FS_REG(2)), + regmap_reg_range(AD4170_MISC_REG(3), AD4170_FILTER_FS_REG(3)), + regmap_reg_range(AD4170_MISC_REG(4), AD4170_FILTER_FS_REG(4)), + regmap_reg_range(AD4170_MISC_REG(5), AD4170_FILTER_FS_REG(5)), + regmap_reg_range(AD4170_MISC_REG(6), AD4170_FILTER_FS_REG(6)), + regmap_reg_range(AD4170_MISC_REG(7), AD4170_FILTER_FS_REG(7)), + regmap_reg_range(AD4170_V_BIAS_REG, AD4170_FIR_CTRL), + regmap_reg_range(AD4170_COEFF_ADDR_REG, AD4170_GPIO_OUTPUT_REG), +}; + +static const struct regmap_access_table ad4170_regmap16_wr_table =3D { + .yes_ranges =3D ad4170_16bit_wr_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_16bit_wr_reg_range), +}; + +static const struct regmap_range ad4170_24bit_rd_reg_range[] =3D { + regmap_reg_range(AD4170_DATA_16B_STATUS_REG, AD4170_DATA_24B_REG), + regmap_reg_range(AD4170_OFFSET_REG(0), AD4170_GAIN_REG(0)), + regmap_reg_range(AD4170_OFFSET_REG(1), AD4170_GAIN_REG(1)), + regmap_reg_range(AD4170_OFFSET_REG(2), AD4170_GAIN_REG(2)), + regmap_reg_range(AD4170_OFFSET_REG(3), AD4170_GAIN_REG(3)), + regmap_reg_range(AD4170_OFFSET_REG(4), AD4170_GAIN_REG(4)), + regmap_reg_range(AD4170_OFFSET_REG(5), AD4170_GAIN_REG(5)), + regmap_reg_range(AD4170_OFFSET_REG(6), AD4170_GAIN_REG(6)), + regmap_reg_range(AD4170_OFFSET_REG(7), AD4170_GAIN_REG(7)), +}; + +static const struct regmap_access_table ad4170_regmap24_rd_table =3D { + .yes_ranges =3D ad4170_24bit_rd_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_24bit_rd_reg_range), +}; + +static const struct regmap_range ad4170_24bit_wr_reg_range[] =3D { + regmap_reg_range(AD4170_OFFSET_REG(0), AD4170_GAIN_REG(0)), + regmap_reg_range(AD4170_OFFSET_REG(1), AD4170_GAIN_REG(1)), + regmap_reg_range(AD4170_OFFSET_REG(2), AD4170_GAIN_REG(2)), + regmap_reg_range(AD4170_OFFSET_REG(3), AD4170_GAIN_REG(3)), + regmap_reg_range(AD4170_OFFSET_REG(4), AD4170_GAIN_REG(4)), + regmap_reg_range(AD4170_OFFSET_REG(5), AD4170_GAIN_REG(5)), + regmap_reg_range(AD4170_OFFSET_REG(6), AD4170_GAIN_REG(6)), + regmap_reg_range(AD4170_OFFSET_REG(7), AD4170_GAIN_REG(7)), +}; + +static const struct regmap_access_table ad4170_regmap24_wr_table =3D { + .yes_ranges =3D ad4170_24bit_wr_reg_range, + .n_yes_ranges =3D ARRAY_SIZE(ad4170_24bit_wr_reg_range), +}; + +static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret =3D -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (readval) { + if (regmap_check_range_table(st->regmap8, reg, + &ad4170_regmap8_rd_table)) + ret =3D regmap_read(st->regmap8, reg, readval); + + if (regmap_check_range_table(st->regmap16, reg, + &ad4170_regmap16_rd_table)) + ret =3D regmap_read(st->regmap16, reg, readval); + + if (regmap_check_range_table(st->regmap24, reg, + &ad4170_regmap24_rd_table)) + ret =3D regmap_read(st->regmap24, reg, readval); + } else { + if (regmap_check_range_table(st->regmap8, reg, + &ad4170_regmap8_wr_table)) + ret =3D regmap_write(st->regmap8, reg, writeval); + + if (regmap_check_range_table(st->regmap16, reg, + &ad4170_regmap16_wr_table)) + ret =3D regmap_write(st->regmap16, reg, writeval); + + if (regmap_check_range_table(st->regmap24, reg, + &ad4170_regmap24_wr_table)) + ret =3D regmap_write(st->regmap24, reg, writeval); + } + iio_device_release_direct(indio_dev); + + return ret; +} + +static const struct regmap_config ad4170_regmap8_config =3D { + .name =3D "ad4170-8", + .reg_bits =3D 16, + .val_bits =3D 8, + .max_register =3D AD4170_IF_STATUS_A_REG, + .rd_table =3D &ad4170_regmap8_rd_table, + .wr_table =3D &ad4170_regmap8_wr_table, + .read_flag_mask =3D BIT(6), + .zero_flag_mask =3D BIT(7), + .reg_format_endian =3D REGMAP_ENDIAN_BIG, + .val_format_endian =3D REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config ad4170_regmap16_config =3D { + .name =3D "ad4170-16", + .reg_bits =3D 16, + .val_bits =3D 16, + .rd_table =3D &ad4170_regmap16_rd_table, + .wr_table =3D &ad4170_regmap16_wr_table, + .read_flag_mask =3D BIT(6), + .zero_flag_mask =3D BIT(7), + .reg_format_endian =3D REGMAP_ENDIAN_BIG, + .val_format_endian =3D REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config ad4170_regmap24_config =3D { + .name =3D "ad4170-24", + .reg_bits =3D 16, + .val_bits =3D 24, + .rd_table =3D &ad4170_regmap24_rd_table, + .wr_table =3D &ad4170_regmap24_wr_table, + .read_flag_mask =3D BIT(6), + .zero_flag_mask =3D BIT(7), + .reg_format_endian =3D REGMAP_ENDIAN_BIG, + .val_format_endian =3D REGMAP_ENDIAN_BIG, +}; + +static int ad4170_find_setup(struct ad4170_state *st, + struct ad4170_setup *target_setup, + unsigned int *setup_num, bool *overwrite) +{ + unsigned int i; + + *setup_num =3D AD4170_INVALID_SETUP; + *overwrite =3D false; + + for (i =3D 0; i < AD4170_MAX_SETUPS; i++) { + struct ad4170_setup_info *setup_info =3D &st->setup_infos[i]; + + /* Immediately accept a matching setup. */ + if (!memcmp(target_setup, &setup_info->setup, + sizeof(*target_setup))) { + *setup_num =3D i; + return 0; + } + + /* Ignore all setups which are used by enabled channels. */ + if (setup_info->enabled_channels) + continue; + + /* Find the least used slot. */ + if (*setup_num =3D=3D AD4170_INVALID_SETUP || + setup_info->channels < st->setup_infos[*setup_num].channels) + *setup_num =3D i; + } + + if (*setup_num =3D=3D AD4170_INVALID_SETUP) + return -EINVAL; + + *overwrite =3D true; + return 0; +} + +static void ad4170_unlink_channel(struct ad4170_state *st, unsigned int ch= annel) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[channel]; + struct ad4170_setup_info *setup_info =3D &st->setup_infos[chan_info->setu= p_num]; + + chan_info->setup_num =3D AD4170_INVALID_SETUP; + setup_info->channels--; +} + +static int ad4170_unlink_setup(struct ad4170_state *st, unsigned int setup= _num) +{ + unsigned int i; + + for (i =3D 0; i < AD4170_MAX_CHANNELS; i++) { + struct ad4170_chan_info *chan_info =3D &st->chan_infos[i]; + + if (!chan_info->initialized || chan_info->setup_num !=3D setup_num) + continue; + + ad4170_unlink_channel(st, i); + } + return 0; +} + +static int ad4170_link_channel_setup(struct ad4170_state *st, + unsigned int chan_addr, + unsigned int setup_num) +{ + struct ad4170_setup_info *setup_info =3D &st->setup_infos[setup_num]; + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan_addr]; + int ret; + + ret =3D regmap_update_bits(st->regmap16, AD4170_CHAN_SETUP_REG(chan_addr), + AD4170_CHAN_SETUP_SETUP_MSK, + FIELD_PREP(AD4170_CHAN_SETUP_SETUP_MSK, + setup_num)); + if (ret) + return ret; + + chan_info->setup_num =3D setup_num; + setup_info->channels++; + return 0; +} + +/* + * Sets the ADC operating mode. Supported modes are + * - Continuous conversion mode (default) + * - Single conversion mode + * - Idle mode + */ +static int ad4170_set_mode(struct ad4170_state *st, unsigned int mode) +{ + return regmap_update_bits(st->regmap16, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, mode)); +} + +static int ad4170_write_setup(struct ad4170_state *st, unsigned int setup_= num, + struct ad4170_setup *setup) +{ + int ret; + + /* + * It is recommended to place the ADC in standby mode or idle mode to + * write to OFFSET and GAIN registers. + */ + ret =3D ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap16, AD4170_MISC_REG(setup_num), setup->mis= c); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap16, AD4170_AFE_REG(setup_num), setup->afe); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap16, AD4170_FILTER_REG(setup_num), + setup->filter); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap16, AD4170_FILTER_FS_REG(setup_num), + setup->filter_fs); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap24, AD4170_OFFSET_REG(setup_num), + setup->offset); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap24, AD4170_GAIN_REG(setup_num), setup->gai= n); + if (ret) + return ret; + + memcpy(&st->setup_infos[setup_num].setup, setup, sizeof(*setup)); + return 0; +} + +static int ad4170_write_channel_setup(struct ad4170_state *st, + unsigned int chan_addr, bool on_enable) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan_addr]; + bool overwrite; + int setup_num; + int ret; + + /* + * Similar to AD4130 driver, the following cases need to be handled. + * + * 1. Enabled and linked channel with setup changes: + * - Find a setup. If not possible, return error. + * - Unlink channel from current setup. + * - If the setup found has only disabled channels linked to it, + * unlink all channels, and write the new setup to it. + * - Link channel to new setup. + * + * 2. Soon to be enabled and unlinked channel: + * - Find a setup. If not possible, return error. + * - If the setup found has only disabled channels linked to it, + * unlink all channels, and write the new setup to it. + * - Link channel to the setup. + * + * 3. Disabled and linked channel with setup changes: + * - Unlink channel from current setup. + * + * 4. Soon to be enabled and linked channel: + * 5. Disabled and unlinked channel with setup changes: + * - Do nothing. + */ + + /* Case 4 */ + if (on_enable && chan_info->setup_num !=3D AD4170_INVALID_SETUP) + return 0; + + if (!on_enable && !chan_info->enabled) { + if (chan_info->setup_num !=3D AD4170_INVALID_SETUP) + /* Case 3 */ + ad4170_unlink_channel(st, chan_addr); + + /* Cases 3 & 5 */ + return 0; + } + + /* Cases 1 & 2 */ + ret =3D ad4170_find_setup(st, &chan_info->setup, &setup_num, &overwrite); + if (ret) + return ret; + + if (chan_info->setup_num !=3D AD4170_INVALID_SETUP) + /* Case 1 */ + ad4170_unlink_channel(st, chan_addr); + + if (overwrite) { + ret =3D ad4170_unlink_setup(st, setup_num); + if (ret) + return ret; + + ret =3D ad4170_write_setup(st, setup_num, &chan_info->setup); + if (ret) + return ret; + } + + return ad4170_link_channel_setup(st, chan_addr, setup_num); +} + +static int ad4170_set_channel_enable(struct ad4170_state *st, + unsigned int chan_addr, bool status) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan_addr]; + struct ad4170_setup_info *setup_info; + int ret; + + if (chan_info->enabled =3D=3D status) + return 0; + + if (status) { + ret =3D ad4170_write_channel_setup(st, chan_addr, true); + if (ret) + return ret; + } + + setup_info =3D &st->setup_infos[chan_info->setup_num]; + + ret =3D regmap_update_bits(st->regmap16, AD4170_CHAN_EN_REG, + AD4170_CHAN_EN(chan_addr), + status ? AD4170_CHAN_EN(chan_addr) : 0); + if (ret) + return ret; + + setup_info->enabled_channels +=3D status ? 1 : -1; + chan_info->enabled =3D status; + return 0; +} + +static int __ad4170_get_filter_type(unsigned int filter) +{ + u16 f_conf =3D FIELD_GET(AD4170_FILTER_FILTER_TYPE_MSK, filter); + + switch (f_conf) { + case AD4170_FILTER_FILTER_TYPE_SINC5_AVG: + return AD4170_SINC5_AVG; + case AD4170_FILTER_FILTER_TYPE_SINC5: + return AD4170_SINC5; + case AD4170_FILTER_FILTER_TYPE_SINC3: + return AD4170_SINC3; + default: + return -EINVAL; + } +} + +static int ad4170_set_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + unsigned int val) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + unsigned int old_filter_fs, old_filter, filter_type_conf; + int ret =3D 0; + + switch (val) { + case AD4170_SINC5_AVG: + filter_type_conf =3D AD4170_FILTER_FILTER_TYPE_SINC5_AVG; + break; + case AD4170_SINC5: + filter_type_conf =3D AD4170_FILTER_FILTER_TYPE_SINC5; + break; + case AD4170_SINC3: + filter_type_conf =3D AD4170_FILTER_FILTER_TYPE_SINC3; + break; + default: + return -EINVAL; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + /* + * The filters provide the same ODR for a given filter_fs value but + * there are different minimum and maximum filter_fs limits for each + * filter. The filter_fs value will be adjusted if the current filter_fs + * is out of the limits of the just requested filter. Since the + * filter_fs value affects the ODR (sampling_frequency), changing the + * filter may lead to a change in the sampling frequency. + */ + old_filter =3D setup->filter; + old_filter_fs =3D setup->filter_fs; + if (val =3D=3D AD4170_SINC5_AVG || val =3D=3D AD4170_SINC3) { + if (setup->filter_fs < AD4170_SINC3_MIN_FS) + setup->filter_fs =3D AD4170_SINC3_MIN_FS; + if (setup->filter_fs > AD4170_SINC3_MAX_FS) + setup->filter_fs =3D AD4170_SINC3_MAX_FS; + + } else if (val =3D=3D AD4170_SINC5) { + if (setup->filter_fs < AD4170_SINC5_MIN_FS) + setup->filter_fs =3D AD4170_SINC5_MIN_FS; + if (setup->filter_fs > AD4170_SINC5_MAX_FS) + setup->filter_fs =3D AD4170_SINC5_MAX_FS; + } + + setup->filter &=3D ~AD4170_FILTER_FILTER_TYPE_MSK; + setup->filter |=3D FIELD_PREP(AD4170_FILTER_FILTER_TYPE_MSK, + filter_type_conf); + + guard(mutex)(&st->lock); + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) { + setup->filter =3D old_filter; + setup->filter_fs =3D old_filter_fs; + } + + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4170_get_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + + return __ad4170_get_filter_type(setup->filter); +} + +static const struct iio_enum ad4170_filter_type_enum =3D { + .items =3D ad4170_filt_names, + .num_items =3D ARRAY_SIZE(ad4170_filt_names), + .get =3D ad4170_get_filter_type, + .set =3D ad4170_set_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = =3D { + 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 =3D { + .type =3D IIO_VOLTAGE, + .indexed =3D 1, + .differential =3D 1, + .info_mask_separate =3D 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 =3D BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info =3D ad4170_filter_type_ext_info, + .scan_type =3D { + .realbits =3D 24, + .storagebits =3D 32, + .endianness =3D IIO_BE, + }, +}; + +/* + * Receives the number of a multiplexed AD4170 input (ain_n), and stores t= he + * voltage (in =C2=B5V) of the specified input into ain_voltage. If the in= put number + * is a ordinary analog input (AIN0 to AIN8), stores zero into ain_voltage. + * If a voltage regulator required by a special input is unavailable, retu= rn + * error code. Return 0 on success. + */ +static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n, + int *ain_voltage) +{ + struct device *dev =3D &st->spi->dev; + + *ain_voltage =3D 0; + if (ain_n <=3D AD4170_CHAN_MAP_TEMP_SENSOR) + return 0; + + switch (ain_n) { + case AD4170_CHAN_MAP_AVDD_AVSS_N: + *ain_voltage =3D (st->vrefs_uv[AD4170_AVDD_SUP] + - st->vrefs_uv[AD4170_AVSS_SUP]) / 5; + return 0; + case AD4170_CHAN_MAP_IOVDD_DGND_N: + *ain_voltage =3D st->vrefs_uv[AD4170_IOVDD_SUP] / 5; + return 0; + case AD4170_CHAN_MAP_AVSS: + *ain_voltage =3D st->vrefs_uv[AD4170_AVSS_SUP]; + return 0; + case AD4170_CHAN_MAP_DGND: + *ain_voltage =3D 0; + return 0; + case AD4170_CHAN_MAP_REFIN1_P: + if (st->vrefs_uv[AD4170_REFIN1P_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN+ but ref not provided\n"); + + *ain_voltage =3D st->vrefs_uv[AD4170_REFIN1P_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN1_N: + if (st->vrefs_uv[AD4170_REFIN1N_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN- but ref not provided\n"); + + *ain_voltage =3D st->vrefs_uv[AD4170_REFIN1N_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN2_P: + if (st->vrefs_uv[AD4170_REFIN2P_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN2+ but ref not provided\n"); + + *ain_voltage =3D st->vrefs_uv[AD4170_REFIN2P_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN2_N: + if (st->vrefs_uv[AD4170_REFIN2N_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN2- but ref not provided\n"); + + *ain_voltage =3D st->vrefs_uv[AD4170_REFIN2N_SUP]; + return 0; + case AD4170_CHAN_MAP_REFOUT: + /* REFOUT is 2.5V relative to AVSS so take that into account */ + *ain_voltage =3D st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V; + return 0; + } + return -EINVAL; +} + +static int ad4170_validate_analog_input(struct ad4170_state *st, int pin) +{ + if (pin <=3D AD4170_MAX_ANALOG_PINS) { + if (st->pins_fn[pin] & AD4170_PIN_CURRENT_OUT) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Pin %d already used with fn %u.\n", + pin, st->pins_fn[pin]); + + st->pins_fn[pin] |=3D 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_CHAN_MAP_AVDD_AVSS_P || pin > AD4170_CHAN_MAP_RE= FOUT)) + 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 t= he + * input numbers. + * Returns 0 on valid channel input configuration. -EINVAL otherwise. + */ +static int ad4170_validate_channel(struct ad4170_state *st, + struct iio_chan_spec const *chan) +{ + int ret; + + ret =3D ad4170_validate_channel_input(st, chan->channel, false); + if (ret < 0) + return ret; + + return ad4170_validate_channel_input(st, chan->channel2, + !chan->differential); +} + +/* + * Verifies whether the channel configuration is valid by checking the pro= vided + * input type, polarity, and voltage references result in a sane input ran= ge. + * Returns negative error code on failure. + */ +static int ad4170_get_input_range(struct ad4170_state *st, + struct iio_chan_spec const *chan, + unsigned int ch_reg, unsigned int ref_sel) +{ + bool bipolar =3D chan->scan_type.sign =3D=3D 's'; + struct device *dev =3D &st->spi->dev; + int refp, refn, ain_voltage, ret; + + switch (ref_sel) { + case AD4170_REF_REFIN1: + if (st->vrefs_uv[AD4170_REFIN1P_SUP] =3D=3D -ENODEV || + st->vrefs_uv[AD4170_REFIN1N_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "REFIN+, REFIN=E2=88=92 selected but not provided\n"); + + refp =3D st->vrefs_uv[AD4170_REFIN1P_SUP]; + refn =3D st->vrefs_uv[AD4170_REFIN1N_SUP]; + break; + case AD4170_REF_REFIN2: + if (st->vrefs_uv[AD4170_REFIN2P_SUP] =3D=3D -ENODEV || + st->vrefs_uv[AD4170_REFIN2N_SUP] =3D=3D -ENODEV) + return dev_err_probe(dev, -ENODEV, + "REFIN2+, REFIN2=E2=88=92 selected but not provided\n"); + + refp =3D st->vrefs_uv[AD4170_REFIN2P_SUP]; + refn =3D st->vrefs_uv[AD4170_REFIN2N_SUP]; + break; + case AD4170_REF_AVDD: + refp =3D st->vrefs_uv[AD4170_AVDD_SUP]; + refn =3D st->vrefs_uv[AD4170_AVSS_SUP]; + break; + case AD4170_REF_REFOUT: + /* REFOUT is 2.5 V relative to AVSS */ + refp =3D st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V; + refn =3D st->vrefs_uv[AD4170_AVSS_SUP]; + break; + default: + return -EINVAL; + } + + /* + * Find out the analog input range from the channel type, polarity, and + * voltage reference selection. + * AD4170 channels are either differential or pseudo-differential. + * Diff input voltage range: =E2=88=92VREF/gain to +VREF/gain (datasheet = page 6) + * Pseudo-diff input voltage range: 0 to VREF/gain (datasheet page 6) + */ + if (chan->differential) { + if (!bipolar) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Channel %u differential unipolar\n", + ch_reg); + + /* + * Differential bipolar channel. + * avss-supply is never above 0V. + * Assuming refin1n-supply not above 0V. + * Assuming refin2n-supply not above 0V. + */ + return refp + abs(refn); + } + /* + * Some configurations can lead to invalid setups. + * For example, if AVSS =3D -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS), + * and pseudo-diff channel configuration set, then the input range + * should go from 0V to +VREF (single-ended - datasheet pg 10), but + * REFOUT/AVSS range would be -2.5V to 0V. + * Check the positive reference is higher than 0V for pseudo-diff + * channels. + */ + if (refp <=3D 0) + return dev_err_probe(&st->spi->dev, -EINVAL, + "REF+ <=3D GND for pseudo-diff chan %u\n", + ch_reg); + + if (bipolar) + return refp; + + /* + * Pseudo-differential unipolar channel. + * Input expected to swing from IN- to +VREF. + */ + ret =3D ad4170_get_ain_voltage_uv(st, chan->channel2, &ain_voltage); + if (ret < 0) + return ret; + + if (refp - ain_voltage <=3D 0) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Negative input >=3D REF+ for pseudo-diff chan %u\n", + ch_reg); + + return refp - ain_voltage; +} + +static int ad4170_read_sample(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int settling_time_ms, ret; + + guard(mutex)(&st->lock); + /* + * The ADC sequences through all enabled channels. That can lead to + * incorrect channel being sampled if a previous read would have left a + * different channel enabled. Thus, always enable and disable the + * channel on single-shot read. + */ + ret =3D ad4170_set_channel_enable(st, chan->address, true); + if (ret) + return ret; + + reinit_completion(&st->completion); + + ret =3D ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_SINGLE); + if (ret) + goto err_disable; + + /* + * When a channel is manually selected by the user, the ADC needs an + * extra time to provide the first stable conversion. The ADC settling + * time depends on the filter type, filter frequency, and ADC clock + * frequency (see datasheet page 53). The maximum settling time among + * all filter configurations is 6291164 / fCLK. Use that formula to wait + * for sufficient time whatever the filter configuration may be. + */ + settling_time_ms =3D DIV_ROUND_UP(6291164 * MILLI, st->mclk_hz); + ret =3D wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(settling_time_ms)); + if (!ret) + dev_dbg(&st->spi->dev, + "No Data Ready signal. Reading after delay.\n"); + + ret =3D regmap_read(st->regmap24, AD4170_DATA_24B_REG, val); + if (ret) + goto err_disable; + + if (chan->scan_type.sign =3D=3D 's') + *val =3D sign_extend32(*val, chan->scan_type.realbits - 1); + +err_disable: + ret =3D ad4170_set_channel_enable(st, chan->address, false); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4170_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + enum ad4170_filter_type f_type; + unsigned int pga, fs_idx; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D ad4170_read_sample(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + pga =3D FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); + *val =3D chan_info->scale_tbl[pga][0]; + *val2 =3D chan_info->scale_tbl[pga][1]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + pga =3D FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); + *val =3D chan_info->offset_tbl[pga]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + f_type =3D __ad4170_get_filter_type(setup->filter); + switch (f_type) { + case AD4170_SINC5_AVG: + fallthrough; + case AD4170_SINC3: + fs_idx =3D find_closest(setup->filter_fs, + ad4170_sinc3_filt_fs_tbl, + ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl)); + *val =3D st->sps_tbl[f_type][fs_idx][0]; + *val2 =3D st->sps_tbl[f_type][fs_idx][1]; + break; + case AD4170_SINC5: + fs_idx =3D find_closest(setup->filter_fs, + ad4170_sinc5_filt_fs_tbl, + ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl)); + *val =3D st->sps_tbl[f_type][fs_idx][0]; + *val2 =3D st->sps_tbl[f_type][fs_idx][1]; + break; + default: + return -EINVAL; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + *val =3D setup->offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val =3D setup->gain; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + int bipolar =3D chan->scan_type.sign =3D=3D 's' ? 1 : 0; + int precision_bits =3D chan->scan_type.realbits; + int pga, ainm_voltage, ret; + unsigned long long offset; + + ainm_voltage =3D 0; + ret =3D ad4170_get_ain_voltage_uv(st, chan->channel2, &ainm_voltage); + if (ret < 0) + return dev_err_probe(&st->spi->dev, ret, + "Failed to fill scale table\n"); + + for (pga =3D 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) { + u64 nv; + unsigned int lshift, rshift; + + /* + * The scale factor to get ADC output codes to values in mV + * units is given by: + * _scale =3D (input_range / gain) / 2^precision + * AD4170 gain is a power of 2 so the above can be written as + * _scale =3D input_range / 2^(precision + gain) + * Keep the input range in =C2=B5V to avoid truncating the less + * significan bits when right shifting it so to preserve scale + * precision. + */ + nv =3D (u64)chan_info->input_range_uv * NANO; + lshift =3D (pga >> 3 & 1); /* handle cases 8 and 9 */ + rshift =3D precision_bits - bipolar + (pga & 0x7) - lshift; + chan_info->scale_tbl[pga][0] =3D 0; + chan_info->scale_tbl[pga][1] =3D div_u64(nv >> rshift, MILLI); + + /* + * If the negative input is not at GND, the conversion result + * (which is relative to IN-) will be offset by the level at IN-. + * Use the scale factor the other way around to go from a known + * voltage to the corresponding ADC output code. + * With that, we are able to get to what would be the output + * code for the voltage at the negative input. + * If the negative input is not fixed, there is no offset. + */ + offset =3D ((unsigned long long)abs(ainm_voltage)) * MICRO; + offset =3D DIV_ROUND_CLOSEST_ULL(offset, chan_info->scale_tbl[pga][1]); + + /* + * After divided by the scale, offset will always fit into 31 + * bits. For _raw + _offset to be relative to GND, the value + * provided as _offset is of opposite sign than the real offset. + */ + if (ainm_voltage > 0) + chan_info->offset_tbl[pga] =3D -(int)(offset); + else + chan_info->offset_tbl[pga] =3D (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 =3D iio_priv(indio_dev); + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + enum ad4170_filter_type f_type; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals =3D (int *)chan_info->scale_tbl; + *length =3D ARRAY_SIZE(chan_info->scale_tbl) * 2; + *type =3D IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + f_type =3D ad4170_get_filter_type(indio_dev, chan); + switch (f_type) { + case AD4170_SINC5_AVG: + fallthrough; + case AD4170_SINC3: + *vals =3D (int *)st->sps_tbl[f_type]; + *length =3D ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) * 2; + break; + case AD4170_SINC5: + *vals =3D (int *)st->sps_tbl[f_type]; + *length =3D ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl) * 2; + break; + default: + return -EINVAL; + } + *type =3D IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4170_set_pga(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val, int val2) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + unsigned int old_pga =3D FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); + unsigned int pga; + int ret =3D 0; + + for (pga =3D 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) { + if (val =3D=3D chan_info->scale_tbl[pga][0] && + val2 =3D=3D chan_info->scale_tbl[pga][1]) + break; + } + + if (pga =3D=3D AD4170_NUM_PGA_OPTIONS) + return -EINVAL; + + if (pga =3D=3D old_pga) + return 0; + + setup->afe &=3D ~AD4170_AFE_PGA_GAIN_MSK; + setup->afe |=3D FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, pga); + + guard(mutex)(&st->lock); + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) { + setup->afe &=3D ~AD4170_AFE_PGA_GAIN_MSK; + setup->afe |=3D FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, old_pga); + } + + return ret; +} + +static int ad4170_set_channel_freq(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val, + int val2) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + enum ad4170_filter_type f_type =3D __ad4170_get_filter_type(setup->filter= ); + int filt_fs_tbl_size, i, ret =3D 0; + unsigned int old_filter_fs; + + switch (f_type) { + case AD4170_SINC5_AVG: + fallthrough; + case AD4170_SINC3: + filt_fs_tbl_size =3D ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); + break; + case AD4170_SINC5: + filt_fs_tbl_size =3D ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); + break; + } + + for (i =3D 0; i < filt_fs_tbl_size; i++) { + if (st->sps_tbl[f_type][i][0] =3D=3D val && + st->sps_tbl[f_type][i][1] =3D=3D val2) + break; + } + if (i >=3D filt_fs_tbl_size) + return -EINVAL; + + old_filter_fs =3D setup->filter_fs; + if (f_type =3D=3D AD4170_SINC5) + setup->filter_fs =3D ad4170_sinc5_filt_fs_tbl[i]; + else + setup->filter_fs =3D ad4170_sinc3_filt_fs_tbl[i]; + + guard(mutex)(&st->lock); + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) + setup->filter_fs =3D old_filter_fs; + + return ret; +} + +static int ad4170_set_calib_offset(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + u32 old_offset; + int ret; + + old_offset =3D setup->offset; + setup->offset =3D val; + + guard(mutex)(&st->lock); + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) + setup->offset =3D old_offset; + + return ret; +} + +static int ad4170_set_calib_gain(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val) +{ + struct ad4170_chan_info *chan_info =3D &st->chan_infos[chan->address]; + struct ad4170_setup *setup =3D &chan_info->setup; + u32 old_gain; + int ret; + + old_gain =3D setup->gain; + setup->gain =3D val; + + guard(mutex)(&st->lock); + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) + setup->gain =3D old_gain; + + return ret; +} + +static int __ad4170_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4170_set_pga(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4170_set_channel_freq(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad4170_set_calib_offset(st, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return ad4170_set_calib_gain(st, chan, val); + default: + return -EINVAL; + } +} + +static int ad4170_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D __ad4170_write_raw(indio_dev, chan, val, val2, info); + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info ad4170_info =3D { + .read_raw =3D ad4170_read_raw, + .read_avail =3D ad4170_read_avail, + .write_raw =3D ad4170_write_raw, + .write_raw_get_fmt =3D ad4170_write_raw_get_fmt, + .debugfs_reg_access =3D ad4170_debugfs_reg_access, +}; + +static int ad4170_soft_reset(struct ad4170_state *st) +{ + int ret; + + ret =3D regmap_write(st->regmap8, AD4170_CONFIG_A_REG, + AD4170_SW_RESET_MSK); + if (ret) + return ret; + + /* AD4170-4 requires 1 ms between reset and any register access. */ + fsleep(MILLI); + + return 0; +} + +static int ad4170_parse_reference(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup) +{ + struct device *dev =3D &st->spi->dev; + int ret; + u8 aux; + + /* Positive reference buffer setup */ + aux =3D AD4170_REF_BUF_PRE; /* Default to have precharge buffer enabled. = */ + ret =3D fwnode_property_read_u8(child, "adi,buffered-positive", &aux); + if (ret) { + if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,buffered-positive: %u\n", + aux); + } + setup->afe |=3D FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, aux); + + /* Negative reference buffer setup */ + aux =3D AD4170_REF_BUF_PRE; /* Default to have precharge buffer enabled. = */ + ret =3D fwnode_property_read_u8(child, "adi,buffered-negative", &aux); + if (ret) { + if (aux < AD4170_REF_BUF_PRE || aux > AD4170_REF_BUF_BYPASS) + return dev_err_probe(dev, -EINVAL, + "Invalid adi,buffered-negative: %u\n", + aux); + } + setup->afe |=3D FIELD_PREP(AD4170_AFE_REF_BUF_M_MSK, aux); + + /* Voltage reference selection */ + aux =3D AD4170_REF_REFOUT; /* Default reference selection. */ + fwnode_property_read_u8(child, "adi,reference-select", &aux); + if (aux > AD4170_REF_AVDD) + return dev_err_probe(dev, -EINVAL, + "Invalid reference selected %u\n", aux); + setup->afe |=3D FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, aux); + + return 0; +} + +static int ad4170_parse_adc_channel_type(struct device *dev, + struct fwnode_handle *child, + struct iio_chan_spec *chan) +{ + u32 pins[2]; + int ret; + + ret =3D fwnode_property_read_u32_array(child, "diff-channels", pins, + ARRAY_SIZE(pins)); + if (!ret) { + chan->differential =3D true; + chan->channel =3D pins[0]; + chan->channel2 =3D pins[1]; + return 0; + } + ret =3D fwnode_property_read_u32(child, "single-channel", &pins[0]); + if (!ret) { + chan->differential =3D false; + chan->channel =3D pins[0]; + + ret =3D 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 =3D pins[1]; + return 0; + } + return dev_err_probe(dev, ret, + "Channel must define one of diff-channels or single-channel.\n"); +} + +static int ad4170_parse_channel_node(struct iio_dev *indio_dev, + struct fwnode_handle *child, + unsigned int chan_num) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + struct ad4170_chan_info *chan_info; + struct ad4170_setup *setup; + struct iio_chan_spec *chan; + unsigned int ch_reg; + u8 ref_select; + bool bipolar; + int ret; + + ret =3D fwnode_property_read_u32(child, "reg", &ch_reg); + if (ret) + return ret; + + if (ch_reg >=3D AD4170_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "Channel idx greater than no of channels\n"); + + chan =3D &st->chans[chan_num]; + *chan =3D ad4170_channel_template; + + chan->address =3D ch_reg; + chan->scan_index =3D ch_reg; + chan_info =3D &st->chan_infos[chan->address]; + + chan_info->setup_num =3D AD4170_INVALID_SETUP; + chan_info->initialized =3D true; + + setup =3D &chan_info->setup; + ret =3D ad4170_parse_reference(st, child, setup); + if (ret) + return ret; + + ret =3D ad4170_parse_adc_channel_type(dev, child, chan); + if (ret < 0) + return ret; + + bipolar =3D fwnode_property_read_bool(child, "bipolar"); + setup->afe |=3D FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar); + if (bipolar) + chan->scan_type.sign =3D 's'; + else + chan->scan_type.sign =3D 'u'; + + ref_select =3D FIELD_GET(AD4170_AFE_REF_SELECT_MSK, setup->afe); + ret =3D ad4170_validate_channel(st, chan); + if (ret < 0) + return ret; + + ret =3D ad4170_get_input_range(st, chan, ch_reg, ref_select); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid input config\n"); + + chan_info->input_range_uv =3D ret; + return 0; +} + +static int ad4170_parse_channels(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + unsigned int num_channels; + unsigned int chan_num; + int ret; + + num_channels =3D device_get_child_node_count(dev); + + if (num_channels > AD4170_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, "Too many channels\n"); + + device_for_each_child_node_scoped(dev, child) { + ret =3D ad4170_parse_channel_node(indio_dev, child, chan_num++); + if (ret) + return ret; + } + + indio_dev->num_channels =3D num_channels; + indio_dev->channels =3D st->chans; + return 0; +} + +static int ad4170_parse_firmware(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + int reg_data, ret, i; + + st->mclk_hz =3D AD4170_INT_CLOCK_16MHZ; + + for (i =3D 0; i < AD4170_NUM_ANALOG_PINS; i++) + st->pins_fn[i] =3D AD4170_PIN_UNASIGNED; + + /* On power on, device defaults to using SDO pin for data ready signal */ + st->int_pin_sel =3D AD4170_INT_PIN_SDO; + ret =3D device_property_match_property_string(dev, "interrupt-names", + ad4170_int_pin_names, + ARRAY_SIZE(ad4170_int_pin_names)); + if (ret >=3D 0) + st->int_pin_sel =3D ret; + + reg_data =3D FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, + st->int_pin_sel =3D=3D AD4170_INT_PIN_DIG_AUX1 ? + AD4170_PIN_MUXING_DIG_AUX1_RDY : + AD4170_PIN_MUXING_DIG_AUX1_DISABLED); + + ret =3D regmap_update_bits(st->regmap16, AD4170_PIN_MUXING_REG, + AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, reg_data); + if (ret) + return ret; + + return ad4170_parse_channels(indio_dev); +} + +static int ad4170_initial_config(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + int i, ret; + + ad4170_fill_sps_tbl(st); + + ret =3D ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set ADC mode to idle\n"); + + for (i =3D 0; i < indio_dev->num_channels; i++) { + struct ad4170_chan_info *chan_info; + struct iio_chan_spec const *chan; + struct ad4170_setup *setup; + unsigned int val; + + chan =3D &indio_dev->channels[i]; + chan_info =3D &st->chan_infos[chan->address]; + + setup =3D &chan_info->setup; + setup->gain =3D AD4170_GAIN_REG_DEFAULT; + ret =3D ad4170_write_channel_setup(st, chan->address, false); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write channel setup\n"); + + val =3D FIELD_PREP(AD4170_CHAN_MAP_AINP_MSK, chan->channel) | + FIELD_PREP(AD4170_CHAN_MAP_AINM_MSK, chan->channel2); + + ret =3D regmap_write(st->regmap16, AD4170_CHAN_MAP_REG(i), val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write CHAN_MAP_REG\n"); + + ret =3D ad4170_set_channel_freq(st, chan, + AD4170_DEFAULT_SAMP_RATE, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set channel freq\n"); + + ret =3D ad4170_fill_scale_tbl(indio_dev, chan); + if (ret) + return dev_err_probe(dev, ret, + "Failed to fill scale tbl\n"); + } + + /* Disable all channels to avoid reading from unexpected channel */ + ret =3D regmap_write(st->regmap16, AD4170_CHAN_EN_REG, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to disable channels\n"); + + /* + * Configure channels to share the same data output register, i.e. data + * can be read from the same register address regardless of channel + * number. + */ + return regmap_update_bits(st->regmap16, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK); +} + +static const struct iio_trigger_ops ad4170_trigger_ops =3D { + .validate_device =3D iio_trigger_validate_own_device, +}; + +static irqreturn_t ad4170_irq_handler(int irq, void *dev_id) +{ + struct iio_dev *indio_dev =3D dev_id; + struct ad4170_state *st =3D iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int ad4170_trigger_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret; + + st->trig =3D devm_iio_trigger_alloc(indio_dev->dev.parent, "%s-trig%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops =3D &ad4170_trigger_ops; + st->trig->dev.parent =3D indio_dev->dev.parent; + + iio_trigger_set_drvdata(st->trig, indio_dev); + ret =3D devm_iio_trigger_register(indio_dev->dev.parent, st->trig); + if (ret) + return ret; + + indio_dev->trig =3D iio_trigger_get(st->trig); + + return request_irq(st->spi->irq, &ad4170_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); +} + +static int ad4170_regulator_setup(struct ad4170_state *st) +{ + struct device *dev =3D &st->spi->dev; + int ret; + + /* Required regulators */ + ret =3D devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get AVDD voltage.\n"); + + st->vrefs_uv[AD4170_AVDD_SUP] =3D ret; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get IOVDD voltage.\n"); + + st->vrefs_uv[AD4170_IOVDD_SUP] =3D ret; + + /* Optional regulators */ + ret =3D devm_regulator_get_enable_read_voltage(dev, "avss"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get AVSS voltage.\n"); + + /* Assume AVSS at GND (0V) if not provided */ + st->vrefs_uv[AD4170_AVSS_SUP] =3D ret =3D=3D -ENODEV ? 0 : -ret; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "refin1p"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN+ voltage.\n"); + + st->vrefs_uv[AD4170_REFIN1P_SUP] =3D ret; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "refin1n"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN- voltage.\n"); + + /* Negative supplies are assumed to provide negative voltage */ + st->vrefs_uv[AD4170_REFIN1N_SUP] =3D ret =3D=3D -ENODEV ? -ENODEV : -ret; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "refin2p"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN2+ voltage.\n"); + + st->vrefs_uv[AD4170_REFIN2P_SUP] =3D ret; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "refin2n"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN2- voltage.\n"); + + /* Negative supplies are assumed to provide negative voltage */ + st->vrefs_uv[AD4170_REFIN2N_SUP] =3D ret =3D=3D -ENODEV ? -ENODEV : -ret; + + return 0; +} + +static int ad4170_probe(struct spi_device *spi) +{ + struct device *dev =3D &spi->dev; + struct iio_dev *indio_dev; + struct ad4170_state *st; + const char *dev_name; + int ret; + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + devm_mutex_init(dev, &st->lock); + + dev_name =3D spi_get_device_match_data(spi); + if (!dev_name) + return -EINVAL; + + indio_dev->name =3D dev_name; + indio_dev->info =3D &ad4170_info; + + st->spi =3D spi; + st->regmap8 =3D devm_regmap_init_spi(spi, &ad4170_regmap8_config); + if (IS_ERR(st->regmap8)) + return dev_err_probe(dev, PTR_ERR(st->regmap8), + "Failed to initialize regmap8\n"); + + st->regmap16 =3D devm_regmap_init_spi(spi, &ad4170_regmap16_config); + if (IS_ERR(st->regmap16)) + return dev_err_probe(dev, PTR_ERR(st->regmap16), + "Failed to initialize regmap16\n"); + + st->regmap24 =3D devm_regmap_init_spi(spi, &ad4170_regmap24_config); + if (IS_ERR(st->regmap24)) + return dev_err_probe(dev, PTR_ERR(st->regmap24), + "Failed to initialize regmap24\n"); + + ret =3D ad4170_regulator_setup(st); + if (ret) + return ret; + + ret =3D ad4170_soft_reset(st); + if (ret) + return ret; + + ret =3D ad4170_parse_firmware(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse firmware\n"); + + ret =3D ad4170_initial_config(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup device\n"); + + init_completion(&st->completion); + + if (spi->irq) { + ret =3D ad4170_trigger_setup(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup trigger\n"); + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4170_id_table[] =3D { + { "ad4170", (kernel_ulong_t)ad4170_chip_names[0] }, + { "ad4190", (kernel_ulong_t)ad4170_chip_names[1] }, + { "ad4195", (kernel_ulong_t)ad4170_chip_names[2] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4170_id_table); + +static const struct of_device_id ad4170_of_match[] =3D { + { .compatible =3D "adi,ad4170", .data =3D ad4170_chip_names[0] }, + { .compatible =3D "adi,ad4190", .data =3D ad4170_chip_names[1] }, + { .compatible =3D "adi,ad4195", .data =3D ad4170_chip_names[2] }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4170_of_match); + +static struct spi_driver ad4170_driver =3D { + .driver =3D { + .name =3D "ad4170", + .of_match_table =3D ad4170_of_match, + }, + .probe =3D ad4170_probe, + .id_table =3D ad4170_id_table, +}; +module_spi_driver(ad4170_driver); + +MODULE_AUTHOR("Ana-Maria Cusco "); +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4170 SPI driver"); +MODULE_LICENSE("GPL"); --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7415725F7B0; Wed, 9 Apr 2025 12:25:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201536; cv=none; b=USQpxAv5C62AYKNjalU012sRjOs/aOPO//qXtMwF6lLGZ9ZNjz+6+JIt1kA5v30++b5rMoBuVvXkaeGR2XB1671W75cDTBkZIX1EPv9LNkaZQLGMsL+NVgLjra9tq9ZlNQqEgoMtfjuhpjfDGkRMkLO0LshcLJLRyaVMg27sXoI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201536; c=relaxed/simple; bh=EGUItf+bK08LHYWCFnRZTpGkZbeNSpjozLnBTnJrLz8=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=CgPRcdoVM98md7oVV7fjfIHYrK8Z4mV6n50WKPfQESEINRr86Z48dY+DgxdUzE5nv+VfuKygVcqu2QBHqQsuNuNoCxucgdQhmvMvnc+K+p/qH8HQHtZ5vJPW+EtT48GQK73VwmoqK33CwxjpPZQ9rqwgAsxOA1AJL7rrW5JSHq0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=meoDxLn9; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="meoDxLn9" Received: from pps.filterd (m0167088.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5399NqVT010181; Wed, 9 Apr 2025 08:25:19 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=7n+3E Uu+hNZYiuEqIVcb1zJJMo5A4zt3AC3M9IAj654=; b=meoDxLn99m1taapP53oBo UoiPbq/PiFHuaBNNHzuX+mDPcrBxZ+CG4ODt/I00GIQ/m4krmlZf+1Yff3ecc6uS 7ZLbMoq9KjnmrPVcxhLWzivLAQdE+WIzLpsmfoj21hHfoGGlsReY3TGeE3uuXlNo t4GVDcRLosZBvWULHyBbS7s5qB6Q0AY/2t7oL5E5TshNnPlAUN/K2qYaAUDRGYhd ttKhprIeHrg66b/KHyzli07e9yX5N80ro6WtyD+U4Zbe/VEwq3nIKI6lEzTO3GVe FFnLnQ7yBDf3O6bkLnQqm7mzM1mO4TdygX6bb0k1ax+RQq3NEL1opDGaYWReAonX w== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45txc66acb-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:25:19 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 539CPI4l041847 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:25:18 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:25:17 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:25:17 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CP4BG016951; Wed, 9 Apr 2025 08:25:06 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 3/7] iio: adc: ad4170: Add support for buffered data capture Date: Wed, 9 Apr 2025 09:25:02 -0300 Message-ID: <5f6d44ad4a4a5df088d91aa386229073f5364406.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-GUID: yhOtpzl7qMegOP6j56KItp2f4IF63lf3 X-Proofpoint-ORIG-GUID: yhOtpzl7qMegOP6j56KItp2f4IF63lf3 X-Authority-Analysis: v=2.4 cv=KePSsRYD c=1 sm=1 tr=0 ts=67f6672f cx=c_pps a=3WNzaoukacrqR9RwcOSAdA==:117 a=3WNzaoukacrqR9RwcOSAdA==:17 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=YGZcWCd7MsqG0SmrAXQA:9 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 bulkscore=0 impostorscore=0 suspectscore=0 spamscore=0 lowpriorityscore=0 clxscore=1015 mlxlogscore=999 adultscore=0 phishscore=0 mlxscore=0 priorityscore=1501 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090074 Content-Type: text/plain; charset="utf-8" Extend the AD4170 driver to allow buffered data capture in continuous read mode. In continuous read mode, the chip skips the instruction phase and outputs just ADC sample data, enabling faster sample rates to be reached. The internal channel sequencer always starts sampling from channel 0 and channel 0 must be enabled if more than one channel is selected for data capture. The scan mask validation callback checks the aforementioned condition is met. Signed-off-by: Marcelo Schmitt --- drivers/iio/adc/ad4170.c | 170 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index 0d24286ac2ab..5ffcdedf3e7f 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -10,10 +10,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -323,6 +325,16 @@ struct ad4170_state { int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; struct completion completion; struct iio_trigger *trig; + + struct spi_transfer xfer; + struct spi_message msg; + __be32 bounce_buffer[AD4170_MAX_CHANNELS]; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + __be32 rx_buf __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[2]; }; =20 static void ad4170_fill_sps_tbl(struct ad4170_state *st) @@ -882,6 +894,7 @@ static const struct iio_chan_spec ad4170_channel_templa= te =3D { .scan_type =3D { .realbits =3D 24, .storagebits =3D 32, + .shift =3D 8, .endianness =3D IIO_BE, }, }; @@ -1480,11 +1493,29 @@ static int ad4170_write_raw_get_fmt(struct iio_dev = *indio_dev, } } =20 +static int ad4170_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int chan_index; + int ret; + + guard(mutex)(&st->lock); + + iio_for_each_active_channel(indio_dev, chan_index) { + ret =3D ad4170_set_channel_enable(st, chan_index, true); + if (ret) + return ret; + } + return 0; +} + static const struct iio_info ad4170_info =3D { .read_raw =3D ad4170_read_raw, .read_avail =3D ad4170_read_avail, .write_raw =3D ad4170_write_raw, .write_raw_get_fmt =3D ad4170_write_raw_get_fmt, + .update_scan_mode =3D ad4170_update_scan_mode, .debugfs_reg_access =3D ad4170_debugfs_reg_access, }; =20 @@ -1759,6 +1790,130 @@ static int ad4170_initial_config(struct iio_dev *in= dio_dev) AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK); } =20 +static int ad4170_prepare_spi_message(struct ad4170_state *st) +{ + /* + * Continuous data register read is enabled on buffer postenable so + * no instruction phase is needed meaning we don't need to send the + * register address to read data. Transfer only needs the read buffer. + */ + st->xfer.rx_buf =3D &st->rx_buf; + st->xfer.len =3D BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits= ); + + spi_message_init_with_transfers(&st->msg, &st->xfer, 1); + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg); +} + +static int ad4170_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret; + + ret =3D ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_CONT); + if (ret < 0) + return ret; + + /* + * Enables continuous data register read. + * This enables continuous read of the ADC Data register. The ADC must + * be in a continuous conversion mode. + */ + return regmap_update_bits(st->regmap16, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_ENABLE)); +} + +static int ad4170_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret, i; + + /* + * To exit continuous read, write 0xA5 to the ADC during the first 8 + * SCLKs of the ADC data read. + */ + st->tx_buf[0] =3D AD4170_ADC_CTRL_CONT_READ_EXIT; + st->tx_buf[1] =3D 0; + ret =3D spi_write(st->spi, st->tx_buf, 2); + if (ret) + return ret; + + ret =3D regmap_update_bits(st->regmap16, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_DISABLE)); + if (ret) + return ret; + + ret =3D ad4170_set_mode(st, AD4170_ADC_CTRL_MODE_IDLE); + if (ret) + return ret; + + /* + * The ADC sequences through all the enabled channels (see datasheet + * page 95). That can lead to incorrect channel being read if a + * single-shot read (or buffered read with different active_scan_mask) + * is done after buffer disable. Disable all channels so only requested + * channels will be read. + */ + for (i =3D 0; i < indio_dev->num_channels; i++) { + ret =3D ad4170_set_channel_enable(st, i, false); + if (ret) + return ret; + } + return ret; +} + +static bool ad4170_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + unsigned int masklength =3D iio_get_masklength(indio_dev); + unsigned long first, next; + + /* + * The channel sequencer cycles through the enabled channels in + * sequential order, from channel 0 to channel 15, bypassing disabled + * channels. When more than one channel is enabled, channel 0 must + * always be enabled. See datasheet channel_en register description at + * page 95. + */ + first =3D find_next_bit(scan_mask, masklength, 0); + next =3D find_next_bit(scan_mask, masklength, first + 1); + if (next < masklength) + return test_bit(0, scan_mask); + + return true; +} + +static const struct iio_buffer_setup_ops ad4170_buffer_ops =3D { + .postenable =3D ad4170_buffer_postenable, + .predisable =3D ad4170_buffer_predisable, + .validate_scan_mask =3D ad4170_validate_scan_mask, +}; + +static irqreturn_t ad4170_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ad4170_state *st =3D iio_priv(indio_dev); + int i, ret; + + iio_for_each_active_channel(indio_dev, i) { + ret =3D spi_sync(st->spi, &st->msg); + if (ret) + goto err_out; + + st->bounce_buffer[i] =3D st->rx_buf; + } + + iio_push_to_buffers(indio_dev, st->bounce_buffer); +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + static const struct iio_trigger_ops ad4170_trigger_ops =3D { .validate_device =3D iio_trigger_validate_own_device, }; @@ -1768,7 +1923,10 @@ static irqreturn_t ad4170_irq_handler(int irq, void = *dev_id) struct iio_dev *indio_dev =3D dev_id; struct ad4170_state *st =3D iio_priv(indio_dev); =20 - complete(&st->completion); + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); =20 return IRQ_HANDLED; }; @@ -1915,6 +2073,16 @@ static int ad4170_probe(struct spi_device *spi) return dev_err_probe(dev, ret, "Failed to setup trigger\n"); } =20 + ret =3D ad4170_prepare_spi_message(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to prepare SPI message\n"); + + ret =3D devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &ad4170_trigger_handler, + &ad4170_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup read buffer\n"); + return devm_iio_device_register(dev, indio_dev); } =20 --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0b-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8DC4B2620C8; Wed, 9 Apr 2025 12:25:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201554; cv=none; b=hUE9v5XL/hK4fXhGxXbite2pAfhxfK/FY/QA384GlRV7wHg0thomr8ymt/Jt80FAsrwGYcjLwfscupSi6wU1MOgzld9lUIIPco13TmXd1ivDA/A3e2RJJ104lfWMj8vQYrjNkIhxkvOrAzRTW0OzjqFKCgnr7IAd4ERAavBswbg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201554; c=relaxed/simple; bh=tYJnQ974Zys7Ol/8cY+C5mpYQQIzGOy9TOcyoP6Rsj4=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Q326rBEifcWxDN4zXU+2qXAlbndNc91CdnUTIaVULnLlrglXkR+crby4uqDTxPizmkoblnpnsFdgTIVlKLMlgu0ErI44ALXeDX69zDOkDZuwkZ+FUGlsP/U/QaKbXJLiZIGJemoKtM45y+KEangVbbeRd+xsNUaz4tahqXfs2dM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=L5ZSYsuJ; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="L5ZSYsuJ" Received: from pps.filterd (m0375855.ppops.net [127.0.0.1]) by mx0b-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5398mI2B009329; Wed, 9 Apr 2025 08:25:36 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=6OGho wcTsy6Jc5v1+rS984AXxVoNr3xwOkUgP6ehtb8=; b=L5ZSYsuJRrIWahAyGUseV dqNn7Lmw1sa4VeryIHOQIghkcnRmsmxYf4VcB5+uj3xMtPA4AZNwZ99qaW6hNqr+ rCkYnpOd2dH3VzeN5zC89BepGL/RmuAZ+7iIIdN0SqDk02nunA9c+0rUdJ0frMtd WX9Lqb1h12F2cDZEiwG7/DUy2OMtBM9a/1cWwZxMdk4tKoNASvc3z2wG51aR77Te ozgnH8iw+mq9TqRX6VpQF4UH5L8HdMaCouTcPI73EnQjUYaEQbXZHWENa8N9MAt3 fc6xXgbM90k89TSBn8535NkRjuO/IjxuXXNecrSVxvj9JxvLEoPJaJN6I7OHMKeZ Q== Received: from nwd2mta3.analog.com ([137.71.173.56]) by mx0b-00128a01.pphosted.com (PPS) with ESMTPS id 45vvjr0sk6-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:25:36 -0400 (EDT) Received: from ASHBMBX9.ad.analog.com (ASHBMBX9.ad.analog.com [10.64.17.10]) by nwd2mta3.analog.com (8.14.7/8.14.7) with ESMTP id 539CPYSo052331 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:25:34 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:25:34 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:25:34 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CPL38016956; Wed, 9 Apr 2025 08:25:23 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 4/7] iio: adc: ad4170: Add clock provider support Date: Wed, 9 Apr 2025 09:25:20 -0300 Message-ID: <65b71e307d37b8e3e26937a1e67398b2af0af399.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Authority-Analysis: v=2.4 cv=Var3PEp9 c=1 sm=1 tr=0 ts=67f66740 cx=c_pps a=PpDZqlmH/M8setHirZLBMw==:117 a=PpDZqlmH/M8setHirZLBMw==:17 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=EjOB5vySZ_iNcyL_diIA:9 X-Proofpoint-ORIG-GUID: 6mE6HjjgTNH3pENmI9tujKMr46M-clYa X-Proofpoint-GUID: 6mE6HjjgTNH3pENmI9tujKMr46M-clYa X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 adultscore=0 clxscore=1015 mlxlogscore=999 suspectscore=0 phishscore=0 mlxscore=0 malwarescore=0 spamscore=0 impostorscore=0 lowpriorityscore=0 bulkscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090074 Content-Type: text/plain; charset="utf-8" The AD4170 chip can use an externally supplied clock at the XTAL2 pin, or an external crystal connected to the XTAL1 and XTAL2 pins. Alternatively, the AD4170 can provide it's 16 MHz internal clock at the XTAL2 pin. Extend the AD4170 driver so it effectively uses the provided external clock, if any, or supplies it's own clock as a clock provider. Signed-off-by: Marcelo Schmitt Reviewed-by: Nuno S=C3=A1 --- drivers/iio/adc/ad4170.c | 135 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index 5ffcdedf3e7f..97cf4465038f 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -7,6 +7,8 @@ =20 #include #include +#include +#include #include #include #include @@ -62,6 +64,7 @@ #define AD4170_DATA_16B_STATUS_REG 0x1A #define AD4170_DATA_24B_REG 0x1E #define AD4170_PIN_MUXING_REG 0x69 +#define AD4170_CLOCK_CTRL_REG 0x6B #define AD4170_ADC_CTRL_REG 0x71 #define AD4170_CHAN_EN_REG 0x79 #define AD4170_CHAN_SETUP_REG(x) (0x81 + 4 * (x)) @@ -89,6 +92,9 @@ #define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK GENMASK(5, 4) #define AD4170_PIN_MUXING_SYNC_CTRL_MSK GENMASK(3, 2) =20 +/* AD4170_CLOCK_CTRL_REG */ +#define AD4170_CLOCK_CTRL_CLOCKSEL_MSK GENMASK(1, 0) + /* AD4170_ADC_CTRL_REG */ #define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7) #define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4) @@ -121,6 +127,12 @@ =20 /* AD4170 register constants */ =20 +/* AD4170_CLOCK_CTRL_REG constants */ +#define AD4170_CLOCK_CTRL_CLOCKSEL_INT 0x0 +#define AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT 0x1 +#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT 0x2 +#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT_XTAL 0x3 + /* AD4170_CHAN_MAP_REG constants */ #define AD4170_CHAN_MAP_AIN0 0 #define AD4170_CHAN_MAP_AIN1 1 @@ -238,6 +250,10 @@ enum ad4170_regulator { AD4170_MAX_SUP }; =20 +static const char *const ad4170_clk_sel[] =3D { + "ext-clk", "xtal" +}; + enum ad4170_int_pin_sel { AD4170_INT_PIN_SDO, AD4170_INT_PIN_DIG_AUX1, @@ -320,6 +336,9 @@ struct ad4170_state { struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS]; struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS]; u32 mclk_hz; + unsigned int clock_ctrl; + struct clk *ext_clk; + struct clk_hw int_clk_hw; int pins_fn[AD4170_NUM_ANALOG_PINS]; u32 int_pin_sel; int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; @@ -1693,13 +1712,127 @@ static int ad4170_parse_channels(struct iio_dev *i= ndio_dev) return 0; } =20 +static struct ad4170_state *clk_hw_to_ad4170(struct clk_hw *hw) +{ + return container_of(hw, struct ad4170_state, int_clk_hw); +} + +static unsigned long ad4170_sel_clk(struct ad4170_state *st, + unsigned int clk_sel) +{ + st->clock_ctrl &=3D ~AD4170_CLOCK_CTRL_CLOCKSEL_MSK; + st->clock_ctrl |=3D FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, clk_sel); + return regmap_write(st->regmap16, AD4170_CLOCK_CTRL_REG, st->clock_ctrl); +} + +static unsigned long ad4170_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return AD4170_INT_CLOCK_16MHZ; +} + +static int ad4170_clk_output_is_enabled(struct clk_hw *hw) +{ + struct ad4170_state *st =3D clk_hw_to_ad4170(hw); + u32 clk_sel; + + clk_sel =3D FIELD_GET(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, st->clock_ctrl); + return clk_sel =3D=3D AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT; +} + +static int ad4170_clk_output_prepare(struct clk_hw *hw) +{ + struct ad4170_state *st =3D clk_hw_to_ad4170(hw); + + return ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT); +} + +static void ad4170_clk_output_unprepare(struct clk_hw *hw) +{ + struct ad4170_state *st =3D clk_hw_to_ad4170(hw); + + ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT); +} + +static const struct clk_ops ad4170_int_clk_ops =3D { + .recalc_rate =3D ad4170_clk_recalc_rate, + .is_enabled =3D ad4170_clk_output_is_enabled, + .prepare =3D ad4170_clk_output_prepare, + .unprepare =3D ad4170_clk_output_unprepare, +}; + +static int ad4170_register_clk_provider(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D indio_dev->dev.parent; + struct fwnode_handle *fwnode =3D dev_fwnode(dev); + struct clk_init_data init =3D {}; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name =3D fwnode_get_name(fwnode); + init.ops =3D &ad4170_int_clk_ops; + + st->int_clk_hw.init =3D &init; + ret =3D devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad4170_clock_select(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + int ret; + + st->mclk_hz =3D AD4170_INT_CLOCK_16MHZ; + ret =3D device_property_match_property_string(dev, "clock-names", + ad4170_clk_sel, + ARRAY_SIZE(ad4170_clk_sel)); + if (ret < 0) { + /* Use internal clock reference */ + st->clock_ctrl |=3D FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, + AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT); + return ad4170_register_clk_provider(indio_dev); + } + + /* Use external clock reference */ + st->ext_clk =3D devm_clk_get_enabled(dev, ad4170_clk_sel[ret]); + if (IS_ERR(st->ext_clk)) + return dev_err_probe(dev, PTR_ERR(st->ext_clk), + "Failed to get external clock\n"); + + st->clock_ctrl |=3D FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, + AD4170_CLOCK_CTRL_CLOCKSEL_EXT + ret); + + st->mclk_hz =3D clk_get_rate(st->ext_clk); + if (st->mclk_hz < AD4170_EXT_CLOCK_MHZ_MIN || + st->mclk_hz > AD4170_EXT_CLOCK_MHZ_MAX) { + return dev_err_probe(dev, -EINVAL, + "Invalid external clock frequency %u\n", + st->mclk_hz); + } + return 0; +} + static int ad4170_parse_firmware(struct iio_dev *indio_dev) { struct ad4170_state *st =3D iio_priv(indio_dev); struct device *dev =3D &st->spi->dev; int reg_data, ret, i; =20 - st->mclk_hz =3D AD4170_INT_CLOCK_16MHZ; + ret =3D ad4170_clock_select(indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to setup device clock\n"); + + ret =3D regmap_write(st->regmap16, AD4170_CLOCK_CTRL_REG, st->clock_ctrl); + if (ret) + return ret; =20 for (i =3D 0; i < AD4170_NUM_ANALOG_PINS; i++) st->pins_fn[i] =3D AD4170_PIN_UNASIGNED; --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F43625F7BA; Wed, 9 Apr 2025 12:26:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201573; cv=none; b=ZV453OoQbLSNonql+/S/+/ehZfFRmLgLeREMtNvvByu1Yrpwa6/Ug4bWz5RuA/LQ9g3yJVyCB1gBz/pkYzdna+bn6NUri650lf0/YhCWHMEWhE3mZYx3e3UaQWVXhPIKFOPI32kGdkwGZ5y8Auu5nVImvOLYRi6d/R/maYzmsTk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201573; c=relaxed/simple; bh=9tfM/kaooL9C4Rth+aizrQuILx26QObxp9PHSlsx2OU=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=oLl5uLWgyMdEf8SwWbljyB/Rb5OEBFuqXptrcf7BK4jtt0I0fIsvlLtanBXTu5LH5LcddL5ZIDVnMoRhd+zoACwOan3eyiVomcj1QmIWm/O/LTRoRVVxMQXcO1nqNf33rdK8YvnWwb2dod/go+EWLvprpGMUx/UoQBZ/K9nrM30= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=BrANpxds; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="BrANpxds" Received: from pps.filterd (m0167089.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 53999D10002733; Wed, 9 Apr 2025 08:25:54 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=u5Qxr F9u32dWo5z+bCbwinzVK+Nus/W8Cfj2qOJhRZk=; b=BrANpxdsEO//LVn5AXGDQ IQH84PQQooLzVk14i+R+OB++I1CiPG/qHNKTAjtXsV2wz17laINmxIZOIMfMpSIm 6gLI3ibkTX9VRkaURkbqtj+JmR5gbta70bzSXJ935H+9xpdxS8kDzU6OX0H+OXuW PY7E3J4f1V0uLfLxg1J2Nhzs5Omosf9kG1Y9E0Sx83SewtOemIGxEembRIX6Odkz mI1CSALRClI0zsqs8HHY6QcM0+m9CJWNNXScsndxMY4DkjL4ABeA94jna2WvWbh6 cgGf0A51vpTrblSN2yP0mPW80erluF91NLRyBnvMSMstXDOvDIqldacBW6+iOXEH w== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45u1e6dquu-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:25:53 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 539CPqpT041950 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:25:52 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:25:52 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:25:52 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CPbSL016960; Wed, 9 Apr 2025 08:25:39 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 5/7] iio: adc: ad4170: Add GPIO controller support Date: Wed, 9 Apr 2025 09:25:35 -0300 Message-ID: <247566f848cdf2a245a8b6da6a84c22e155beeb7.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-ORIG-GUID: VO6Yi72DCrhj3f3zFaCuobqc67y-u4z2 X-Authority-Analysis: v=2.4 cv=cdjSrmDM c=1 sm=1 tr=0 ts=67f66752 cx=c_pps a=3WNzaoukacrqR9RwcOSAdA==:117 a=3WNzaoukacrqR9RwcOSAdA==:17 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=26uHqrsnSEcdkGl8FGcA:9 X-Proofpoint-GUID: VO6Yi72DCrhj3f3zFaCuobqc67y-u4z2 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 lowpriorityscore=0 impostorscore=0 spamscore=0 malwarescore=0 priorityscore=1501 adultscore=0 mlxscore=0 clxscore=1015 mlxlogscore=999 phishscore=0 bulkscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090073 Content-Type: text/plain; charset="utf-8" The AD4170 has four multifunctional pins that can be used as GPIOs. The GPIO functionality can be accessed when the AD4170 chip is not busy performing continuous data capture or handling any other register read/write request. Also, the AD4170 does not provide any interrupt based on GPIO pin states so AD4170 GPIOs can't be used as interrupt sources. Implement gpio_chip callbacks so to make AD4170 GPIO pins controllable through the gpiochip interface. Signed-off-by: Marcelo Schmitt --- drivers/iio/adc/ad4170.c | 167 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index 97cf4465038f..b382e7f3dbe0 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ #define AD4170_FIR_CTRL 0x141 #define AD4170_COEFF_DATA_REG 0x14A #define AD4170_COEFF_ADDR_REG 0x14C +#define AD4170_GPIO_MODE_REG 0x191 #define AD4170_GPIO_OUTPUT_REG 0x193 #define AD4170_GPIO_INPUT_REG 0x195 =20 @@ -189,6 +191,7 @@ /* Device properties and auxiliary constants */ =20 #define AD4170_NUM_ANALOG_PINS 9 +#define AD4170_NUM_GPIO_PINS 4 #define AD4170_MAX_CHANNELS 16 #define AD4170_MAX_ANALOG_PINS 8 #define AD4170_MAX_SETUPS 8 @@ -340,6 +343,7 @@ struct ad4170_state { struct clk *ext_clk; struct clk_hw int_clk_hw; int pins_fn[AD4170_NUM_ANALOG_PINS]; + struct gpio_chip gpiochip; u32 int_pin_sel; int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; struct completion completion; @@ -1553,6 +1557,156 @@ static int ad4170_soft_reset(struct ad4170_state *s= t) return 0; } =20 +static int ad4170_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct iio_dev *indio_dev =3D gpiochip_get_data(gc); + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D regmap_read(st->regmap16, AD4170_GPIO_MODE_REG, &val); + if (ret) + goto err_release; + + /* + * If the GPIO is configured as an input, read the current value from + * AD4170_GPIO_INPUT_REG. Otherwise, read the input value from + * AD4170_GPIO_OUTPUT_REG. + */ + if (val & BIT(offset * 2)) + ret =3D regmap_read(st->regmap16, AD4170_GPIO_INPUT_REG, &val); + else + ret =3D regmap_read(st->regmap16, AD4170_GPIO_OUTPUT_REG, &val); + if (ret) + goto err_release; + + ret =3D !!(val & BIT(offset)); +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_set(struct gpio_chip *gc, unsigned int offset, int = value) +{ + struct iio_dev *indio_dev =3D gpiochip_get_data(gc); + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D regmap_read(st->regmap16, AD4170_GPIO_MODE_REG, &val); + if (ret) + goto err_release; + + if (val & BIT(offset * 2 + 1)) + ret =3D regmap_update_bits(st->regmap16, AD4170_GPIO_OUTPUT_REG, + BIT(offset), value << offset); + +err_release: + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4170_gpio_get_direction(struct gpio_chip *gc, unsigned int of= fset) +{ + struct iio_dev *indio_dev =3D gpiochip_get_data(gc); + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D regmap_read(st->regmap16, AD4170_GPIO_MODE_REG, &val); + if (ret) + goto err_release; + + if (val & BIT(offset * 2 + 1)) + ret =3D GPIO_LINE_DIRECTION_OUT; + else + ret =3D GPIO_LINE_DIRECTION_IN; + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_direction_input(struct gpio_chip *gc, unsigned int = offset) +{ + struct iio_dev *indio_dev =3D gpiochip_get_data(gc); + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D regmap_clear_bits(st->regmap16, AD4170_GPIO_MODE_REG, + BIT(offset * 2 + 1)); + if (ret) + goto err_release; + + ret =3D regmap_set_bits(st->regmap16, AD4170_GPIO_MODE_REG, + BIT(offset * 2)); + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct iio_dev *indio_dev =3D gpiochip_get_data(gc); + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret =3D regmap_clear_bits(st->regmap16, AD4170_GPIO_MODE_REG, + BIT(offset * 2)); + if (ret) + goto err_release; + + ret =3D regmap_set_bits(st->regmap16, AD4170_GPIO_MODE_REG, + BIT(offset * 2 + 1)); + +err_release: + iio_device_release_direct(indio_dev); + + ad4170_gpio_set(gc, offset, value); + return ret; +} + +static int ad4170_gpio_init(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + + st->gpiochip =3D (struct gpio_chip) { + .label =3D "ad4170_gpios", + .base =3D -1, + .ngpio =3D 4, + .parent =3D &st->spi->dev, + .can_sleep =3D true, + .get_direction =3D ad4170_gpio_get_direction, + .direction_input =3D ad4170_gpio_direction_input, + .direction_output =3D ad4170_gpio_direction_output, + .get =3D ad4170_gpio_get, + .set_rv =3D ad4170_gpio_set, + .owner =3D THIS_MODULE, + }; + + return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev); +} + static int ad4170_parse_reference(struct ad4170_state *st, struct fwnode_handle *child, struct ad4170_setup *setup) @@ -1855,7 +2009,18 @@ static int ad4170_parse_firmware(struct iio_dev *ind= io_dev) if (ret) return ret; =20 - return ad4170_parse_channels(indio_dev); + ret =3D ad4170_parse_channels(indio_dev); + if (ret) + return ret; + + /* Only create a GPIO chip if flagged for it */ + if (device_property_read_bool(&st->spi->dev, "gpio-controller")) { + ret =3D ad4170_gpio_init(indio_dev); + if (ret < 0) + return ret; + } + + return 0; } =20 static int ad4170_initial_config(struct iio_dev *indio_dev) --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 305CA25F793; Wed, 9 Apr 2025 12:26:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201600; cv=none; b=Tnosl9kwQOOd0Rd/T+4qXdw0BYp59R0li8p1B7iZrg6dLdyFYAOeHK6HXRQO723BSs6cdr4+X2eG8ffuSTeLU6Uefh/1v5xngW5p5P1p0WP3Q8O1Sq5MnBsbYZKxafPn6Z1frOGfL1r66uXPWlnMedILK0KC1Hsk7DmuHGQl2k0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201600; c=relaxed/simple; bh=QcKK2U+2tUOL2j7TDY2TCatzACyLM65E5Y1E3wSeLwk=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=G46Az+5xLt1O/jXyTsjC5DBakp4edqXrsAxQENNZ3yMGFsTAKJTtf6TP4U137ZA03eXtRFkVokQ944H0zqtlBMkadSYSNyzD7z6tW/RfKjGzzPGONF5V0JvMxIhazwpcj++L1GNVlLUgXStG/F0UlHCgVafiv9NPeQ0AJDyI+zc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=HE2pd0Ix; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="HE2pd0Ix" Received: from pps.filterd (m0167089.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5399QrKx002764; Wed, 9 Apr 2025 08:26:25 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=hS5Zw kntZFL1o8dtnWK8VrXGimZrhUHS5zbQQp3JWYg=; b=HE2pd0Ix1IvNGv1MwaGfi 8Zde7Rd1i1Jfr6srx8NW3wFhTTQ+ip0fGtWu6Y0xApeWmUII5Nz1urIn1zVy8jZX p0oVwKrx6pe4ICoEl3JBLDy7HzpUvx9LsLJ7f2pWrL1NN8/1/zwhXC4nXT4sRXuo YcErdXVfdMuhaQJtgCfsIAFsxH/W7X824q9eCX3JrdYL1tv6ookX+6cwfiCPqG3f WkZ7uFRxNOGQo3Z+lktkqqVekGigFjCT3wbQd44OH6iJJGhBi5kW8hsJ/zqYbaQP HtiUQVbsAkUFB9x1o5UsmhK3fhzsg6a3++WS2yor2tW8rCQLf+3abSEJ/xs1Eyph A== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45u1e6dqwn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:26:24 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 539CQNrU041991 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:26:23 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:26:23 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:26:23 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CQ7Es016969; Wed, 9 Apr 2025 08:26:10 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 6/7] iio: adc: ad4170: Add support for internal temperature sensor Date: Wed, 9 Apr 2025 09:26:06 -0300 Message-ID: <33ed56211aed85df1bcb86d5fea83991441dbec0.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-ORIG-GUID: 4P1jzgQxKGkpBfwsoyYRdq2ymJtB4xXG X-Authority-Analysis: v=2.4 cv=cdjSrmDM c=1 sm=1 tr=0 ts=67f66770 cx=c_pps a=3WNzaoukacrqR9RwcOSAdA==:117 a=3WNzaoukacrqR9RwcOSAdA==:17 a=IkcTkHD0fZMA:10 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=o3DtMFfdRpqB0q9FUmIA:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 X-Proofpoint-GUID: 4P1jzgQxKGkpBfwsoyYRdq2ymJtB4xXG X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 lowpriorityscore=0 impostorscore=0 spamscore=0 malwarescore=0 priorityscore=1501 adultscore=0 mlxscore=0 clxscore=1015 mlxlogscore=999 phishscore=0 bulkscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090073 The AD4170 has an internal temperature sensor that can be read using the ADC. Whenever possible, configure an IIO channel to provide the chip's temperature. Signed-off-by: Marcelo Schmitt Reviewed-by: Nuno S=C3=A1 --- drivers/iio/adc/ad4170.c | 72 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index b382e7f3dbe0..d204f8ca840f 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -922,6 +922,27 @@ static const struct iio_chan_spec ad4170_channel_templ= ate =3D { }, }; =20 +static const struct iio_chan_spec ad4170_temp_channel_template =3D { + .type =3D IIO_TEMP, + .indexed =3D 0, + .channel =3D 17, + .channel2 =3D 17, + .info_mask_separate =3D 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 =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type =3D { + .sign =3D 's', + .realbits =3D 24, + .storagebits =3D 32, + .shift =3D 8, + .endianness =3D IIO_BE, + }, +}; + /* * Receives the number of a multiplexed AD4170 input (ain_n), and stores t= he * voltage (in =C2=B5V) of the specified input into ain_voltage. If the in= put number @@ -1209,9 +1230,27 @@ static int ad4170_read_raw(struct iio_dev *indio_dev, return ret; case IIO_CHAN_INFO_SCALE: pga =3D FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); - *val =3D chan_info->scale_tbl[pga][0]; - *val2 =3D chan_info->scale_tbl[pga][1]; - return IIO_VAL_INT_PLUS_NANO; + switch (chan->type) { + case IIO_VOLTAGE: + *val =3D chan_info->scale_tbl[pga][0]; + *val2 =3D chan_info->scale_tbl[pga][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + /* + * The scale_tbl converts output codes to mV units so + * multiply by MILLI to make the factor convert to =C2=B5V. + * Then, apply the temperature sensor change sensitivity + * of 477 =CE=BCV/K. Finally, multiply the result by MILLI + * again to comply with milli degrees Celsius IIO ABI. + */ + *val =3D 0; /* The scale integer part is always 0. */ + *val2 =3D DIV_ROUND_CLOSEST(chan_info->scale_tbl[pga][1] * MILLI, + 477) * MILLI; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } case IIO_CHAN_INFO_OFFSET: pga =3D FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); *val =3D chan_info->offset_tbl[pga]; @@ -1855,12 +1894,39 @@ static int ad4170_parse_channels(struct iio_dev *in= dio_dev) if (num_channels > AD4170_MAX_CHANNELS) return dev_err_probe(dev, -EINVAL, "Too many channels\n"); =20 + /* Add one for temperature */ + num_channels =3D min(num_channels + 1, AD4170_MAX_CHANNELS); + device_for_each_child_node_scoped(dev, child) { ret =3D ad4170_parse_channel_node(indio_dev, child, chan_num++); if (ret) return ret; } =20 + /* + * Add internal temperature sensor channel if the maximum number of + * channels has not been reached. + */ + if (num_channels < AD4170_MAX_CHANNELS) { + struct ad4170_setup *setup =3D &st->chan_infos[chan_num].setup; + + st->chans[chan_num] =3D ad4170_temp_channel_template; + st->chans[chan_num].address =3D chan_num; + st->chans[chan_num].scan_index =3D chan_num; + + st->chan_infos[chan_num].setup_num =3D AD4170_INVALID_SETUP; + st->chan_infos[chan_num].initialized =3D true; + + setup->afe |=3D FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, + AD4170_REF_AVDD); + + ret =3D ad4170_get_input_range(st, &st->chans[chan_num], chan_num, + AD4170_REF_AVDD); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid input config\n"); + + st->chan_infos[chan_num].input_range_uv =3D ret; + } indio_dev->num_channels =3D num_channels; indio_dev->channels =3D st->chans; return 0; --=20 2.47.2 From nobody Sat Feb 7 08:43:03 2026 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EAAAC245031; Wed, 9 Apr 2025 12:26:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201618; cv=none; b=mSJ6DJr5L5Lfpx6aQiCZrjx82hT2JF6V55Zp63rUEYNUFKlSI1LXF3dXRKynQtoSUM+1b1Dp95S7CYggqxHf7RilPkbg0ao099lp0tNPmsK/rJpmxClQXjBYHYnLfvpI9/K7plqwGBCjtmFWc4ezkCNkDRu4THpX7h4Gn7/X+wA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744201618; c=relaxed/simple; bh=7ly4YrsdN0QW2qLgBuSqLWWxmoMTauIezIw79eucUw0=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=V5vg9RKzL3Gsp+uQH0lNg6HUh8Zsf9hjobvVLa3tfI7HrYaA73h+M9RnIzIb2EZ9fwccI4f9IXFJfckCPzBUQ5Ba/A5jtWxPzepwom6ayav8nIoSdZgO9kSeDuyXuLbuDSWY0Ta118vS4alewKfywN9wH0QbFVZ1uyCzDnHtgBE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=UOhhc1wZ; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="UOhhc1wZ" Received: from pps.filterd (m0167089.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 5399eLs6002723; Wed, 9 Apr 2025 08:26:41 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=h/v+6 mD3bVqnrnTTP8KGuWn1+wyqY9qGLX+GKjFwdC8=; b=UOhhc1wZe1wQdcJMLg38/ 0CGrjtIiSWufiUUOE4cL306PIDOoGHqrB0UY6tETfkVtkVg5DD4U57mCLc04WhdR 8uZJEsNW7e5V6SDqan9JE0ZXVJjqcoBDQX4j7msLcMOZGPqrjPm+PlTRCaz9y6SF 2ofLkmko9Utttehdu2Nt7dR0Z1IwYzDJHYuT4ojZ1rxE83LP92b+3D5kep81/SVc 0L59X3IrhUDOOY7sqz4ky6biILzwBxgh7yZQ35LgU4ICmjNP71UvmUHsNo9puDd3 G8efZEsMYpIB4mE5hV3/xfM+iyV8LqJTuP222NnAtlGrX367Z92TmxZRkinu0rtC g== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 45u1e6dqxe-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 09 Apr 2025 08:26:40 -0400 (EDT) Received: from ASHBMBX9.ad.analog.com (ASHBMBX9.ad.analog.com [10.64.17.10]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 539CQd1u042024 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 9 Apr 2025 08:26:39 -0400 Received: from ASHBCASHYB5.ad.analog.com (10.64.17.133) by ASHBMBX9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:26:39 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBCASHYB5.ad.analog.com (10.64.17.133) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.14; Wed, 9 Apr 2025 08:26:39 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Wed, 9 Apr 2025 08:26:39 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 539CQNMr016975; Wed, 9 Apr 2025 08:26:25 -0400 From: Marcelo Schmitt To: , , CC: , , , , , , , , , Subject: [PATCH v1 7/7] iio: adc: ad4170: Add support for weigh scale and RTD sensors Date: Wed, 9 Apr 2025 09:26:22 -0300 Message-ID: <56e76070b72d15950bf1fb01e68e94c42e79905b.1744200264.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-ORIG-GUID: SkYVrmzOGcXFMGzjW5cK3-1w-Uo0dd5A X-Authority-Analysis: v=2.4 cv=cdjSrmDM c=1 sm=1 tr=0 ts=67f66780 cx=c_pps a=3WNzaoukacrqR9RwcOSAdA==:117 a=3WNzaoukacrqR9RwcOSAdA==:17 a=XR8D0OoHHMoA:10 a=gAnH3GRIAAAA:8 a=EiBr_KzpBCXFKVrstIUA:9 X-Proofpoint-GUID: SkYVrmzOGcXFMGzjW5cK3-1w-Uo0dd5A X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1095,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-04-09_04,2025-04-08_04,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 lowpriorityscore=0 impostorscore=0 spamscore=0 malwarescore=0 priorityscore=1501 adultscore=0 mlxscore=0 clxscore=1015 mlxlogscore=999 phishscore=0 bulkscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2502280000 definitions=main-2504090073 Content-Type: text/plain; charset="utf-8" The AD4170 design has features to aid interfacing with weigh scale and RTD sensors that are expected to be setup with external circuitry for proper sensor operation. A key characteristic of those sensors is that the circuit they are in must be excited with a pair of signals. The external circuit can be excited either by voltage supply or by AD4170 excitation signals. The sensor can then be read through a different pair of lines that are connected to AD4170 ADC. Configure AD4170 to handle external circuit sensors. Signed-off-by: Marcelo Schmitt --- drivers/iio/adc/ad4170.c | 341 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 338 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad4170.c b/drivers/iio/adc/ad4170.c index d204f8ca840f..2cf578608316 100644 --- a/drivers/iio/adc/ad4170.c +++ b/drivers/iio/adc/ad4170.c @@ -77,6 +77,7 @@ #define AD4170_OFFSET_REG(x) (0xCA + 14 * (x)) #define AD4170_GAIN_REG(x) (0xCD + 14 * (x)) #define AD4170_V_BIAS_REG 0x135 +#define AD4170_CURRENT_SRC_REG(x) (0x139 + 2 * (x)) #define AD4170_FIR_CTRL 0x141 #define AD4170_COEFF_DATA_REG 0x14A #define AD4170_COEFF_ADDR_REG 0x14C @@ -127,6 +128,10 @@ /* AD4170_FILTER_REG */ #define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0) =20 +/* AD4170_CURRENT_SRC_REG */ +#define AD4170_CURRENT_SRC_I_OUT_PIN_MSK GENMASK(12, 8) +#define AD4170_CURRENT_SRC_I_OUT_VAL_MSK GENMASK(2, 0) + /* AD4170 register constants */ =20 /* AD4170_CLOCK_CTRL_REG constants */ @@ -188,6 +193,21 @@ #define AD4170_FILTER_FILTER_TYPE_SINC5 0x4 #define AD4170_FILTER_FILTER_TYPE_SINC3 0x6 =20 +/* AD4170_CURRENT_SRC_REG constants */ +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN0 0 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN1 1 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN2 2 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN3 3 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN4 4 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN5 5 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN6 6 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN7 7 +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN8 8 +#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0 17 +#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1 18 +#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2 19 +#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3 20 + /* Device properties and auxiliary constants */ =20 #define AD4170_NUM_ANALOG_PINS 9 @@ -222,6 +242,12 @@ #define AD4170_PIN_UNASIGNED 0x00 #define AD4170_PIN_ANALOG_IN 0x01 #define AD4170_PIN_CURRENT_OUT 0x02 +#define AD4170_PIN_VBIAS 0x04 + +/* GPIO pin functions */ +#define AD4170_GPIO_UNASIGNED 0x00 +#define AD4170_GPIO_AC_EXCITATION 0x02 +#define AD4170_GPIO_OUTPUT 0x04 =20 enum ad4170_ref_buf { AD4170_REF_BUF_PRE, /* Pre-charge referrence buffer */ @@ -278,6 +304,33 @@ static const unsigned int ad4170_sinc5_filt_fs_tbl[] = =3D { 1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256 }; =20 +static const unsigned int ad4170_iout_pin_tbl[] =3D { + AD4170_CURRENT_SRC_I_OUT_PIN_AIN0, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN1, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN2, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN3, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN4, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN5, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN6, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN7, + AD4170_CURRENT_SRC_I_OUT_PIN_AIN8, + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0, + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO1, + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO2, + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO3, +}; + +static const unsigned int ad4170_iout_current_ua_tbl[] =3D { + 0, 10, 50, 100, 250, 500, 1000, 1500 +}; + +enum ad4170_sensor_type { + AD4170_ADC_SENSOR =3D 0, + AD4170_WEIGH_SCALE_SENSOR =3D 1, + AD4170_THERMOCOUPLE_SENSOR =3D 2, + AD4170_RTD_SENSOR =3D 3, +}; + static const char * const ad4170_chip_names[] =3D { "ad4170", "ad4190", @@ -343,6 +396,7 @@ struct ad4170_state { struct clk *ext_clk; struct clk_hw int_clk_hw; int pins_fn[AD4170_NUM_ANALOG_PINS]; + int gpio_fn[AD4170_NUM_GPIO_PINS]; struct gpio_chip gpiochip; u32 int_pin_sel; int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; @@ -956,6 +1010,19 @@ static int ad4170_get_ain_voltage_uv(struct ad4170_st= ate *st, int ain_n, struct device *dev =3D &st->spi->dev; =20 *ain_voltage =3D 0; + /* + * The voltage bias (vbias) sets the common-mode voltage of the channel + * to (AVDD + AVSS)/2. If provided, AVSS supply provides the magnitude + * (absolute value) of the negative voltage supplied to the AVSS pin. + * So, we do AVDD - AVSS to compute the DC voltage generated by the bias + * voltage generator. + */ + if (st->pins_fn[ain_n] & AD4170_PIN_VBIAS) { + *ain_voltage =3D (st->vrefs_uv[AD4170_AVDD_SUP] + - st->vrefs_uv[AD4170_AVSS_SUP]) / 2; + return 0; + } + if (ain_n <=3D AD4170_CHAN_MAP_TEMP_SENSOR) return 0; =20 @@ -1746,6 +1813,242 @@ static int ad4170_gpio_init(struct iio_dev *indio_d= ev) return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev); } =20 +static int _ad4170_find_table_index(const unsigned int *tbl, size_t len, + unsigned int val) +{ + unsigned int i; + + for (i =3D 0; i < len; i++) + if (tbl[i] =3D=3D val) + return i; + + return -EINVAL; +} + +#define ad4170_find_table_index(table, val) \ + _ad4170_find_table_index(table, ARRAY_SIZE(table), val) + +static int ad4170_validate_excitation_pins(struct ad4170_state *st, + u32 *exc_pins, int num_exc_pins) +{ + struct device *dev =3D &st->spi->dev; + int ret, i; + + for (i =3D 0; i < num_exc_pins; i++) { + unsigned int pin =3D exc_pins[i]; + + ret =3D ad4170_find_table_index(ad4170_iout_pin_tbl, pin); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation pin: %u\n", + pin); + + if (pin <=3D AD4170_MAX_ANALOG_PINS) { + if (st->pins_fn[pin] !=3D AD4170_PIN_UNASIGNED) + return dev_err_probe(dev, -EINVAL, + "Pin %u already used with fn %u\n", + pin, st->pins_fn[pin]); + + st->pins_fn[pin] =3D AD4170_PIN_CURRENT_OUT; + } else { + unsigned int gpio =3D pin - AD4170_CURRENT_SRC_I_OUT_PIN_GPIO0; + + if (st->gpio_fn[gpio] !=3D AD4170_GPIO_UNASIGNED) + return dev_err_probe(dev, -EINVAL, + "GPIO %u already used with fn %u\n", + gpio, st->gpio_fn[gpio]); + + st->gpio_fn[gpio] =3D AD4170_GPIO_AC_EXCITATION; + } + } + return 0; +} + +static int ad4170_setup_rtd(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, u32 *exc_pins, + int num_exc_pins, int exc_cur, bool ac_excited) +{ + int current_src, ret, i; + + for (i =3D 0; i < num_exc_pins; i++) { + unsigned int pin =3D exc_pins[i]; + + current_src |=3D FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin); + current_src |=3D FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, exc_cur); + + ret =3D regmap_write(st->regmap16, AD4170_CURRENT_SRC_REG(i), + current_src); + if (ret) + return ret; + } + + if (ac_excited) + setup->misc |=3D FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK, + num_exc_pins =3D=3D 2 ? 0x2 : 0x3); + + return 0; +} + +static int ad4170_setup_bridge(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, u32 *exc_pins, + int num_exc_pins, int exc_cur, bool ac_excited) +{ + int current_src, ret, i; + + if (!ac_excited) + return 0; + + /* + * If a specific current is provided through + * adi,excitation-current-microamp, set excitation pins provided through + * adi,excitation-pins to AC excite the bridge circuit. Else, use + * predefined ACX1, ACX1 negated, ACX2, ACX2 negated signals to AC + * excite the bridge. Those signals are output on GPIO2, GPIO0, GPIO3, + * and GPIO1, respectively. If only two pins are specified for AC + * excitation, use ACX1 and ACX2. See AD4170 datasheet for instructions + * on how to setup the bridge circuit. + * + * Also, to avoid any short-circuit condition when more than one channel + * is enabled, set GPIO2 and GPIO0 high, and set GPIO1 and GPIO3 low to + * DC excite the bridge whenever a channel without AC excitation is + * selected. That is needed because GPIO pins are controlled by the next + * highest priority GPIO function when a channel doesn't enable AC + * excitation. See datasheet Figure 113 Weigh Scale (AC Excitation) for + * an example circuit diagram. + */ + if (exc_cur =3D=3D 0) { + if (num_exc_pins =3D=3D 2) { + setup->misc |=3D FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3); + ret =3D regmap_set_bits(st->regmap16, + AD4170_GPIO_MODE_REG, + BIT(7) | BIT(5)); + if (ret) + return ret; + + ret =3D regmap_set_bits(st->regmap16, + AD4170_GPIO_OUTPUT_REG, + BIT(3) | BIT(2)); + if (ret) + return ret; + + st->gpio_fn[3] |=3D AD4170_GPIO_OUTPUT; + st->gpio_fn[2] |=3D AD4170_GPIO_OUTPUT; + } else { + setup->misc |=3D FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x2); + ret =3D regmap_set_bits(st->regmap16, + AD4170_GPIO_MODE_REG, + BIT(7) | BIT(5) | BIT(3) | BIT(1)); + if (ret) + return ret; + + ret =3D regmap_set_bits(st->regmap16, + AD4170_GPIO_OUTPUT_REG, + BIT(3) | BIT(2) | BIT(1) | BIT(0)); + if (ret) + return ret; + + st->gpio_fn[3] |=3D AD4170_GPIO_OUTPUT; + st->gpio_fn[2] |=3D AD4170_GPIO_OUTPUT; + st->gpio_fn[1] |=3D AD4170_GPIO_OUTPUT; + st->gpio_fn[0] |=3D AD4170_GPIO_OUTPUT; + } + + return 0; + } + for (i =3D 0; i < num_exc_pins; i++) { + unsigned int pin =3D exc_pins[i]; + + current_src |=3D FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin); + current_src |=3D FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, exc_cur); + + ret =3D regmap_write(st->regmap16, AD4170_CURRENT_SRC_REG(i), + current_src); + if (ret) + return ret; + } + + setup->misc |=3D FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK, + num_exc_pins =3D=3D 2 ? 0x2 : 0x3); + + return 0; +} + +static int ad4170_parse_external_sensor(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, + struct iio_chan_spec *chan, u8 s_type) +{ + unsigned int num_exc_pins, exc_cur, reg_val; + struct device *dev =3D &st->spi->dev; + u32 pins[2], exc_pins[4]; + bool ac_excited, vbias; + int ret; + + ret =3D fwnode_property_read_u32_array(child, "diff-channels", pins, + ARRAY_SIZE(pins)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read sensor diff-channels\n"); + + chan->differential =3D true; + chan->channel =3D pins[0]; + chan->channel2 =3D pins[1]; + + ac_excited =3D fwnode_property_read_bool(child, "adi,ac-excited"); + + num_exc_pins =3D fwnode_property_count_u32(child, "adi,excitation-pins"); + if (num_exc_pins !=3D 2 && num_exc_pins !=3D 4) + return dev_err_probe(dev, -EINVAL, + "Invalid number of excitation pins\n"); + + ret =3D fwnode_property_read_u32_array(child, "adi,excitation-pins", + exc_pins, num_exc_pins); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read adi,excitation-pins\n"); + + ret =3D ad4170_validate_excitation_pins(st, exc_pins, num_exc_pins); + if (ret) + return ret; + + exc_cur =3D 0; + ret =3D fwnode_property_read_u32(child, "adi,excitation-current-microamp", + &exc_cur); + if (ret && s_type =3D=3D AD4170_RTD_SENSOR) + return dev_err_probe(dev, ret, + "Failed to read adi,excitation-current-microamp\n"); + + ret =3D ad4170_find_table_index(ad4170_iout_current_ua_tbl, exc_cur); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current: %uuA\n", + exc_cur); + + /* Get the excitation current configuration value */ + exc_cur =3D ret; + + if (s_type =3D=3D AD4170_THERMOCOUPLE_SENSOR) { + vbias =3D fwnode_property_read_bool(child, "adi,vbias"); + if (vbias) { + st->pins_fn[chan->channel2] |=3D AD4170_PIN_VBIAS; + reg_val =3D BIT(chan->channel2); + return regmap_write(st->regmap16, AD4170_V_BIAS_REG, + reg_val); + } + } + if (s_type =3D=3D AD4170_WEIGH_SCALE_SENSOR || + s_type =3D=3D AD4170_THERMOCOUPLE_SENSOR) { + ret =3D ad4170_setup_bridge(st, child, setup, exc_pins, + num_exc_pins, exc_cur, ac_excited); + } else { + ret =3D ad4170_setup_rtd(st, child, setup, exc_pins, num_exc_pins, + exc_cur, ac_excited); + } + return ret; +} + static int ad4170_parse_reference(struct ad4170_state *st, struct fwnode_handle *child, struct ad4170_setup *setup) @@ -1827,6 +2130,7 @@ static int ad4170_parse_channel_node(struct iio_dev *= indio_dev, struct ad4170_state *st =3D iio_priv(indio_dev); struct device *dev =3D &st->spi->dev; struct ad4170_chan_info *chan_info; + u8 s_type =3D AD4170_ADC_SENSOR; struct ad4170_setup *setup; struct iio_chan_spec *chan; unsigned int ch_reg; @@ -1857,10 +2161,34 @@ static int ad4170_parse_channel_node(struct iio_dev= *indio_dev, if (ret) return ret; =20 - ret =3D ad4170_parse_adc_channel_type(dev, child, chan); - if (ret < 0) - return ret; + ret =3D fwnode_property_read_u8(child, "adi,sensor-type", &s_type); + if (!ret) { + if (s_type > AD4170_RTD_SENSOR) + return dev_err_probe(dev, ret, + "Invalid adi,sensor-type: %u\n", + s_type); + } + switch (s_type) { + case AD4170_ADC_SENSOR: + ret =3D ad4170_parse_adc_channel_type(dev, child, chan); + if (ret < 0) + return ret; =20 + break; + case AD4170_WEIGH_SCALE_SENSOR: + fallthrough; + case AD4170_THERMOCOUPLE_SENSOR: + fallthrough; + case AD4170_RTD_SENSOR: + ret =3D ad4170_parse_external_sensor(st, child, setup, chan, + s_type); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } bipolar =3D fwnode_property_read_bool(child, "bipolar"); setup->afe |=3D FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar); if (bipolar) @@ -2057,6 +2385,9 @@ static int ad4170_parse_firmware(struct iio_dev *indi= o_dev) for (i =3D 0; i < AD4170_NUM_ANALOG_PINS; i++) st->pins_fn[i] =3D AD4170_PIN_UNASIGNED; =20 + for (i =3D 0; i < AD4170_NUM_GPIO_PINS; i++) + st->gpio_fn[i] =3D AD4170_GPIO_UNASIGNED; + /* On power on, device defaults to using SDO pin for data ready signal */ st->int_pin_sel =3D AD4170_INT_PIN_SDO; ret =3D device_property_match_property_string(dev, "interrupt-names", @@ -2081,6 +2412,10 @@ static int ad4170_parse_firmware(struct iio_dev *ind= io_dev) =20 /* Only create a GPIO chip if flagged for it */ if (device_property_read_bool(&st->spi->dev, "gpio-controller")) { + for (i =3D 0; i < AD4170_NUM_GPIO_PINS; i++) + if (st->gpio_fn[i] !=3D AD4170_GPIO_UNASIGNED) + return 0; + ret =3D ad4170_gpio_init(indio_dev); if (ret < 0) return ret; --=20 2.47.2