From nobody Tue Dec 2 02:43:01 2025 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (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 D8A6F30217B for ; Tue, 18 Nov 2025 14:18:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763475510; cv=none; b=kKnFv/UeGFiOweGtE6Je0L853ruYaTEFICdm39LXLxNCoFTWRvU2mnOwbZAj+/YFqdFPMEd2bUIs2fJuyue/PcnGSCvcG4y3lXFMRDHyQkkDAzEyWhIT1pJkmcEjHcODcIGnIKf3RwEAWhmMwEHa95xsf8lCYlsf5aeCemOXT6A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763475510; c=relaxed/simple; bh=pcxDN9ij9nGGiYPl7/WyvlJ1vm+7GJ5D1LmyOAHTE6E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=grPTGx1T7hV1WiB25ZQyJ4WA88qNP4wyqVgJ+wgUoibW+2QjYtC9SxB1rUcprWJmNpsi1jyz1ZQgND2xlBI4mrumIUgyx+Phj68D4zDeLgsGR0+bv3MDXmGASp8/3GaUk/x9yCLYcgm8T0xwMGuv6s+dWeMO9QjDhScxHaH1W9M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vLMXG-0000ef-Mr; Tue, 18 Nov 2025 15:18:22 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vLMXG-0015yH-0T; Tue, 18 Nov 2025 15:18:22 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.98.2) (envelope-from ) id 1vLMXG-00000003o3o-0IeA; Tue, 18 Nov 2025 15:18:22 +0100 From: Oleksij Rempel To: Jonathan Cameron , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Oleksij Rempel , Conor Dooley , kernel@pengutronix.de, linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org, devicetree@vger.kernel.org, Andy Shevchenko , David Lechner , =?UTF-8?q?Nuno=20S=C3=A1?= Subject: [PATCH v4 1/2] bindings: iio: adc: Add bindings for TI ADS131M0x ADCs Date: Tue, 18 Nov 2025 15:18:20 +0100 Message-ID: <20251118141821.907364-2-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251118141821.907364-1-o.rempel@pengutronix.de> References: <20251118141821.907364-1-o.rempel@pengutronix.de> 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-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add device tree bindings documentation for the Texas Instruments ADS131M0x analog-to-digital converters. This family includes the ADS131M02, ADS131M03, ADS131M04, ADS131M06, and ADS131M08 variants. These variants differ primarily in the number of supported channels (2, 3, 4, 6, and 8, respectively), which requires separate compatible strings to validate the channel nodes. Signed-off-by: Oleksij Rempel Reviewed-by: Conor Dooley Reviewed-by: David Lechner --- changes v4: - Move clock-names and refin-supply to top-level properties - Update clock-names description to explicitly note XTAL support is limited= to M06/M08 - Simplify conditional schema logic by removing redundant M06/M08 block changes v3: - Make channel@ regex patterns consistent changes v2: - Rename file to ti,ads131m02.yaml and update $id. - Add supplies (avdd, dvdd, refin), interrupts, reset-gpios, and clock-name= s. - Make avdd-supply, dvdd-supply, and clock-names required. - Tighten channel validation logic for each device variant. - Simplify channel description and reorder datasheet list. - Update commit message to clarify device difference --- .../bindings/iio/adc/ti,ads131m02.yaml | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131m02.= yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml b/= Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml new file mode 100644 index 000000000000..5d52bb7dd5d4 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads131m02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS131M0x 2-, 3-, 4-, 6- and 8-Channel ADCs + +maintainers: + - Oleksij Rempel + +description: | + The ADS131M0x are a family of multichannel, simultaneous sampling, + 24-bit, delta-sigma, analog-to-digital converters (ADCs) with a + built-in programmable gain amplifier (PGA) and internal reference. + Communication with the ADC chip is via SPI. + + Datasheets: + - ADS131M02: https://www.ti.com/lit/ds/symlink/ads131m02.pdf + - ADS131M03: https://www.ti.com/lit/ds/symlink/ads131m03.pdf + - ADS131M04: https://www.ti.com/lit/ds/symlink/ads131m04.pdf + - ADS131M06: https://www.ti.com/lit/ds/symlink/ads131m06.pdf + - ADS131M08: https://www.ti.com/lit/ds/symlink/ads131m08.pdf + +properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + - ti,ads131m06 + - ti,ads131m08 + + reg: + description: SPI chip select number. + + clocks: + description: + Phandle to the external clock source required by the ADC's CLKIN pin. + The datasheet recommends specific frequencies based on the desired p= ower + mode (e.g., 8.192 MHz for High-Resolution mode). + maxItems: 1 + + avdd-supply: + description: Analog power supply (AVDD). + + dvdd-supply: + description: Digital power supply (DVDD). + + interrupts: + description: DRDY (Data Ready) output signal. + maxItems: 1 + + reset-gpios: + description: Optional RESET signal. + maxItems: 1 + + clock-names: + description: + Indicates if a crystal oscillator (XTAL) or CMOS signal is connected + (CLKIN). Note that XTAL mode is only supported on ADS131M06 and ADS1= 31M08. + enum: [xtal, clkin] + + refin-supply: + description: Optional external reference supply (REFIN). + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - avdd-supply + - dvdd-supply + +patternProperties: + "^channel@[0-7]$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Properties for a single ADC channel. + + properties: + reg: + description: The channel index (0-7). + minimum: 0 + maximum: 7 # Max channels on ADS131M08 + + label: true + + required: + - reg + + unevaluatedProperties: false + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + # 20-pin devices: M02, M03, M04 + # These do not support XTAL or REFIN. + properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + then: + properties: + clock-names: + const: clkin + refin-supply: false + + - if: + # ADS131M02: 2 channels max (0-1) + properties: + compatible: + contains: + const: ti,ads131m02 + then: + patternProperties: + "^channel@[0-1]$": + properties: + reg: + maximum: 1 + "^channel@[2-7]$": false + + - if: + # ADS131M03: 3 channels max (0-2) + properties: + compatible: + contains: + const: ti,ads131m03 + then: + patternProperties: + "^channel@[0-2]$": + properties: + reg: + maximum: 2 + "^channel@[3-7]$": false + + - if: + # ADS131M04: 4 channels max (0-3) + properties: + compatible: + contains: + const: ti,ads131m04 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + maximum: 3 + "^channel@[4-7]$": false + + - if: + # ADS131M06: 6 channels max (0-5) + properties: + compatible: + contains: + const: ti,ads131m06 + then: + patternProperties: + "^channel@[0-5]$": + properties: + reg: + maximum: 5 + "^channel@[6-7]$": false + +unevaluatedProperties: false + +examples: + - | + #include + + spi1 { + #address-cells =3D <1>; + #size-cells =3D <0>; + + adc@0 { + compatible =3D "ti,ads131m02"; + reg =3D <0>; + spi-max-frequency =3D <8000000>; + + clocks =3D <&rcc CK_MCO2>; + clock-names =3D "clkin"; + + avdd-supply =3D <&vdd_ana>; + dvdd-supply =3D <&vdd_dig>; + + #address-cells =3D <1>; + #size-cells =3D <0>; + + channel@0 { + reg =3D <0>; + label =3D "input_voltage"; + }; + + channel@1 { + reg =3D <1>; + label =3D "input_current"; + }; + }; + }; --=20 2.47.3 From nobody Tue Dec 2 02:43:01 2025 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (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 D89CD2FBDEE for ; Tue, 18 Nov 2025 14:18:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763475509; cv=none; b=mF3wOVeyDtX+A9Ob2oaePNjgqx4jYjSHC1GQS3wF7RSejO2E5joA09M6tGiByx2NeFqIKG8k1I7bf16koVDVMy/ouyABRGYsD2Cf7LkOJY9YpyeNzy7bM1ax9fZpNV3hzBsQT8j05tAFTgZKd10wEw9ys8aVEYNc9DZqMAbv/Ck= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763475509; c=relaxed/simple; bh=7kcedp3z8R7NzAkr4px1I3ER2z2WhVg4v5qJ0TUHGpc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NAt/9DpO786evdGilVhVCq9hduCWeK6D6ZNZWBvYjJH7zjHrNujGOyAFXQOoVCJeEexxymHFiYH9LEShkNRy/jCaRPwHViSJgSSpFZ9Bw+phUkBLhwQbjjueGsdfbje9Gxj5WZ3MRcPCei8Q1GmcgMtrhee/PxAKd3C3kUPFMB8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vLMXG-0000eg-Ms; Tue, 18 Nov 2025 15:18:22 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vLMXG-0015yI-0Y; Tue, 18 Nov 2025 15:18:22 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.98.2) (envelope-from ) id 1vLMXG-00000003o3y-0Otb; Tue, 18 Nov 2025 15:18:22 +0100 From: Oleksij Rempel To: Jonathan Cameron , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: David Jander , Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org, devicetree@vger.kernel.org, Andy Shevchenko , David Lechner , =?UTF-8?q?Nuno=20S=C3=A1?= Subject: [PATCH v4 2/2] iio: adc: Add TI ADS131M0x ADC driver Date: Tue, 18 Nov 2025 15:18:21 +0100 Message-ID: <20251118141821.907364-3-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251118141821.907364-1-o.rempel@pengutronix.de> References: <20251118141821.907364-1-o.rempel@pengutronix.de> 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-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: David Jander Add a new IIO ADC driver for Texas Instruments ADS131M0x devices (ADS131M02/03/04/06/08). These are 24-bit, up to 64 kSPS, simultaneous- sampling delta-sigma ADCs accessed via SPI. Highlights: - Supports 2/3/4/6/8-channel variants with per-channel RAW and SCALE. - Implements device-required full-duplex fixed-frame transfers. - Handles both input and output CRC Note: Despite the almost identical name, this hardware is not compatible with the ADS131E0x series handled by drivers/iio/adc/ti-ads131e08.c. Signed-off-by: David Jander Co-developed-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Reviewed-by: David Lechner --- changes v4: - Switch to devm_regulator_get_enable_read_voltage() and cache reference vo= ltage - Remove runtime regulator voltage queries; calculate scale factors in probe - Move IIO_CHAN_INFO_SCALE to info_mask_shared_by_type - Set .differential =3D 1 for all channels - Refactor ads131m_parse_clock() to handle error paths first and simplify l= ogic - Move clock parsing after power initialization in probe - Assert reset using framework instead of direct GPIO handl= ing - Remove wrapper ads131m_crc_calculate() - Drop redundant spi_get_device_match_data() check - Clean up comments: remove obvious descriptions and standard API explanati= ons - Remove COMMON_CLK dependency from Kconfig; add strict NULL check for cloc= k handle changes v3: - Add Kconfig 'select CRC_ITU_T' for crc_itu_t() usage - Replace custom CRC logic with crc_itu_t() and add detailed comment - Rewrite RREG/WREG command macros using FIELD_PREP() and GENMASK() - Add GENMASK-based CMD_ADDR and CMD_NUM masks, remove hand-written shifts - Replace ADS131M_SCALE_DIVISOR with CODE_BITS and use BIT(CODE_BITS) - Rename ADS131M_VREF_INTERNAL_MV to ADS131M_VREF_INTERNAL_mV - Rename local vref_uv -> vref_uV for unit correctness - Collapse multi-line function prototypes into single-line forms - Consolidate CLOCK register updates into one rmw with clear mask - Remove unused clk_clear logic and use positive extref checks - Introduce ads131m_parse_clock() helper combining clk enable and xtal pars= ing - Validate xtal only for xtal-capable variants; default to clkin otherwise - Improve device_property_match_string() error handling for xtal cases - Fix refin regulator handling with PTR_ERR_OR_ZERO() pattern - Clean up dev_err_ratelimited() formatting and log message consistency - Add missing kernel headers: array_size.h, bitops.h, crc-itu-t.h, etc. - Reorder ads131m_configuration struct; convert flags to bitfields - Add trailing comma to IIO channel macro for checkpatch compatibility - Minor whitespace and style fixes across write/read helpers changes v2: - add , ; drop , ; - parenthesize macro args (ADS131M_FRAME_WSIZE, ADS131M_CHANNEL_INDEX). - add indio_dev to priv; add .name to config; drop enum device IDs + table, use per-variant ads131m0{2,3,4,6,8}_config. - switch to spi_get_device_match_data(spi); set indio_dev->name =3D config-= >name. - replace manual prepare/enable + devm action with devm_clk_get_enabled(); - use guard(mutex); add lockdep_assert_held() where needed; - use put_unaligned_be16() for command/data/CRC (was BE24 with shifts); - call devm_spi_optimize_message(); - use fsleep() for instead of usleep_ranges() - convert to dev_err_probe() paths; tighten comments; simplify flow. - drop manual spi->mode/bpw/max_speed - reorder/static arrays by variant; no functional change. - remove of_match_ptr() wrapper - remove stray debug logs - rename driver file, Kconfig, and Makefile object to ti-ads131m02. - remove ADS131M_MAX_SCLK_HZ define (now relies on firmware). - change ADS131M_RESET_DELAY_US from 10us to 5us per datasheet. - add optional hardware reset GPIO support. - reordere struct ads131m_priv to place DMA buffers last - remove unused gain array and simplified IIO_CHAN_INFO_SCALE logic. - rename size macros to ADS131M_FRAME_WORDS/BYTES for clarity. - rate-limite error messages in SPI helpers to prevent log flooding. - remove spi_setup() call from probe. - change xfer[1] array to a single struct spi_transfer xfer. - update scale calculation comment in read_raw. - add support for avdd, dvdd, and optional refin regulators - add support for "clock-names" to select xtal or clkin - configure CLOCK register based on chip (M06/M08) capabilities - add scaling support when using an external reference - refactor scaling logic into a new ads131m_read_scale() helper --- drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads131m02.c | 968 +++++++++++++++++++++++++++++++++ 3 files changed, 980 insertions(+) create mode 100644 drivers/iio/adc/ti-ads131m02.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58a14e6833f6..4d6431731986 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1691,6 +1691,17 @@ config TI_ADS131E08 This driver can also be built as a module. If so, the module will be called ti-ads131e08. =20 +config TI_ADS131M02 + tristate "Texas Instruments ADS131M02" + depends on SPI && REGULATOR + select CRC_ITU_T + help + Say yes here to get support for Texas Instruments ADS131M02, ADS131M03, + ADS131M04, ADS131M06 and ADS131M08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131m02. + config TI_ADS7138 tristate "Texas Instruments ADS7128 and ADS7138 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d008f78dc010..1179f86e6caa 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -147,6 +147,7 @@ obj-$(CONFIG_TI_ADS1119) +=3D ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) +=3D ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) +=3D ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) +=3D ti-ads131e08.o +obj-$(CONFIG_TI_ADS131M02) +=3D ti-ads131m02.o obj-$(CONFIG_TI_ADS7138) +=3D ti-ads7138.o obj-$(CONFIG_TI_ADS7924) +=3D ti-ads7924.o obj-$(CONFIG_TI_ADS7950) +=3D ti-ads7950.o diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c new file mode 100644 index 000000000000..07d63bf62c5f --- /dev/null +++ b/drivers/iio/adc/ti-ads131m02.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M02 family ADC chips. + * + * Copyright (C) 2024 Protonic Holland + * Copyright (C) 2025 Oleksij Rempel , Pengutronix + * + * Primary Datasheet Reference (used for citations): + * ADS131M08 8-Channel, Simultaneously-Sampling, 24-Bit, Delta-Sigma ADC + * Document SBAS950B, Revised February 2021 + * https://www.ti.com/lit/ds/symlink/ads131m08.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max channels supported by the largest variant in the family (ADS131M08)= */ +#define ADS131M_MAX_CHANNELS 8 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 5 + +#define ADS131M_WORD_SIZE_BYTES 3 +#define ADS131M_RESPONSE_WORDS 1 +#define ADS131M_CRC_WORDS 1 + +/* + * SPI Frame word count calculation. + * Frame =3D N channel words + 1 response word + 1 CRC word. + * Word size depends on WLENGTH bits in MODE register (Default 24-bit). + */ +#define ADS131M_FRAME_WORDS(nch) \ + ((nch) + ADS131M_RESPONSE_WORDS + ADS131M_CRC_WORDS) + +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BYTES(nch) \ + (ADS131M_FRAME_WORDS(nch) * ADS131M_WORD_SIZE_BYTES) + +/* + * Index calculation for the start byte of channel 'x' data within the RX = buffer. + * Assumes 24-bit words (3 bytes per word). + * The received frame starts with the response word (e.g., STATUS register + * content when NULL command was sent), followed by data for channels 0 to= N-1, + * and finally the output CRC word. + * Response =3D index 0..2, Chan0 =3D index 3..5, Chan1 =3D index 6..8, ... + * Index for ChanX =3D 3 (response) + x * 3 (channel data size). + */ +#define ADS131M_CHANNEL_INDEX(x) \ + ((x) * ADS131M_WORD_SIZE_BYTES + ADS131M_WORD_SIZE_BYTES) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_ADDR_MASK GENMASK(11, 7) +#define ADS131M_CMD_NUM_MASK GENMASK(6, 0) + +#define ADS131M_CMD_RREG_OP 0xa000 +#define ADS131M_CMD_WREG_OP 0x6000 + +#define ADS131M_CMD_RREG(a, n) \ + (ADS131M_CMD_RREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) +#define ADS131M_CMD_WREG(a, n) \ + (ADS131M_CMD_WREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) + +/* STATUS Register (0x01h) bit definitions */ +#define ADS131M_STATUS_CRC_ERR BIT(12) /* Input CRC error */ + +#define ADS131M_REG_MODE 0x02 +#define ADS131M_MODE_RX_CRC_EN BIT(12) /* Enable Input CRC */ +#define ADS131M_MODE_CRC_TYPE_ANSI BIT(11) /* 0 =3D CCITT, 1 =3D ANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +#define ADS131M_REG_CLOCK 0x03 +#define ADS131M_CLOCK_XTAL_DIS BIT(7) +#define ADS131M_CLOCK_EXTREF_EN BIT(6) + +/* 1.2V internal reference, in millivolts, for IIO_VAL_FRACTIONAL_LOG2 */ +#define ADS131M_VREF_INTERNAL_mV 1200 +/* 24-bit resolution */ +#define ADS131M_RESOLUTION_BITS 24 +/* Signed data uses (RESOLUTION_BITS - 1) magnitude bits */ +#define ADS131M_CODE_BITS (ADS131M_RESOLUTION_BITS - 1) + +/* External ref FSR =3D Vref * 0.96 */ +#define ADS131M_EXTREF_SCALE_NUM 96 +#define ADS131M_EXTREF_SCALE_DEN 100 + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + const char *name; + u16 reset_ack; + u8 num_channels; + u8 supports_extref:1; + u8 supports_xtal:1; +}; + +struct ads131m_priv { + struct iio_dev *indio_dev; + struct spi_device *spi; + const struct ads131m_configuration *config; + + bool use_external_ref; + int scale_val; + int scale_val2; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * Protects the shared tx_buffer and rx_buffer. More importantly, + * this serializes all SPI communication to ensure the atomicity + * of multi-cycle command sequences (like WREG, RREG, or RESET). + */ + struct mutex lock; + + /* DMA-safe buffers should be placed at the end of the struct. */ + u8 tx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)]; +}; + +/** + * ads131m_tx_frame_unlocked - Sends a command frame with Input CRC + * @priv: Device private data structure. + * @command: The 16-bit command to send (e.g., NULL, RREG, RESET). + * + * This function sends a command in Word 0, and its calculated 16-bit + * CRC in Word 1, as required when Input CRC is enabled. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 comman= d) +{ + struct iio_dev *indio_dev =3D priv->indio_dev; + u16 crc; + + lockdep_assert_held(&priv->lock); + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: 16-bit command, MSB-aligned in 24-bit word */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Input CRC. Calculated over the 3 bytes of Word 0. */ + crc =3D crc_itu_t(0xffff, priv->tx_buffer, 3); + put_unaligned_be16(crc, &priv->tx_buffer[3]); + + return spi_sync(priv->spi, &priv->msg); +} + +/** + * ads131m_rx_frame_unlocked - Receives a full SPI data frame. + * @priv: Device private data structure. + * + * This function sends a NULL command (with its CRC) to clock out a + * full SPI frame from the device (e.g., response + channel data + CRC). + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rx_frame_unlocked(struct ads131m_priv *priv) +{ + return ads131m_tx_frame_unlocked(priv, ADS131M_CMD_NULL); +} + +/** + * ads131m_check_status_crc_err - Checks for an Input CRC error. + * @priv: Device private data structure. + * + * Sends a NULL command to fetch the STATUS register and checks the + * CRC_ERR bit. This is used to verify the integrity of the previous + * command (like RREG or WREG). + * + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + struct device *dev =3D &priv->spi->dev; + u16 status; + int ret; + + lockdep_assert_held(&priv->lock); + + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, + "SPI error on STATUS read for CRC check\n"); + return ret; + } + + status =3D get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Input CRC error reported in STATUS =3D 0x%04x\n", + status); + return -EIO; + } + + return 0; +} + +/** + * ads131m_write_reg_unlocked - Writes a single register and verifies the = ACK. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: The 16-bit value to write. + * + * This function performs the full 3-cycle WREG operation with Input CRC: + * 1. (Cycle 1) Sends WREG command, data, and its calculated CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the response from Cycle 1. + * 3. Verifies the response is the correct ACK for the WREG. + * 4. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, u= 16 val) +{ + struct iio_dev *indio_dev =3D priv->indio_dev; + u16 command, expected_ack, response, crc; + struct device *dev =3D &priv->spi->dev; + int ret_crc_err =3D 0; + int ret; + + lockdep_assert_held(&priv->lock); + + command =3D ADS131M_CMD_WREG(reg, 0); /* n =3D 0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n =3D 0 -> m =3D 0): 010a aaaa a000 0000 =3D 0x4000 | (reg = << 7) + */ + expected_ack =3D 0x4000 | (reg << 7); + + /* Cycle 1: Send WREG Command + Data + Input CRC */ + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: WREG command, 1 reg (n =3D 0), MSB-aligned */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be16(val, &priv->tx_buffer[3]); + + /* Word 2: Input CRC. Calculated over Word 0 (Cmd) and Word 1 (Data). */ + crc =3D crc_itu_t(0xffff, priv->tx_buffer, 6); + put_unaligned_be16(crc, &priv->tx_buffer[6]); + + /* Ignore the RX buffer (it's from the previous command) */ + ret =3D spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the WREG response */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG ACK (cycle 2)\n"); + return ret; + } + + /* + * Response is in the first 2 bytes of the RX buffer + * (MSB-aligned 16-bit response) + */ + response =3D get_unaligned_be16(&priv->rx_buffer[0]); + if (response !=3D expected_ack) { + dev_err_ratelimited(dev, "WREG(0x%02x) failed, expected ACK 0x%04x, got = 0x%04x\n", + reg, expected_ack, response); + ret_crc_err =3D -EIO; + /* + * Don't return yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + ret =3D ads131m_check_status_crc_err(priv); + if (ret < 0) + return ret; + + return ret_crc_err; +} + +/** + * ads131m_read_reg_unlocked - Reads a single register from the device. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: Pointer to store the 16-bit register value. + * + * This function performs the full 3-cycle RREG operation with Input CRC: + * 1. (Cycle 1) Sends the RREG command + Input CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the register data. + * 3. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u1= 6 *val) +{ + struct device *dev =3D &priv->spi->dev; + u16 command; + int ret; + + lockdep_assert_held(&priv->lock); + + command =3D ADS131M_CMD_RREG(reg, 0); /* n=3D0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * Ignore the RX buffer (it's from the previous command) + */ + ret =3D ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the register data */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG data (cycle 2)\n"); + return ret; + } + + /* + * Per datasheet, for a single reg read, the response is the data. + * It's in the first 2 bytes of the RX buffer (MSB-aligned 16-bit). + */ + *val =3D get_unaligned_be16(&priv->rx_buffer[0]); + + /* + * Cycle 3: Check STATUS for Input CRC error. + * The RREG command does not execute if CRC is bad, but we read + * STATUS anyway to clear the flag in case it was set. + */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_rmw_reg - Reads, modifies, and writes a single register. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @clear: Bitmask of bits to clear. + * @set: Bitmask of bits to set. + * + * This function performs an atomic read-modify-write operation on a regis= ter. + * It reads the register, applies the clear and set masks, and writes + * the new value back if it has changed. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, u= 16 set) +{ + u16 old_val, new_val; + int ret; + + guard(mutex)(&priv->lock); + + ret =3D ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + return ret; + + new_val =3D (old_val & ~clear) | set; + if (new_val =3D=3D old_val) + return 0; + + return ads131m_write_reg_unlocked(priv, reg, new_val); +} + +/** + * ads131m_verify_output_crc - Verifies the CRC of the received SPI frame. + * @priv: Device private data structure. + * + * This function calculates the CRC-16-CCITT (Poly 0x1021, Seed 0xFFFF) ov= er + * the received response and channel data, and compares it to the CRC word + * received at the end of the SPI frame. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev =3D priv->indio_dev; + struct device *dev =3D &priv->spi->dev; + u16 calculated_crc, received_crc; + size_t data_len; + + lockdep_assert_held(&priv->lock); + + /* + * Frame: [Response][Chan 0]...[Chan N-1][CRC Word] + * Data for CRC: [Response][Chan 0]...[Chan N-1] + * Data length =3D (N_channels + 1) * 3 bytes (at 24-bit word size) + */ + data_len =3D ADS131M_FRAME_BYTES(indio_dev->num_channels) - 3; + calculated_crc =3D crc_itu_t(0xffff, priv->rx_buffer, data_len); + + /* + * The received 16-bit CRC is MSB-aligned in the last 24-bit word. + * We extract it from the first 2 bytes (BE) of that word. + */ + received_crc =3D get_unaligned_be16(&priv->rx_buffer[data_len]); + if (calculated_crc !=3D received_crc) { + dev_err_ratelimited(dev, "Output CRC error. Got %04x, expected %04x\n", + received_crc, calculated_crc); + return -EIO; + } + + return 0; +} + +/** + * ads131m_adc_read - Reads channel data, checks input and output CRCs. + * @priv: Device private data structure. + * @channel: The channel number to read. + * @val: Pointer to store the raw 24-bit value. + * + * This function sends a NULL command (with Input CRC) to retrieve data. + * It checks the received STATUS word for any Input CRC errors from the + * previous command, and then verifies the Output CRC of the current + * data frame. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_adc_read(struct ads131m_priv *priv, u8 channel, s32 *va= l) +{ + struct device *dev =3D &priv->spi->dev; + u16 status; + int ret; + u8 *buf; + + guard(mutex)(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return ret; + + /* + * Check STATUS for Input CRC error from the previous command frame. + * Note: the STATUS word belongs to the frame before this NULL command. + */ + status =3D get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Previous input CRC error reported in STATUS (0x%04x)\n", + status); + } + + ret =3D ads131m_verify_output_crc(priv); + if (ret < 0) + return ret; + + buf =3D &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val =3D sign_extend32(get_unaligned_be24(buf), ADS131M_CODE_BITS); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, struct iio_chan_spe= c const *channel, + int *val, int *val2, long mask) +{ + struct ads131m_priv *priv =3D iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret =3D ads131m_adc_read(priv, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val =3D priv->scale_val; + *val2 =3D priv->scale_val2; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type =3D IIO_VOLTAGE, \ + .differential =3D 1, \ + .indexed =3D 1, \ + .channel =3D (num), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads131m02_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec ads131m03_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m04_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec ads131m06_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), +}; + +static const struct iio_chan_spec ads131m08_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), + ADS131M_VOLTAGE_CHANNEL(6), + ADS131M_VOLTAGE_CHANNEL(7), +}; + +static const struct ads131m_configuration ads131m02_config =3D { + .channels =3D ads131m02_channels, + .num_channels =3D ARRAY_SIZE(ads131m02_channels), + .reset_ack =3D 0xff22, + .name =3D "ads131m02", +}; + +static const struct ads131m_configuration ads131m03_config =3D { + .channels =3D ads131m03_channels, + .num_channels =3D ARRAY_SIZE(ads131m03_channels), + .reset_ack =3D 0xff23, + .name =3D "ads131m03", +}; + +static const struct ads131m_configuration ads131m04_config =3D { + .channels =3D ads131m04_channels, + .num_channels =3D ARRAY_SIZE(ads131m04_channels), + .reset_ack =3D 0xff24, + .name =3D "ads131m04", +}; + +static const struct ads131m_configuration ads131m06_config =3D { + .channels =3D ads131m06_channels, + .num_channels =3D ARRAY_SIZE(ads131m06_channels), + .reset_ack =3D 0xff26, + .supports_extref =3D true, + .supports_xtal =3D true, + .name =3D "ads131m06", +}; + +static const struct ads131m_configuration ads131m08_config =3D { + .channels =3D ads131m08_channels, + .num_channels =3D ARRAY_SIZE(ads131m08_channels), + .reset_ack =3D 0xff28, + .supports_extref =3D true, + .supports_xtal =3D true, + .name =3D "ads131m08", +}; + +static const struct iio_info ads131m_info =3D { + .read_raw =3D ads131m_read_raw, +}; + +/* + * Prepares the reusable SPI message structure for a full-duplex transfer. + * The ADS131M requires sending a command frame while simultaneously + * receiving the response/data frame from the previous command cycle. + * + * This message is optimized for the primary data acquisition workflow: + * sending a single-word command (like NULL) and receiving a full data + * frame (Response + N*Channels + CRC). + * + * This message is sized for a full data frame and is reused for all + * command/data cycles. The driver does not implement variable-length SPI + * messages. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_prepare_message(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev =3D priv->indio_dev; + struct device *dev =3D &priv->spi->dev; + int ret; + + priv->xfer.tx_buf =3D priv->tx_buffer; + priv->xfer.rx_buf =3D priv->rx_buffer; + priv->xfer.len =3D ADS131M_FRAME_BYTES(indio_dev->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + ret =3D devm_spi_optimize_message(dev, priv->spi, &priv->msg); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + return 0; +} + +/** + * ads131m_hw_reset - Pulses the optional hardware reset. + * @priv: Device private data structure. + * @rstc: Reset control for the /RESET line. + * + * Pulses the /RESET line to perform a hardware reset and waits the + * required t_REGACQ time for the device to be ready. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_reset(struct ads131m_priv *priv, + struct reset_control *rstc) +{ + struct device *dev =3D &priv->spi->dev; + int ret; + + /* + * Manually pulse the reset line using the framework. + * The reset-gpio provider does not implement the .reset op, + * so we must use .assert and .deassert. + */ + ret =3D reset_control_assert(rstc); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert reset\n"); + + /* Datasheet: Hold /RESET low for > 2 f_CLKIN cycles. 1us is ample. */ + fsleep(1); + + ret =3D reset_control_deassert(rstc); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + + /* Wait t_REGACQ (5us) for registers to be accessible */ + fsleep(ADS131M_RESET_DELAY_US); + + return 0; +} + +/** + * ads131m_sw_reset - Issues a software RESET and verifies ACK. + * @priv: Device private data structure. + * + * This function sends a RESET command (with Input CRC), waits t_REGACQ, + * reads back the RESET ACK, and then sends a final NULL to check for + * any input CRC errors. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_sw_reset(struct ads131m_priv *priv) +{ + u16 expected_ack =3D priv->config->reset_ack; + struct device *dev =3D &priv->spi->dev; + u16 response; + int ret; + + guard(mutex)(&priv->lock); + + ret =3D ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to send RESET command\n"); + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + fsleep(ADS131M_RESET_DELAY_US); + + /* Cycle 2: Send NULL + CRC to retrieve the response to the RESET */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read RESET ACK\n"); + + response =3D get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response !=3D expected_ack) + return dev_err_probe(dev, -EIO, + "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_reset - Resets the device using hardware or software. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * + * This function performs a hardware reset if supported (rstc provided), + * otherwise it issues a software RESET command via SPI. + * + * Note: The software reset path also validates the device's reset + * acknowledgment against the expected ID for the compatible string. + * The hardware reset path bypasses this ID check. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_reset(struct ads131m_priv *priv, struct reset_control *= rstc) +{ + if (rstc) + return ads131m_hw_reset(priv, rstc); + + return ads131m_sw_reset(priv); +} + +static int ads131m_power_init(struct ads131m_priv *priv) +{ + static const char * const supply_ids[] =3D { "avdd", "dvdd" }; + struct device *dev =3D &priv->spi->dev; + int vref_uV; + int ret; + + ret =3D devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supply_ids), suppl= y_ids); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); + + /* Default to Internal 1.2V reference: 1200mV / 2^23 */ + priv->scale_val =3D ADS131M_VREF_INTERNAL_mV; + priv->scale_val2 =3D BIT(ADS131M_CODE_BITS); + + if (!priv->config->supports_extref) + return 0; + + ret =3D devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "failed to get refin supply\n"); + + if (ret =3D=3D 0) + return dev_err_probe(dev, -EINVAL, "refin supply reports 0V\n"); + + if (ret =3D=3D -ENODEV) + return 0; + + vref_uV =3D ret; + + /* + * External reference found: Scale(mV) =3D (vref_uV * 0.96) / 1000 + * The denominator is 100 * 2^23 because of the 0.96 factor (96/100). + */ + priv->scale_val =3D div_s64((s64)vref_uV * ADS131M_EXTREF_SCALE_NUM, 1000= ); + priv->scale_val2 =3D ADS131M_EXTREF_SCALE_DEN * BIT(ADS131M_CODE_BITS); + priv->use_external_ref =3D true; + + return 0; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * @is_xtal: True if 'clock-names' is "xtal", false if "clkin". + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv, + struct reset_control *rstc, bool is_xtal) +{ + struct device *dev =3D &priv->spi->dev; + u16 mode_clear, mode_set; + int ret; + + ret =3D ads131m_reset(priv, rstc); + if (ret < 0) + return ret; + + /* + * Configure CLOCK register (0x03) based on DT properties. + * This register only needs configuration for 32-pin (M06/M08) + * variants, as the configurable bits (XTAL_DIS, EXTREF_EN) + * are reserved on 20-pin (M02/M03/M04) variants. + */ + if (priv->config->supports_xtal || priv->config->supports_extref) { + u16 clk_set =3D 0; + + if (priv->config->supports_xtal && !is_xtal) + clk_set |=3D ADS131M_CLOCK_XTAL_DIS; + + if (priv->config->supports_extref && priv->use_external_ref) + clk_set |=3D ADS131M_CLOCK_EXTREF_EN; + + ret =3D ads131m_rmw_reg(priv, ADS131M_REG_CLOCK, + ADS131M_CLOCK_EXTREF_EN | ADS131M_CLOCK_XTAL_DIS, + clk_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure CLOCK register\n"); + } + + /* + * The RESET command sets all registers to default, which means: + * 1. The RESET bit (Bit 10) in MODE is set to '1'. + * 2. The CRC_TYPE bit (Bit 11) in MODE is '0' (CCITT). + * 3. The RX_CRC_EN bit (Bit 12) in MODE is '0' (Disabled). + * + * We must: + * 1. Clear the RESET bit. + * 2. Enable Input CRC (RX_CRC_EN). + * 3. Explicitly clear the ANSI CRC bit (for certainty). + */ + mode_clear =3D ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + mode_set =3D ADS131M_MODE_RX_CRC_EN; + + ret =3D ads131m_rmw_reg(priv, ADS131M_REG_MODE, mode_clear, mode_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure MODE register\n"); + + return 0; +} + +/** + * ads131m_parse_clock - enable clock and detect "xtal" selection + * @priv: Device private data structure. + * @is_xtal: result flag (true if "xtal", false if default "clkin") + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_parse_clock(struct ads131m_priv *priv, bool *is_xtal) +{ + struct device *dev =3D &priv->spi->dev; + struct clk *clk; + int ret; + + clk =3D devm_clk_get_enabled(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + if (IS_ERR(clk)) + ret =3D PTR_ERR(clk); + else + ret =3D -ENODEV; + + return dev_err_probe(dev, ret, "clk get enabled failed\n"); + } + + ret =3D device_property_match_string(dev, "clock-names", "xtal"); + if (ret > 0) + return dev_err_probe(dev, -EINVAL, + "'xtal' must be the only or first clock name"); + + if (ret < 0 && ret !=3D -ENODATA) + return dev_err_probe(dev, ret, + "failed to read 'clock-names' property"); + + if (ret =3D=3D 0 && !priv->config->supports_xtal) + return dev_err_probe(dev, -EINVAL, + "'xtal' clock not supported on this device"); + + *is_xtal =3D !ret; + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct device *dev =3D &spi->dev; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + bool is_xtal; + int ret; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv =3D iio_priv(indio_dev); + priv->indio_dev =3D indio_dev; + priv->spi =3D spi; + + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->info =3D &ads131m_info; + + config =3D spi_get_device_match_data(spi); + + priv->config =3D config; + indio_dev->name =3D config->name; + indio_dev->channels =3D config->channels; + indio_dev->num_channels =3D config->num_channels; + + rstc =3D devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + ret =3D devm_mutex_init(dev, &priv->lock); + if (ret < 0) + return ret; + + ret =3D ads131m_prepare_message(priv); + if (ret < 0) + return ret; + + ret =3D ads131m_power_init(priv); + if (ret < 0) + return ret; + + /* Power must be applied and stable before the clock is enabled. */ + ret =3D ads131m_parse_clock(priv, &is_xtal); + if (ret < 0) + return ret; + + ret =3D ads131m_hw_init(priv, rstc, is_xtal); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] =3D { + { .compatible =3D "ti,ads131m02", .data =3D &ads131m02_config }, + { .compatible =3D "ti,ads131m03", .data =3D &ads131m03_config }, + { .compatible =3D "ti,ads131m04", .data =3D &ads131m04_config }, + { .compatible =3D "ti,ads131m06", .data =3D &ads131m06_config }, + { .compatible =3D "ti,ads131m08", .data =3D &ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] =3D { + { "ads131m02", (kernel_ulong_t)&ads131m02_config }, + { "ads131m03", (kernel_ulong_t)&ads131m03_config }, + { "ads131m04", (kernel_ulong_t)&ads131m04_config }, + { "ads131m06", (kernel_ulong_t)&ads131m06_config }, + { "ads131m08", (kernel_ulong_t)&ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver =3D { + .driver =3D { + .name =3D "ads131m02", + .of_match_table =3D ads131m_of_match, + }, + .probe =3D ads131m_probe, + .id_table =3D ads131m_id, +}; +module_spi_driver(ads131m_driver); + +MODULE_AUTHOR("David Jander "); +MODULE_DESCRIPTION("Texas Instruments ADS131M02 ADC driver"); +MODULE_LICENSE("GPL"); --=20 2.47.3