From nobody Fri Dec 19 12:48:25 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 D443030C60A for ; Wed, 5 Nov 2025 14:38:23 +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=1762353505; cv=none; b=CjKxcDY+z8C1AOuK2TKJ6F1UyLNc4QXaMeU0G2YGS3AwnyJ1VPCUYTYAGnU4+LEHBcUhmwJltP06/qUMPLMR8u/C8qyxuhttfpuKCXUc4akb86JVmlXbrNOzvhDxxuxGQbaZcZOstOdHEjScF+kxrpHyGWe9YCLC6Rj9coN09Ac= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1762353505; c=relaxed/simple; bh=JAozV/BWaYyahkCMxTGFqzeqdas0jf1QKKAIVCCWTbU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Y+J3CRDPnuGFWvT1M+TwpGbXZkcHtrYqok1Nl1Axdv1+jWk6+J1nSSRy7lmgffky5LVbQWm/4yulVJmmz/fBEoH/9dj5c7aW0xYzlmDPnib9k+Bmsf73Kz+9pjZgQeKC1nh/Fjag1BgTEn9ElhBTFOwKv1WqFZ4T3T5N2bZbttI= 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 1vGeeO-0003od-3U; Wed, 05 Nov 2025 15:38:16 +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 1vGeeN-007DGC-0h; Wed, 05 Nov 2025 15:38:15 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.98.2) (envelope-from ) id 1vGeeN-00000007aD9-0Z5K; Wed, 05 Nov 2025 15:38:15 +0100 From: Oleksij Rempel To: Jonathan Cameron , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: 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 v1 1/2] bindings: iio: adc: Add bindings for TI ADS131M0x ADCs Date: Wed, 5 Nov 2025 15:38:13 +0100 Message-ID: <20251105143814.1807444-2-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251105143814.1807444-1-o.rempel@pengutronix.de> References: <20251105143814.1807444-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. Signed-off-by: Oleksij Rempel --- .../bindings/iio/adc/ti,ads131m08.yaml | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131m08.= yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131m08.yaml b/= Documentation/devicetree/bindings/iio/adc/ti,ads131m08.yaml new file mode 100644 index 000000000000..193ac84c41cd --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131m08.yaml @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads131m08.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: + - ADS131M08: https://www.ti.com/lit/ds/symlink/ads131m08.pdf + - ADS131M06: https://www.ti.com/lit/ds/symlink/ads131m06.pdf + - ADS131M04: https://www.ti.com/lit/ds/symlink/ads131m04.pdf + - ADS131M03: https://www.ti.com/lit/ds/symlink/ads131m03.pdf + - ADS131M02: https://www.ti.com/lit/ds/symlink/ads131m02.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 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + - clocks + +patternProperties: + "^channel@([0-7])$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: | + Properties for a single ADC channel. The maximum valid channel number + depends on the specific compatible string used (e.g., 0-1 for ads131= m02, + 0-7 for ads131m08). + + 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: + properties: + compatible: + contains: + const: ti,ads131m02 + then: + patternProperties: + "^channel@[0-7]$": + properties: + reg: + maximum: 1 + "^channel@([2-7])$": false + + - if: + properties: + compatible: + contains: + const: ti,ads131m03 + then: + patternProperties: + "^channel@[0-7]$": + properties: + reg: + maximum: 2 + "^channel@([3-7])$": false + + - if: + properties: + compatible: + contains: + const: ti,ads131m04 + then: + patternProperties: + "^channel@[0-7]$": + properties: + reg: + maximum: 3 + "^channel@([4-7])$": false + + - if: + properties: + compatible: + contains: + const: ti,ads131m06 + then: + patternProperties: + "^channel@[0-7]$": + 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>; + + #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 Fri Dec 19 12:48:25 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 2379E32ED45 for ; Wed, 5 Nov 2025 14:38:28 +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=1762353511; cv=none; b=d7qgtqKt+HmLQ/9j0bQF3JBusJOyNJN3/X2DAdtWj0OGaHLPoQHguHgWym0X6RftEe6F1rgOvdp0ejghr5eOIr6pUrygtA1W6oum5oKrvkjS/SM4o8pysXFyQtFHtt1qvvAufvxDoiToAY1RUggEOw8so3c4jk/dCEJ1DWCmYmc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1762353511; c=relaxed/simple; bh=LZyej7ZxXOwNUCi+qLhSziVnMw8FgUTSOVTzq6xdHlY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XEYVxea6iV+aC81EbG6ruB4Pd20kZvz/gDm+NXZ5HNf2gWkwTJC62PRct9Gk06GAKTDrW6iiuTSIFqimZ7rDgB1yM1XPyfdRTwkoXdWNuBCgIVlOX82vTQNPWH10L2kIRVGScNmiQ9wtO66bzmY+Ra2EBRbd9BSLYTKNsgCY2ns= 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 1vGeeO-0003oe-3U; Wed, 05 Nov 2025 15:38:16 +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 1vGeeN-007DGD-0l; Wed, 05 Nov 2025 15:38:15 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.98.2) (envelope-from ) id 1vGeeN-00000007aDJ-0g1o; Wed, 05 Nov 2025 15:38:15 +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 v1 2/2] iio: adc: Add TI ADS131M0x ADC driver Date: Wed, 5 Nov 2025 15:38:14 +0100 Message-ID: <20251105143814.1807444-3-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251105143814.1807444-1-o.rempel@pengutronix.de> References: <20251105143814.1807444-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; uses a non-reflected CCITT (0x1021) implementation because the generic crc_ccitt helper is incompatible. 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 --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads131m0x.c | 886 +++++++++++++++++++++++++++++++++ 3 files changed, 897 insertions(+) create mode 100644 drivers/iio/adc/ti-ads131m0x.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58a14e6833f6..c17f8914358c 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1691,6 +1691,16 @@ config TI_ADS131E08 This driver can also be built as a module. If so, the module will be called ti-ads131e08. =20 +config TI_ADS131M0X + tristate "Texas Instruments ADS131M0x" + depends on SPI && COMMON_CLK + 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-ads131m0x. + 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..c23dae3ddcc7 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_ADS131M0X) +=3D ti-ads131m0x.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-ads131m0x.c b/drivers/iio/adc/ti-ads131m0x.c new file mode 100644 index 000000000000..d40aacc129ba --- /dev/null +++ b/drivers/iio/adc/ti-ads131m0x.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M0x 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 + +/* Max channels supported by the largest variant in the family (ADS131M08)= */ +#define ADS131M_MAX_CHANNELS 8 + +/* ADS131M08 tolerates up to 25 MHz SCLK. + */ +#define ADS131M_MAX_SCLK_HZ 25000000 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 10 + +/* + * 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_WSIZE(nch) (nch + 2) +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BSIZE(nch) (ADS131M_FRAME_WSIZE(nch) * 3) +/* + * 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 * 3 + 3) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_RREG(a, n) \ + (0xa000 | ((u16)(a & 0x1f) << 7) | (u16)(n & 0x7f)) +#define ADS131M_CMD_WREG(a, n) \ + (0x6000 | ((u16)(a & 0x1f) << 7) | (u16)(n & 0x7f)) + +/* 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=3DCCITT, 1=3DANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + u8 num_channels; + u16 reset_ack; +}; + +enum ads131m_device_id { + ADS131M08_ID, + ADS131M06_ID, + ADS131M04_ID, + ADS131M03_ID, + ADS131M02_ID, +}; + +struct ads131m_priv { + struct spi_device *spi; + struct clk *clk; + struct mutex lock; + u8 num_channels; + const struct ads131m_configuration *config; + u8 tx_buffer[ADS131M_FRAME_BSIZE(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BSIZE(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + struct spi_transfer xfer[1]; + struct spi_message msg; + unsigned int gain[ADS131M_MAX_CHANNELS]; +}; + +/** + * ads131m_crc_ccitt_byte - Calculate CRC-16-CCITT (Poly 0x1021) for one b= yte + * @crc: The previous 16-bit CRC value + * @data: The new byte of data + * + * Device CRC: + * - CRC-16-CCITT, polynomial 0x1021 + * - MSB-first (non-reflected), init 0xFFFF, no final xor + * - Computed over 24-bit words as sent on the wire (MSB-aligned), includi= ng + * padded bits of command/data words (See Table 8-7 CRC Types). + * Note: This differs from kernel crc_ccitt(), which is a reflected varian= t. + * + * Return: The new 16-bit CRC value. + */ +static inline u16 ads131m_crc_ccitt_byte(u16 crc, u8 data) +{ + int i; + + crc ^=3D ((u16)data << 8); + for (i =3D 0; i < 8; i++) { + if (crc & 0x8000) + crc =3D (crc << 1) ^ 0x1021; + else + crc =3D (crc << 1); + } + return crc & 0xFFFF; +} + +/** + * ads131m_crc_calculate - Calculate CRC-16-CCITT over a buffer + * @buffer: The data buffer to process + * @len: The length of the buffer + * + * This function processes a buffer with the CCITT algorithm required + * by the device, using the 0xFFFF seed. + * + * Return: The final 16-bit CRC. + */ +static u16 ads131m_crc_calculate(const u8 *buffer, size_t len) +{ + u16 crc =3D 0xFFFF; + size_t i; + + for (i =3D 0; i < len; i++) + crc =3D ads131m_crc_ccitt_byte(crc, buffer[i]); + + return crc; +} + +/** + * 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). + * + * Assumes the mutex lock is held. + * 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 from spi_sync. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 comman= d) +{ + int ret; + u16 crc; + + /* + * Zero the entire TX buffer to send a valid frame. + */ + memset(priv->tx_buffer, 0, ADS131M_FRAME_BSIZE(priv->num_channels)); + + /* + * Word 0: 16-bit command, MSB-aligned in 24-bit word. + */ + put_unaligned_be24(command << 8, &priv->tx_buffer[0]); + + /* + * Word 1: Input CRC + * Calculated over the 3 bytes of Word 0. + */ + crc =3D ads131m_crc_calculate(priv->tx_buffer, 3); + put_unaligned_be24(crc << 8, &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). + * + * Assumes the mutex lock is held. + * + * Return: 0 on success, or a negative error code from spi_sync. + */ +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). + * + * Context: This function assumes the mutex 'lock' is held. + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + int ret; + u16 status; + + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err(&priv->spi->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(&priv->spi->dev, "Previous input CRC error, STATUS=3D0x%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. + * + * Assumes the mutex lock is held. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, + u16 val) +{ + u16 command, expected_ack, response, crc; + int ret; + + command =3D ADS131M_CMD_WREG(reg, 0); /* n=3D0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n=3D0 -> m=3D0): 010a aaaa a000 0000 =3D 0x4000 | (reg << 7) + */ + expected_ack =3D 0x4000 | (reg << 7); + + /* + * Cycle 1: Send WREG Command + Data + Input CRC + */ + + /* Zero the entire TX buffer */ + memset(priv->tx_buffer, 0, ADS131M_FRAME_BSIZE(priv->num_channels)); + + /* Word 0: WREG command, 1 reg (n=3D0), MSB-aligned */ + put_unaligned_be24(command << 8, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be24(val << 8, &priv->tx_buffer[3]); + + /* + * Word 2: Input CRC + * Calculated over Word 0 (Cmd) and Word 1 (Data). + */ + crc =3D ads131m_crc_calculate(priv->tx_buffer, 6); + put_unaligned_be24(crc << 8, &priv->tx_buffer[6]); + + /* We ignore the RX buffer (it's from the *previous* command) */ + ret =3D spi_sync(priv->spi, &priv->msg); + + if (ret < 0) { + dev_err(&priv->spi->dev, "SPI error on WREG (cycle 1)\n"); + goto write_err; + } + + /* + * Cycle 2: Send NULL Command to get the WREG response + */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err(&priv->spi->dev, "SPI error on WREG ACK (cycle 2)\n"); + goto write_err; + } + + /* + * 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(&priv->spi->dev, + "WREG(0x%02x) failed, expected ACK 0x%04x, got 0x%04x\n", + reg, expected_ack, response); + ret =3D -EIO; + /* + * Don't unlock yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } else { + dev_dbg(&priv->spi->dev, "WREG(0x%02x) ACK 0x%04x OK\n", + reg, response); + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + if (ads131m_check_status_crc_err(priv) < 0) + ret =3D -EIO; + +write_err: + return ret; +} + +/** + * 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. + * + * Assumes the mutex lock is held. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u1= 6 *val) +{ + u16 command; + int ret; + + command =3D ADS131M_CMD_RREG(reg, 0); /* n=3D0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * We ignore the RX buffer (it's from the previous command). + */ + ret =3D ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err(&priv->spi->dev, "SPI error on RREG (cycle 1)\n"); + goto read_err; + } + + /* + * Cycle 2: Send NULL Command to get the register data + */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err(&priv->spi->dev, "SPI error on RREG data (cycle 2)\n"); + goto read_err; + } + + /* + * 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]); + + dev_dbg(&priv->spi->dev, "RREG(0x%02x) =3D 0x%04x\n", reg, *val); + + /* + * 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. + */ + if (ads131m_check_status_crc_err(priv) < 0) + ret =3D -EIO; + +read_err: + return ret; +} + +/** + * 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. + * + * Context: This function handles its own mutex locking + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, + u16 set) +{ + u16 old_val, new_val; + int ret =3D 0; + + mutex_lock(&priv->lock); + + ret =3D ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + goto rmw_unlock; + + new_val =3D (old_val & ~clear) | set; + + if (new_val =3D=3D old_val) + goto rmw_unlock; + + ret =3D ads131m_write_reg_unlocked(priv, reg, new_val); + +rmw_unlock: + mutex_unlock(&priv->lock); + return ret; +} + +/** + * 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. + * + * Context: Must be called with mutex lock held. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + size_t data_len; + u16 calculated_crc; + u16 received_crc; + + /* + * 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_BSIZE(priv->num_channels) - 3; + calculated_crc =3D ads131m_crc_calculate(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(&priv->spi->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) +{ + int ret; + u8 *buf; + u16 status; + + mutex_lock(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + /* + * Check STATUS word (Word 0) for an Input CRC Error from the + * previous SPI frame. + */ + status =3D get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(&priv->spi->dev, + "Previous input CRC Error reported in STATUS (0x%04x)\n", + status); + } + + /* + * Validate the output CRC on the current data frame to ensure + * data integrity. + */ + ret =3D ads131m_verify_output_crc(priv); + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + buf =3D &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val =3D sign_extend32(get_unaligned_be24(buf), 23); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec 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: + /* + * Scale =3D (Vref / Gain) / 2^(Resolution - 1) + * Vref =3D 1.2V (1200mV) [DS, 8.3.4], Resolution =3D 24 bits + * LSB =3D (1.2V / Gain) / 2^23 + * + * Using IIO_VAL_FRACTIONAL_LOG2, the unit is millivolts. + * Scale =3D val * 2^(-val2) + * Scale =3D 1200 * 2^-(23 + log2(Gain)) + * + * priv->gain[] stores log2(Gain) (e.g., 0 for Gain=3D1). + */ + *val =3D 1200; /* 1.2Vref, in millivolts */ + *val2 =3D 23 + priv->gain[channel->channel]; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type =3D IIO_VOLTAGE, \ + .indexed =3D 1, \ + .channel =3D (num), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) \ + } + +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 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 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 ads131m03_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m02_channels[] =3D { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct ads131m_configuration ads131m_config[] =3D { + [ADS131M08_ID] =3D { + .channels =3D ads131m08_channels, + .num_channels =3D ARRAY_SIZE(ads131m08_channels), + .reset_ack =3D 0xFF28, + }, + [ADS131M06_ID] =3D { + .channels =3D ads131m06_channels, + .num_channels =3D ARRAY_SIZE(ads131m06_channels), + .reset_ack =3D 0xFF26, + }, + [ADS131M04_ID] =3D { + .channels =3D ads131m04_channels, + .num_channels =3D ARRAY_SIZE(ads131m04_channels), + .reset_ack =3D 0xFF24, + }, + [ADS131M03_ID] =3D { + .channels =3D ads131m03_channels, + .num_channels =3D ARRAY_SIZE(ads131m03_channels), + .reset_ack =3D 0xFF23, + }, + [ADS131M02_ID] =3D { + .channels =3D ads131m02_channels, + .num_channels =3D ARRAY_SIZE(ads131m02_channels), + .reset_ack =3D 0xFF22, + }, +}; + +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 pre-configured message is NOT suitable for variable-length SPI + * transactions (e.g., multi-word WREG or multi-response RREG), + * which would require a separate, dynamically-sized spi_message. + */ +static void ads131m_prepare_message(struct ads131m_priv *priv) +{ + priv->xfer[0].tx_buf =3D &priv->tx_buffer[0]; + priv->xfer[0].rx_buf =3D &priv->rx_buffer[0]; + priv->xfer[0].len =3D ADS131M_FRAME_BSIZE(priv->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer[0], 1); +} + +static void ads131m_clk_disable_unprepare(void *arg) +{ + struct clk *clk =3D arg; + + clk_disable_unprepare(clk); +} + +/** + * ads131m_soft_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_soft_reset(struct ads131m_priv *priv) +{ + struct spi_device *spi =3D priv->spi; + u16 response; + int ret; + u16 expected_ack =3D priv->config->reset_ack; + + mutex_lock(&priv->lock); + dev_dbg(&spi->dev, "Sending RESET command\n"); + ret =3D ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) { + dev_err(&spi->dev, "Failed to send RESET command\n"); + goto err_unlock; + } + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + usleep_range(ADS131M_RESET_DELAY_US, ADS131M_RESET_DELAY_US + 5); + + /* Cycle 2: Send NULL+CRC to retrieve the response to the RESET */ + dev_dbg(&spi->dev, "Reading RESET ACK\n"); + ret =3D ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err(&spi->dev, "Failed to read RESET ACK\n"); + goto err_unlock; + } + + response =3D get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response !=3D expected_ack) { + dev_warn(&spi->dev, "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + ret =3D -EIO; + goto err_unlock; + } + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + if (ads131m_check_status_crc_err(priv) < 0) + ret =3D -EIO; + +err_unlock: + mutex_unlock(&priv->lock); + return ret; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * + * This function performs the hardware-specific initialization sequence: + * 1. Enables the main clock. + * 2. Issues a software RESET command to clear FIFOs and defaults. + * 3. Configures the MODE register to clear RESET, set CCITT CRC, + * and enable Input CRC checking. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv) +{ + struct spi_device *spi =3D priv->spi; + u16 clear_mask, set_mask; + int ret; + + ret =3D clk_prepare_enable(priv->clk); + if (ret) { + dev_err(&spi->dev, "clk enable failed: %d\n", ret); + return ret; + } + ret =3D devm_add_action_or_reset(&spi->dev, ads131m_clk_disable_unprepare, + priv->clk); + if (ret) { + clk_disable_unprepare(priv->clk); + return ret; + } + + /* + * Issue a software RESET to ensure device is in a known state. + * This clears the 2-deep FIFO and resets all registers to default. + */ + ret =3D ads131m_soft_reset(priv); + if (ret < 0) + return ret; + + /* + * 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). + */ + clear_mask =3D ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + set_mask =3D ADS131M_MODE_RX_CRC_EN; + + ret =3D ads131m_rmw_reg(priv, ADS131M_REG_MODE, clear_mask, set_mask); + if (ret < 0) { + dev_err(&spi->dev, "Failed to configure MODE register\n"); + return ret; + } + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + int ret; + + spi->mode =3D SPI_MODE_1; + spi->bits_per_word =3D 8; + + if (!spi->max_speed_hz || spi->max_speed_hz > ADS131M_MAX_SCLK_HZ) + spi->max_speed_hz =3D ADS131M_MAX_SCLK_HZ; + + ret =3D spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "Error in spi setup\n"); + return ret; + } + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv =3D iio_priv(indio_dev); + priv->spi =3D spi; + + indio_dev->name =3D spi_get_device_id(spi)->name; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->info =3D &ads131m_info; + + config =3D device_get_match_data(&spi->dev); + if (!config) { + const struct spi_device_id *id; + + id =3D spi_get_device_id(spi); + if (!id) + return -ENODEV; + + config =3D (const void *)id->driver_data; + } + priv->config =3D config; + + indio_dev->channels =3D config->channels; + indio_dev->num_channels =3D config->num_channels; + priv->num_channels =3D config->num_channels; + + /* Get the external clock source connected to the CLKIN pin */ + priv->clk =3D devm_clk_get(&spi->dev, NULL); + if (IS_ERR(priv->clk)) { + ret =3D PTR_ERR(priv->clk); + dev_err(&spi->dev, "clk get failed: %d\n", ret); + return ret; + } + + mutex_init(&priv->lock); + /* Setup the reusable SPI message structure */ + ads131m_prepare_message(priv); + + /* + * Perform all hardware-specific initialization. + */ + ret =3D ads131m_hw_init(priv); + if (ret < 0) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] =3D { + { .compatible =3D "ti,ads131m08", .data =3D &ads131m_config[ADS131M08_ID]= }, + { .compatible =3D "ti,ads131m06", .data =3D &ads131m_config[ADS131M06_ID]= }, + { .compatible =3D "ti,ads131m04", .data =3D &ads131m_config[ADS131M04_ID]= }, + { .compatible =3D "ti,ads131m03", .data =3D &ads131m_config[ADS131M03_ID]= }, + { .compatible =3D "ti,ads131m02", .data =3D &ads131m_config[ADS131M02_ID]= }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] =3D { + { "ads131m08", (kernel_ulong_t)&ads131m_config[ADS131M08_ID] }, + { "ads131m06", (kernel_ulong_t)&ads131m_config[ADS131M06_ID] }, + { "ads131m04", (kernel_ulong_t)&ads131m_config[ADS131M04_ID] }, + { "ads131m03", (kernel_ulong_t)&ads131m_config[ADS131M03_ID] }, + { "ads131m02", (kernel_ulong_t)&ads131m_config[ADS131M02_ID] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver =3D { + .driver =3D { + .name =3D "ads131m0x", + .of_match_table =3D of_match_ptr(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 ADS131M0x ADC driver"); +MODULE_LICENSE("GPL"); --=20 2.47.3