Linux IIO development
 help / color / mirror / Atom feed
* [PATCH 0/2] iio: adc: add TI ADS1299
@ 2026-10-19  0:12 Stephan Costa Barros
  2026-10-19  0:12 ` [PATCH 1/2] dt-bindings: " Stephan Costa Barros
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Stephan Costa Barros @ 2026-10-19  0:12 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Mike Looijmans, linux-iio,
	devicetree, linux-kernel, Stephan Costa Barros

This series adds a driver for the TI ADS1299 family (ADS1299, ADS1299-6
and ADS1299-4): 24-bit, simultaneous-sampling biopotential front-ends
for EEG, on SPI.

This first version keeps to the generic IIO ABI: raw reads, the gain of
each channel's PGA through its scale, the sampling frequency, and a
kfifo buffer fed from the DRDY interrupt with a software timestamp.

The part has more, which I would like to add in follow-up series, each
with its own ABI discussion: SRB1 and SRB2 reference routing, the bias
drive amplifier, lead-off detection (through IIO_EV_TYPE_FAULT and
IIO_EV_DIR_FAULT_OPENWIRE), the internal test signal, the temperature
sensor and supply monitors, GPIO1 to GPIO4 as a gpiochip, the CLK pin
as a clock output, and daisy chains. The binding already describes the
hardware for those.

A question on structure. The driver started from ti-ads1298.c, and in
this subset the two are close. What differs here is the PGA gain table
(1 to 24 against 1 to 12 with 3), the data rates (no high-resolution
mode), the fixed 4.5 V internal reference, the ID register layout and
the 4 and 6 channel variants; above 0x14 the register maps also part
(MISC1 and MISC2 here, PACE, RESP and WCT on the ADS1298). Most of what
would follow, SRB1 and SRB2, the bias drive and lead-off on every
channel, is specific to the ADS1299, so I kept a separate driver. If you
would rather have the ADS1299 as a variant in ti-ads1298, I can rework
it that way.

Tested on a Raspberry Pi 4B running 6.18.50 (Raspberry Pi OS), with one
ADS1299 and DRDY on a GPIO edge interrupt, the driver built out of tree
from this patch:
- every value in in_voltage_scale_available and
  sampling_frequency_available written and read back, and values outside
  the lists refused;
- the internal DC test signal, routed through debugfs register access,
  read at gain 24 against shorted inputs: -0.08 % to -0.41 % from
  VREF / 2400 across the eight channels, as with the out-of-tree driver
  on the same board;
- a 10 s capture at 250 SPS: 2497 samples for 2497 DRDY interrupts, no
  gaps in the timestamps, and buffered values matching direct reads.
So on 6.18 the driver was run and tested as above; on the IIO tree below,
the same source, unchanged, was compiled but not run.

The series is based on the testing branch of Jonathan's IIO tree
(git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git).

Stephan Costa Barros (2):
  dt-bindings: iio: adc: add TI ADS1299
  iio: adc: ti-ads1299: add driver for TI ADS1299

 .../bindings/iio/adc/ti,ads1299.yaml          | 221 +++++
 MAINTAINERS                                   |   7 +
 drivers/iio/adc/Kconfig                       |  14 +
 drivers/iio/adc/Makefile                      |   1 +
 drivers/iio/adc/ti-ads1299.c                  | 897 ++++++++++++++++++
 5 files changed, 1140 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml
 create mode 100644 drivers/iio/adc/ti-ads1299.c


base-commit: 19c5db3507633edc21b4172d99f573c2dfbf42b1
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] dt-bindings: iio: adc: add TI ADS1299
  2026-10-19  0:12 [PATCH 0/2] iio: adc: add TI ADS1299 Stephan Costa Barros
@ 2026-10-19  0:12 ` Stephan Costa Barros
  2026-10-19  0:12 ` [PATCH 2/2] iio: adc: ti-ads1299: add driver for " Stephan Costa Barros
  2026-10-19 10:16 ` [PATCH 0/2] iio: adc: add " Andy Shevchenko
  2 siblings, 0 replies; 5+ messages in thread
From: Stephan Costa Barros @ 2026-10-19  0:12 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Mike Looijmans, linux-iio,
	devicetree, linux-kernel, Stephan Costa Barros

The ADS1299 is an 8-channel, 24-bit, simultaneous-sampling biopotential
(EEG) analog front-end with an SPI interface. The ADS1299-6 and ADS1299-4
are the same part with six and four channels.

One converter can drive its clock out of its CLK pin to the others
(#clock-cells), and converters sharing a chip select in daisy-chain mode
are described as one node with #daisy-chained-devices.

Signed-off-by: Stephan Costa Barros <stephanbrrs8@gmail.com>
---
 .../bindings/iio/adc/ti,ads1299.yaml          | 221 ++++++++++++++++++
 MAINTAINERS                                   |   6 +
 2 files changed, 227 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml

diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml
new file mode 100644
index 000000000..a8966cd07
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml
@@ -0,0 +1,221 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1299.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments ADS1299 biopotential analog front-end
+
+maintainers:
+  - Stephan Costa Barros <stephanbrrs8@gmail.com>
+
+description: |
+  Texas Instruments ADS1299 family: low-noise, 24-bit, simultaneous-sampling
+  biopotential (EEG) analog front-ends with an SPI interface. The ADS1299 has
+  eight channels, the ADS1299-6 six and the ADS1299-4 four.
+
+  Each channel has its own programmable gain amplifier and a multiplexer that
+  can select the electrode inputs, a shorted input, the internal test signal,
+  the supply or the temperature sensor. The part also provides lead-off
+  detection and a bias drive amplifier for the driven reference electrode.
+
+  The converter signals a completed conversion on its DRDY output and is read
+  over SPI in mode 1 (CPOL=0, CPHA=1).
+
+  Several converters can share one SPI bus in two ways. In cascaded mode each
+  has its own chip select and its own node; one of them can drive its internal
+  oscillator out of its CLK pin (#clock-cells) for the others to use as their
+  external clock. In daisy-chain mode they share the chip select and DIN, the
+  DOUT of each one feeds the DAISY_IN of the one before it, and the chain is a
+  single node with #daisy-chained-devices. A daisy chain shares DIN, so every
+  register write reaches every device: the devices run with one configuration
+  and from one external clock, and the bias drive amplifier cannot be used.
+
+  https://www.ti.com/lit/gpn/ads1299
+
+properties:
+  compatible:
+    enum:
+      - ti,ads1299
+      - ti,ads1299-4
+      - ti,ads1299-6
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description:
+      The DRDY output of the converter, asserted low when a conversion is
+      ready to be read.
+
+  reset-gpios:
+    maxItems: 1
+    description: The active-low RESET input of the converter.
+
+  powerdown-gpios:
+    maxItems: 1
+    description:
+      The active-low PWDN input.
+
+  clocks:
+    maxItems: 1
+    description:
+      External clock on the CLK pin, used when the CLKSEL pin selects an
+      external clock. Omit when the part runs from its internal oscillator.
+
+  clock-names:
+    const: clk
+
+  '#clock-cells':
+    const: 0
+    description:
+      The CLK pin as a clock output, for a part whose CLKSEL pin selects the
+      internal oscillator.
+
+  clock-output-names:
+    maxItems: 1
+
+  gpio-controller:
+    description:
+      GPIO1 to GPIO4 are wired for use as general-purpose pins. Omit when they
+      are tied to DGND, as the datasheet asks of unused ones, so that no pin
+      tied to ground can be driven as an output.
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-line-names:
+    minItems: 1
+    maxItems: 4
+
+  '#daisy-chained-devices':
+    description:
+      Number of converters in the daisy chain, all of the type the compatible
+      names. Their channels are reported in chain order, the device whose DOUT
+      reaches the host first.
+    minimum: 1
+    maximum: 8
+    default: 1
+
+  avdd-supply:
+    description: Analog supply.
+
+  dvdd-supply:
+    description: Digital supply.
+
+  vref-supply:
+    description:
+      External voltage reference on VREFP. Omit to use the internal 4.5 V
+      reference.
+
+  biasref-supply:
+    description:
+      External voltage on the BIASREF pin, the reference of the bias drive
+      amplifier. Omit to have the part generate (AVDD + AVSS) / 2.
+
+  spi-cpha: true
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - avdd-supply
+  - spi-cpha
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+  - if:
+      required:
+        - '#clock-cells'
+    then:
+      properties:
+        clocks: false
+  - if:
+      required:
+        - '#daisy-chained-devices'
+      properties:
+        '#daisy-chained-devices':
+          minimum: 2
+    then:
+      required:
+        - clocks
+      properties:
+        '#clock-cells': false
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "ti,ads1299";
+            reg = <0>;
+            spi-max-frequency = <4000000>;
+            spi-cpha;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            reset-gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
+            powerdown-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
+            avdd-supply = <&reg_avdd>;
+            dvdd-supply = <&reg_dvdd>;
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-line-names = "ADC_GPIO1", "ADC_GPIO2", "", "STATUS_LED";
+        };
+    };
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    /* Cascaded: the first converter clocks the second */
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc0: adc@0 {
+            compatible = "ti,ads1299";
+            reg = <0>;
+            spi-cpha;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            avdd-supply = <&reg_avdd>;
+            #clock-cells = <0>;
+        };
+
+        adc@1 {
+            compatible = "ti,ads1299";
+            reg = <1>;
+            spi-cpha;
+            interrupt-parent = <&gpio1>;
+            interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
+            avdd-supply = <&reg_avdd>;
+            clocks = <&adc0>;
+            clock-names = "clk";
+        };
+    };
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    /* Daisy chain: three converters, 24 channels, one chip select */
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "ti,ads1299";
+            reg = <0>;
+            spi-cpha;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            avdd-supply = <&reg_avdd>;
+            clocks = <&osc_2048k>;
+            clock-names = "clk";
+            #daisy-chained-devices = <3>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 52024397d..b4a8e4573 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27370,6 +27370,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
 F:	drivers/iio/adc/ti-ads1018.c
 
+TI ADS1299 ADC DRIVER
+M:	Stephan Costa Barros <stephanbrrs8@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml
+
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
 L:	linux-iio@vger.kernel.org
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] iio: adc: ti-ads1299: add driver for TI ADS1299
  2026-10-19  0:12 [PATCH 0/2] iio: adc: add TI ADS1299 Stephan Costa Barros
  2026-10-19  0:12 ` [PATCH 1/2] dt-bindings: " Stephan Costa Barros
@ 2026-10-19  0:12 ` Stephan Costa Barros
  2026-10-19  7:03   ` Uwe Kleine-König
  2026-10-19 10:16 ` [PATCH 0/2] iio: adc: add " Andy Shevchenko
  2 siblings, 1 reply; 5+ messages in thread
From: Stephan Costa Barros @ 2026-10-19  0:12 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Mike Looijmans, linux-iio,
	devicetree, linux-kernel, Stephan Costa Barros

Add a driver for the TI ADS1299, ADS1299-6 and ADS1299-4: low-noise
24-bit biopotential (EEG) analog front-ends with eight, six and four
channels, on SPI. It is derived from ti-ads1298.c.

The driver reads each channel on demand, or all of them through a kfifo
buffer fed from the DRDY interrupt. The gain of each channel's PGA is
set through its scale, and the data rate through sampling_frequency.

The channel count is read from the ID register. It sets the length of
each data read, so when it differs from the compatible the part found
is used, with a warning.

The reference is the internal 4.5 V one unless a vref supply is given,
and the clock the internal oscillator unless an external one is given.
Daisy-chained converters are refused.

Signed-off-by: Stephan Costa Barros <stephanbrrs8@gmail.com>
---
 MAINTAINERS                  |   1 +
 drivers/iio/adc/Kconfig      |  14 +
 drivers/iio/adc/Makefile     |   1 +
 drivers/iio/adc/ti-ads1299.c | 897 +++++++++++++++++++++++++++++++++++
 4 files changed, 913 insertions(+)
 create mode 100644 drivers/iio/adc/ti-ads1299.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b4a8e4573..29dcc2c2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27375,6 +27375,7 @@ M:	Stephan Costa Barros <stephanbrrs8@gmail.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1299.yaml
+F:	drivers/iio/adc/ti-ads1299.c
 
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 02de4809a..b9f2d3d6e 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1913,6 +1913,20 @@ config TI_ADS1298
 	  This driver can also be built as a module. If so, the module will be
 	  called ti-ads1298.
 
+config TI_ADS1299
+	tristate "Texas Instruments ADS1299"
+	depends on SPI
+	select IIO_BUFFER
+	select IIO_KFIFO_BUF
+	select REGMAP
+	help
+	  Say yes here to build support for Texas Instruments ADS1299,
+	  ADS1299-6 and ADS1299-4 low-noise 24-bit biopotential (EEG)
+	  analog front-ends.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ti-ads1299.
+
 config TI_ADS131E08
 	tristate "Texas Instruments ADS131E08"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 9e82c21db..4bb543b66 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -162,6 +162,7 @@ obj-$(CONFIG_TI_ADS112C04) += ti-ads112c04.o
 obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
 obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
 obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
+obj-$(CONFIG_TI_ADS1299) += ti-ads1299.o
 obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
 obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
 obj-$(CONFIG_TI_ADS7138) += ti-ads7138.o
diff --git a/drivers/iio/adc/ti-ads1299.c b/drivers/iio/adc/ti-ads1299.c
new file mode 100644
index 000000000..f817a86cc
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1299.c
@@ -0,0 +1,897 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI ADS1299 4/6/8-channel, 24-bit biopotential ADC
+ *
+ * Copyright (C) 2023 - 2024 Topic Embedded Products
+ * Copyright (C) 2026 Stephan Costa Barros <stephanbrrs8@gmail.com>
+ *
+ * Based on ti-ads1298.c by Mike Looijmans <mike.looijmans@topic.nl>.
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/ads1299.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+
+#include <linux/unaligned.h>
+
+/* Commands */
+#define ADS1299_CMD_RESET			0x06
+#define ADS1299_CMD_START			0x08
+#define ADS1299_CMD_STOP			0x0a
+#define ADS1299_CMD_SDATAC			0x11
+#define ADS1299_CMD_RDATA			0x12
+#define ADS1299_CMD_RREG			0x20
+#define ADS1299_CMD_WREG			0x40
+
+/* REV_ID[7:5] | 1 | DEV_ID[3:2] | NU_CH[1:0]; REV_ID may change */
+#define ADS1299_REG_ID				0x00
+#define ADS1299_MASK_ID_FAMILY			GENMASK(4, 2)
+#define ADS1299_MASK_ID_CHANNELS		GENMASK(1, 0)
+#define ADS1299_ID_FAMILY_ADS1299		0x7
+
+/* Data rate = f_CLK / 2^(7 + DR); DR = 7 is reserved */
+#define ADS1299_REG_CONFIG1			0x01
+#define ADS1299_MASK_CONFIG1_DR			GENMASK(2, 0)
+#define ADS1299_SHIFT_DR			7
+#define ADS1299_NUM_DR				7
+
+/* PWR_REFBUF is the datasheet's PD_REFBUF: 1 powers the buffer up */
+#define ADS1299_REG_CONFIG3			0x03
+#define ADS1299_MASK_CONFIG3_PWR_REFBUF		BIT(7)
+#define ADS1299_MASK_CONFIG3_RESERVED		GENMASK(6, 5)
+#define ADS1299_CONFIG3_RESERVED_VALUE					\
+	FIELD_PREP(ADS1299_MASK_CONFIG3_RESERVED, 0x3)
+
+#define ADS1299_REG_CHnSET(n)			(0x05 + (n))
+#define ADS1299_MASK_CH_PD			BIT(7)
+#define ADS1299_MASK_CH_PGA			GENMASK(6, 4)
+#define ADS1299_MASK_CH_MUX			GENMASK(2, 0)
+#define ADS1299_MUX_NORMAL			0
+
+#define ADS1299_REG_LOFF_STATP			0x12
+#define ADS1299_REG_LOFF_STATN			0x13
+#define ADS1299_REG_GPIO			0x14
+
+#define ADS1299_REG_CONFIG4			0x17
+#define ADS1299_MASK_CONFIG4_SINGLE_SHOT	BIT(3)
+
+#define ADS1299_MAX_CHANNELS			8
+#define ADS1299_BITS_PER_SAMPLE			24
+#define ADS1299_CLK_RATE_HZ			2048000
+#define ADS1299_CLOCKS_TO_USECS(x)					\
+	(DIV_ROUND_UP((x) * MICROHZ_PER_HZ, ADS1299_CLK_RATE_HZ))
+
+/* Power-up and reference start-up time */
+#define ADS1299_START_UP_MS			150
+
+#define ADS1299_INTERNAL_VREF_UV		4500000
+
+/* A command takes 4 t_CLK to decode, so register access runs at f_CLK */
+#define ADS1299_SPI_BUS_SPEED_SLOW		ADS1299_CLK_RATE_HZ
+#define ADS1299_SPI_CMD_BUFFER_SIZE		3
+
+/* Command byte, 24-bit status word and n 24-bit samples */
+#define ADS1299_SPI_RDATA_BUFFER_SIZE(n)	(((n) + 1) * 3 + 1)
+#define ADS1299_SPI_RDATA_BUFFER_SIZE_MAX				\
+	ADS1299_SPI_RDATA_BUFFER_SIZE(ADS1299_MAX_CHANNELS)
+
+/* Three bytes per sample in RX buffer, starting at offset 4 */
+#define ADS1299_OFFSET_IN_RX_BUFFER(index)	(3 * (index) + 4)
+
+/* PGA field to gain; 111 is reserved */
+static const int ads1299_pga_gain[] = { 1, 2, 4, 6, 8, 12, 24 };
+
+struct ads1299_chip_info {
+	const char *name;
+	unsigned int num_channels;
+};
+
+/* The NU_CH field of the ID register; 3 is reserved */
+enum ads1299_nu_ch {
+	ADS1299_NU_CH_4,
+	ADS1299_NU_CH_6,
+	ADS1299_NU_CH_8,
+};
+
+static const struct ads1299_chip_info ads1299_chip_info_tbl[] = {
+	[ADS1299_NU_CH_4] = { .name = "ads1299-4", .num_channels = 4 },
+	[ADS1299_NU_CH_6] = { .name = "ads1299-6", .num_channels = 6 },
+	[ADS1299_NU_CH_8] = { .name = "ads1299", .num_channels = 8 },
+};
+
+struct ads1299_private {
+	struct spi_device *spi;
+	struct regmap *regmap;
+	struct completion completion;
+	struct spi_transfer rdata_xfer;
+	struct spi_message rdata_msg;
+	spinlock_t irq_busy_lock; /* Handshake between SPI and DRDY irqs */
+	/*
+	 * 0 = idle, 1 = transfer in progress, 2 = a sample became due during
+	 * it; any more were lost.
+	 */
+	unsigned int rdata_xfer_busy;
+	unsigned int num_adc_channels;
+	int samp_freq_avail[ADS1299_NUM_DR];
+	int scale_avail[ARRAY_SIZE(ads1299_pga_gain)][2];
+
+	struct {
+		u32 chan[ADS1299_MAX_CHANNELS];
+		aligned_s64 timestamp;
+	} scan;
+
+	/* For synchronous SPI exchanges (read/write registers) */
+	u8 cmd_buffer[ADS1299_SPI_CMD_BUFFER_SIZE] __aligned(IIO_DMA_MINALIGN);
+
+	u8 rx_buffer[ADS1299_SPI_RDATA_BUFFER_SIZE_MAX];
+	/* Contains the RDATA command and zeroes to clock out */
+	u8 tx_buffer[ADS1299_SPI_RDATA_BUFFER_SIZE_MAX];
+};
+
+#define ADS1299_CHAN(index)						\
+{									\
+	.type = IIO_VOLTAGE,						\
+	.indexed = 1,							\
+	.channel = index,						\
+	.address = ADS1299_OFFSET_IN_RX_BUFFER(index),			\
+	.info_mask_separate =						\
+		BIT(IIO_CHAN_INFO_RAW) |				\
+		BIT(IIO_CHAN_INFO_SCALE),				\
+	.info_mask_shared_by_type_available =				\
+		BIT(IIO_CHAN_INFO_SCALE),				\
+	.info_mask_shared_by_all =					\
+		BIT(IIO_CHAN_INFO_SAMP_FREQ),				\
+	.info_mask_shared_by_all_available =				\
+		BIT(IIO_CHAN_INFO_SAMP_FREQ),				\
+	.scan_index = index,						\
+	.scan_type = {							\
+		.sign = 's',						\
+		.realbits = ADS1299_BITS_PER_SAMPLE,			\
+		.storagebits = 32,					\
+		.endianness = IIO_CPU,					\
+	},								\
+}
+
+static const struct iio_chan_spec ads1299_channels[ADS1299_MAX_CHANNELS] = {
+	ADS1299_CHAN(0),
+	ADS1299_CHAN(1),
+	ADS1299_CHAN(2),
+	ADS1299_CHAN(3),
+	ADS1299_CHAN(4),
+	ADS1299_CHAN(5),
+	ADS1299_CHAN(6),
+	ADS1299_CHAN(7),
+};
+
+static int ads1299_write_cmd(struct ads1299_private *priv, u8 command)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = priv->cmd_buffer,
+		.rx_buf = priv->cmd_buffer,
+		.len = 1,
+		.speed_hz = ADS1299_SPI_BUS_SPEED_SLOW,
+		.delay = {
+			.value = 2,
+			.unit = SPI_DELAY_UNIT_USECS,
+		},
+	};
+
+	priv->cmd_buffer[0] = command;
+
+	return spi_sync_transfer(priv->spi, &xfer, 1);
+}
+
+static int ads1299_read_one(struct ads1299_private *priv, int chan_index)
+{
+	int ret;
+
+	/* Enable the channel */
+	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CHnSET(chan_index),
+				 ADS1299_MASK_CH_PD, 0);
+	if (ret)
+		return ret;
+
+	/* Enable single-shot mode, so we don't need to send a STOP */
+	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CONFIG4,
+				 ADS1299_MASK_CONFIG4_SINGLE_SHOT,
+				 ADS1299_MASK_CONFIG4_SINGLE_SHOT);
+	if (ret)
+		return ret;
+
+	reinit_completion(&priv->completion);
+
+	ret = ads1299_write_cmd(priv, ADS1299_CMD_START);
+	if (ret < 0) {
+		dev_err(&priv->spi->dev, "CMD_START error: %d\n", ret);
+		return ret;
+	}
+
+	/* A conversion takes 4 ms at the lowest rate, plus bus latency */
+	ret = wait_for_completion_timeout(&priv->completion, msecs_to_jiffies(100));
+	if (!ret)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int ads1299_get_samp_freq(struct ads1299_private *priv, int *val)
+{
+	unsigned int cfg;
+	unsigned int dr;
+	int ret;
+
+	ret = regmap_read(priv->regmap, ADS1299_REG_CONFIG1, &cfg);
+	if (ret)
+		return ret;
+
+	dr = FIELD_GET(ADS1299_MASK_CONFIG1_DR, cfg);
+	if (dr >= ADS1299_NUM_DR)
+		return -EINVAL;
+
+	*val = priv->samp_freq_avail[dr];
+
+	return IIO_VAL_INT;
+}
+
+static int ads1299_set_samp_freq(struct ads1299_private *priv, int val)
+{
+	unsigned int dr;
+
+	/* Exact rates only: the part has nothing in between */
+	for (dr = 0; dr < ADS1299_NUM_DR; dr++) {
+		if (priv->samp_freq_avail[dr] == val)
+			return regmap_update_bits(priv->regmap,
+						  ADS1299_REG_CONFIG1,
+						  ADS1299_MASK_CONFIG1_DR,
+						  FIELD_PREP(ADS1299_MASK_CONFIG1_DR,
+							     dr));
+	}
+
+	return -EINVAL;
+}
+
+static int ads1299_get_scale(struct ads1299_private *priv,
+			     unsigned int channel, int *val, int *val2)
+{
+	unsigned int regval;
+	unsigned int pga;
+	int ret;
+
+	ret = regmap_read(priv->regmap, ADS1299_REG_CHnSET(channel), &regval);
+	if (ret)
+		return ret;
+
+	pga = FIELD_GET(ADS1299_MASK_CH_PGA, regval);
+	if (pga >= ARRAY_SIZE(ads1299_pga_gain))
+		return -EINVAL;
+
+	*val = priv->scale_avail[pga][0];
+	*val2 = priv->scale_avail[pga][1];
+
+	return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int ads1299_set_scale(struct ads1299_private *priv,
+			     unsigned int channel, int val, int val2)
+{
+	unsigned int pga;
+
+	for (pga = 0; pga < ARRAY_SIZE(ads1299_pga_gain); pga++) {
+		if (priv->scale_avail[pga][0] == val &&
+		    priv->scale_avail[pga][1] == val2)
+			return regmap_update_bits(priv->regmap,
+						  ADS1299_REG_CHnSET(channel),
+						  ADS1299_MASK_CH_PGA,
+						  FIELD_PREP(ADS1299_MASK_CH_PGA,
+							     pga));
+	}
+
+	return -EINVAL;
+}
+
+static int ads1299_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+
+		ret = ads1299_read_one(priv, chan->scan_index);
+		if (!ret)
+			*val = sign_extend32(get_unaligned_be24(priv->rx_buffer +
+								chan->address),
+					     ADS1299_BITS_PER_SAMPLE - 1);
+
+		iio_device_release_direct(indio_dev);
+
+		return ret ? ret : IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		return ads1299_get_scale(priv, chan->channel, val, val2);
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ads1299_get_samp_freq(priv, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads1299_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      const int **vals, int *type, int *length,
+			      long mask)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*vals = priv->samp_freq_avail;
+		*length = ARRAY_SIZE(priv->samp_freq_avail);
+		*type = IIO_VAL_INT;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SCALE:
+		*vals = (const int *)priv->scale_avail;
+		*length = ARRAY_SIZE(priv->scale_avail) * 2;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads1299_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	int ret;
+
+	if (!iio_device_claim_direct(indio_dev))
+		return -EBUSY;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret = ads1299_set_samp_freq(priv, val);
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		ret = ads1299_set_scale(priv, chan->channel, val, val2);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	iio_device_release_direct(indio_dev);
+
+	return ret;
+}
+
+static int ads1299_write_raw_get_fmt(struct iio_dev *indio_dev,
+				     struct iio_chan_spec const *chan,
+				     long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads1299_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct ads1299_private *priv = context;
+	struct spi_transfer reg_write_xfer = {
+		.tx_buf = priv->cmd_buffer,
+		.rx_buf = priv->cmd_buffer,
+		.len = 3,
+		.speed_hz = ADS1299_SPI_BUS_SPEED_SLOW,
+		.delay = {
+			.value = 2,
+			.unit = SPI_DELAY_UNIT_USECS,
+		},
+	};
+
+	priv->cmd_buffer[0] = ADS1299_CMD_WREG | reg;
+	priv->cmd_buffer[1] = 0; /* Number of registers to be written - 1 */
+	priv->cmd_buffer[2] = val;
+
+	return spi_sync_transfer(priv->spi, &reg_write_xfer, 1);
+}
+
+static int ads1299_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct ads1299_private *priv = context;
+	struct spi_transfer reg_read_xfer = {
+		.tx_buf = priv->cmd_buffer,
+		.rx_buf = priv->cmd_buffer,
+		.len = 3,
+		.speed_hz = ADS1299_SPI_BUS_SPEED_SLOW,
+		.delay = {
+			.value = 2,
+			.unit = SPI_DELAY_UNIT_USECS,
+		},
+	};
+	int ret;
+
+	priv->cmd_buffer[0] = ADS1299_CMD_RREG | reg;
+	priv->cmd_buffer[1] = 0; /* Number of registers to be read - 1 */
+	priv->cmd_buffer[2] = 0;
+
+	ret = spi_sync_transfer(priv->spi, &reg_read_xfer, 1);
+	if (ret)
+		return ret;
+
+	*val = priv->cmd_buffer[2];
+
+	return 0;
+}
+
+static int ads1299_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+			      unsigned int writeval, unsigned int *readval)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+
+	if (readval)
+		return regmap_read(priv->regmap, reg, readval);
+
+	return regmap_write(priv->regmap, reg, writeval);
+}
+
+static void ads1299_rdata_unmark_busy(struct ads1299_private *priv)
+{
+	guard(spinlock_irqsave)(&priv->irq_busy_lock);
+	priv->rdata_xfer_busy = 0;
+}
+
+static int ads1299_update_scan_mode(struct iio_dev *indio_dev,
+				    const unsigned long *scan_mask)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	unsigned int val;
+	unsigned int i;
+	int ret;
+
+	/* Make the interrupt routines start with a clean slate */
+	ads1299_rdata_unmark_busy(priv);
+
+	/* Power down the channels that are not captured */
+	for (i = 0; i < priv->num_adc_channels; i++) {
+		val = test_bit(i, scan_mask) ? 0 : ADS1299_MASK_CH_PD;
+		ret = regmap_update_bits(priv->regmap, ADS1299_REG_CHnSET(i),
+					 ADS1299_MASK_CH_PD, val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct iio_info ads1299_info = {
+	.read_raw = &ads1299_read_raw,
+	.read_avail = &ads1299_read_avail,
+	.write_raw = &ads1299_write_raw,
+	.write_raw_get_fmt = &ads1299_write_raw_get_fmt,
+	.update_scan_mode = &ads1299_update_scan_mode,
+	.debugfs_reg_access = &ads1299_reg_access,
+};
+
+static void ads1299_rdata_release_busy_or_restart(struct ads1299_private *priv)
+{
+	guard(spinlock_irqsave)(&priv->irq_busy_lock);
+
+	if (priv->rdata_xfer_busy > 1) {
+		/* A sample became due during the transfer: fetch it now */
+		spi_async(priv->spi, &priv->rdata_msg);
+		priv->rdata_xfer_busy = 1;
+	} else {
+		priv->rdata_xfer_busy = 0;
+	}
+}
+
+/* Called from SPI completion interrupt handler */
+static void ads1299_rdata_complete(void *context)
+{
+	struct iio_dev *indio_dev = context;
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	u32 *bounce = priv->scan.chan;
+	int scan_index;
+
+	if (!iio_buffer_enabled(indio_dev)) {
+		/*
+		 * For a single transfer we are kept in direct mode until
+		 * completion, avoiding a race with buffered IO.
+		 */
+		ads1299_rdata_unmark_busy(priv);
+		complete(&priv->completion);
+		return;
+	}
+
+	iio_for_each_active_channel(indio_dev, scan_index) {
+		const struct iio_chan_spec *scan_chan =
+					&indio_dev->channels[scan_index];
+
+		if (scan_chan->type == IIO_TIMESTAMP)
+			continue;
+
+		*bounce++ = get_unaligned_be24(priv->rx_buffer +
+					       scan_chan->address);
+	}
+
+	/* rx_buffer can be overwritten from this point on */
+	ads1299_rdata_release_busy_or_restart(priv);
+
+	iio_push_to_buffers_with_ts(indio_dev, &priv->scan, sizeof(priv->scan),
+				    iio_get_time_ns(indio_dev));
+}
+
+static irqreturn_t ads1299_interrupt(int irq, void *dev_id)
+{
+	struct iio_dev *indio_dev = dev_id;
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	unsigned int wasbusy;
+
+	guard(spinlock_irqsave)(&priv->irq_busy_lock);
+
+	wasbusy = priv->rdata_xfer_busy++;
+	/* When no SPI transfer is in transit, start one now */
+	if (!wasbusy)
+		spi_async(priv->spi, &priv->rdata_msg);
+
+	return IRQ_HANDLED;
+}
+
+static int ads1299_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	int ret;
+
+	/* Disable single-shot mode */
+	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CONFIG4,
+				 ADS1299_MASK_CONFIG4_SINGLE_SHOT, 0);
+	if (ret)
+		return ret;
+
+	return ads1299_write_cmd(priv, ADS1299_CMD_START);
+}
+
+static int ads1299_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+
+	return ads1299_write_cmd(priv, ADS1299_CMD_STOP);
+}
+
+static const struct iio_buffer_setup_ops ads1299_setup_ops = {
+	.postenable = &ads1299_buffer_postenable,
+	.predisable = &ads1299_buffer_predisable,
+};
+
+/* CONFIG3 holds the read-only BIAS_STAT; GPIO reads the pins */
+static const struct regmap_range ads1299_regmap_volatile_range[] = {
+	regmap_reg_range(ADS1299_REG_CONFIG3, ADS1299_REG_CONFIG3),
+	regmap_reg_range(ADS1299_REG_LOFF_STATP, ADS1299_REG_LOFF_STATN),
+	regmap_reg_range(ADS1299_REG_GPIO, ADS1299_REG_GPIO),
+};
+
+static const struct regmap_access_table ads1299_regmap_volatile = {
+	.yes_ranges = ads1299_regmap_volatile_range,
+	.n_yes_ranges = ARRAY_SIZE(ads1299_regmap_volatile_range),
+};
+
+static const struct regmap_range ads1299_regmap_read_only_range[] = {
+	regmap_reg_range(ADS1299_REG_ID, ADS1299_REG_ID),
+	regmap_reg_range(ADS1299_REG_LOFF_STATP, ADS1299_REG_LOFF_STATN),
+};
+
+static const struct regmap_access_table ads1299_regmap_writeable = {
+	.no_ranges = ads1299_regmap_read_only_range,
+	.n_no_ranges = ARRAY_SIZE(ads1299_regmap_read_only_range),
+};
+
+static const struct regmap_config ads1299_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.reg_read = ads1299_reg_read,
+	.reg_write = ads1299_reg_write,
+	.max_register = ADS1299_REG_CONFIG4,
+	.wr_table = &ads1299_regmap_writeable,
+	.volatile_table = &ads1299_regmap_volatile,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static int ads1299_init(struct iio_dev *indio_dev, bool internal_vref)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	const struct ads1299_chip_info *expected, *info;
+	struct device *dev = &priv->spi->dev;
+	unsigned int nu_ch;
+	unsigned int val;
+	unsigned int i;
+	int ret;
+
+	/* The device starts in RDATAC mode, in which it ignores RREG/WREG */
+	ret = ads1299_write_cmd(priv, ADS1299_CMD_SDATAC);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(priv->regmap, ADS1299_REG_ID, &val);
+	if (ret)
+		return ret;
+
+	if (FIELD_GET(ADS1299_MASK_ID_FAMILY, val) != ADS1299_ID_FAMILY_ADS1299)
+		return dev_err_probe(dev, -ENODEV, "Unknown ID: 0x%x\n", val);
+
+	nu_ch = FIELD_GET(ADS1299_MASK_ID_CHANNELS, val);
+	if (nu_ch >= ARRAY_SIZE(ads1299_chip_info_tbl))
+		return dev_err_probe(dev, -ENODEV, "Unknown ID: 0x%x\n", val);
+
+	/*
+	 * The channel count sets the length of each data read, so the part
+	 * found is used even when another one is described.
+	 */
+	info = &ads1299_chip_info_tbl[nu_ch];
+	expected = spi_get_device_match_data(priv->spi);
+	if (expected && expected != info)
+		dev_warn(dev, "Found %s (ID 0x%x) instead of %s\n", info->name,
+			 val, expected->name);
+
+	priv->num_adc_channels = info->num_channels;
+	indio_dev->name = info->name;
+
+	/* Without the reference buffer every channel reads zero */
+	val = ADS1299_CONFIG3_RESERVED_VALUE;
+	if (internal_vref)
+		val |= ADS1299_MASK_CONFIG3_PWR_REFBUF;
+	ret = regmap_write(priv->regmap, ADS1299_REG_CONFIG3, val);
+	if (ret)
+		return ret;
+
+	/* Out of reset the inputs are shorted: connect them */
+	for (i = 0; i < priv->num_adc_channels; i++) {
+		ret = regmap_update_bits(priv->regmap, ADS1299_REG_CHnSET(i),
+					 ADS1299_MASK_CH_MUX,
+					 FIELD_PREP(ADS1299_MASK_CH_MUX,
+						    ADS1299_MUX_NORMAL));
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int ads1299_setup_channels(struct iio_dev *indio_dev)
+{
+	struct ads1299_private *priv = iio_priv(indio_dev);
+	unsigned int n = priv->num_adc_channels;
+	struct iio_chan_spec *channels;
+
+	channels = devm_kcalloc(&priv->spi->dev, n + 1, sizeof(*channels),
+				GFP_KERNEL);
+	if (!channels)
+		return -ENOMEM;
+
+	memcpy(channels, ads1299_channels, n * sizeof(*channels));
+	channels[n] = (struct iio_chan_spec)IIO_CHAN_SOFT_TIMESTAMP(n);
+
+	indio_dev->channels = channels;
+	indio_dev->num_channels = n + 1;
+
+	return 0;
+}
+
+static int ads1299_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct gpio_desc *powerdown_gpio;
+	struct ads1299_private *priv;
+	struct gpio_desc *reset_gpio;
+	struct iio_dev *indio_dev;
+	unsigned long clk_rate;
+	bool internal_vref;
+	struct clk *clk;
+	unsigned int i;
+	u32 chain = 1;
+	int vref_uv;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	priv = iio_priv(indio_dev);
+
+	/* Reset to be asserted before enabling clock and power */
+	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(reset_gpio),
+				     "Cannot get reset GPIO\n");
+
+	powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown",
+						 GPIOD_OUT_LOW);
+	if (IS_ERR(powerdown_gpio))
+		return dev_err_probe(dev, PTR_ERR(powerdown_gpio),
+				     "Cannot get powerdown GPIO\n");
+
+	/* VREF can be supplied externally, otherwise use the internal 4.5 V */
+	vref_uv = devm_regulator_get_enable_read_voltage(dev, "vref");
+	if (vref_uv < 0 && vref_uv != -ENODEV)
+		return dev_err_probe(dev, vref_uv, "Failed to get vref voltage\n");
+
+	internal_vref = vref_uv == -ENODEV;
+	if (internal_vref)
+		vref_uv = ADS1299_INTERNAL_VREF_UV;
+
+	clk = devm_clk_get_optional_enabled(dev, "clk");
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "Failed to get clk\n");
+
+	clk_rate = clk ? clk_get_rate(clk) : ADS1299_CLK_RATE_HZ;
+	if (!clk_rate)
+		return dev_err_probe(dev, -EINVAL, "Unknown clock rate\n");
+
+	device_property_read_u32(dev, "#daisy-chained-devices", &chain);
+	if (chain > 1)
+		return dev_err_probe(dev, -EOPNOTSUPP,
+				     "Daisy chain of %u devices not supported\n",
+				     chain);
+
+	if (spi->irq <= 0)
+		return dev_err_probe(dev, -EINVAL, "DRDY interrupt required\n");
+
+	ret = devm_regulator_get_enable(dev, "avdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable avdd regulator\n");
+
+	ret = devm_regulator_get_enable(dev, "dvdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable dvdd regulator\n");
+
+	priv->spi = spi;
+	init_completion(&priv->completion);
+	spin_lock_init(&priv->irq_busy_lock);
+	priv->regmap = devm_regmap_init(dev, NULL, priv, &ads1299_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	for (i = 0; i < ADS1299_NUM_DR; i++)
+		priv->samp_freq_avail[i] = clk_rate >> (ADS1299_SHIFT_DR + i);
+
+	/* One code is VREF / (gain * 2^23), in mV with nano precision */
+	for (i = 0; i < ARRAY_SIZE(ads1299_pga_gain); i++) {
+		priv->scale_avail[i][0] = 0;
+		priv->scale_avail[i][1] =
+			div_u64((u64)vref_uv * MICRO,
+				ads1299_pga_gain[i] <<
+				(ADS1299_BITS_PER_SAMPLE - 1));
+	}
+
+	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+	indio_dev->info = &ads1299_info;
+
+	/* Out of power-down: wait for the oscillator and the reference */
+	if (powerdown_gpio)
+		msleep(ADS1299_START_UP_MS);
+
+	if (reset_gpio) {
+		/* Minimum reset pulse width is 2 clock cycles */
+		fsleep(ADS1299_CLOCKS_TO_USECS(2));
+		gpiod_set_value_cansleep(reset_gpio, 0);
+	} else {
+		ret = ads1299_write_cmd(priv, ADS1299_CMD_RESET);
+		if (ret)
+			return dev_err_probe(dev, ret, "RESET failed\n");
+	}
+	/* Wait 18 clock cycles for the reset to complete */
+	fsleep(ADS1299_CLOCKS_TO_USECS(18));
+
+	ret = ads1299_init(indio_dev, internal_vref);
+	if (ret)
+		return dev_err_probe(dev, ret, "Init failed\n");
+
+	ret = ads1299_setup_channels(indio_dev);
+	if (ret)
+		return ret;
+
+	priv->tx_buffer[0] = ADS1299_CMD_RDATA;
+	priv->rdata_xfer.tx_buf = priv->tx_buffer;
+	priv->rdata_xfer.rx_buf = priv->rx_buffer;
+	priv->rdata_xfer.len =
+		ADS1299_SPI_RDATA_BUFFER_SIZE(priv->num_adc_channels);
+	/* Must keep CS low for 4 clocks */
+	priv->rdata_xfer.delay.value = 2;
+	priv->rdata_xfer.delay.unit = SPI_DELAY_UNIT_USECS;
+	spi_message_init_with_transfers(&priv->rdata_msg, &priv->rdata_xfer, 1);
+	priv->rdata_msg.complete = &ads1299_rdata_complete;
+	priv->rdata_msg.context = indio_dev;
+
+	ret = devm_request_irq(dev, spi->irq, &ads1299_interrupt, 0,
+			       indio_dev->name, indio_dev);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &ads1299_setup_ops);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id ads1299_id[] = {
+	{
+		.name = "ads1299",
+		.driver_data = (kernel_ulong_t)&ads1299_chip_info_tbl[ADS1299_NU_CH_8],
+	},
+	{
+		.name = "ads1299-4",
+		.driver_data = (kernel_ulong_t)&ads1299_chip_info_tbl[ADS1299_NU_CH_4],
+	},
+	{
+		.name = "ads1299-6",
+		.driver_data = (kernel_ulong_t)&ads1299_chip_info_tbl[ADS1299_NU_CH_6],
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ads1299_id);
+
+static const struct of_device_id ads1299_of_table[] = {
+	{
+		.compatible = "ti,ads1299",
+		.data = &ads1299_chip_info_tbl[ADS1299_NU_CH_8],
+	},
+	{
+		.compatible = "ti,ads1299-4",
+		.data = &ads1299_chip_info_tbl[ADS1299_NU_CH_4],
+	},
+	{
+		.compatible = "ti,ads1299-6",
+		.data = &ads1299_chip_info_tbl[ADS1299_NU_CH_6],
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ads1299_of_table);
+
+static struct spi_driver ads1299_driver = {
+	.driver = {
+		.name = "ads1299",
+		.of_match_table = ads1299_of_table,
+	},
+	.probe = ads1299_probe,
+	.id_table = ads1299_id,
+};
+module_spi_driver(ads1299_driver);
+
+MODULE_AUTHOR("Stephan Costa Barros <stephanbrrs8@gmail.com>");
+MODULE_DESCRIPTION("TI ADS1299 biopotential ADC driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] iio: adc: ti-ads1299: add driver for TI ADS1299
  2026-10-19  0:12 ` [PATCH 2/2] iio: adc: ti-ads1299: add driver for " Stephan Costa Barros
@ 2026-10-19  7:03   ` Uwe Kleine-König
  0 siblings, 0 replies; 5+ messages in thread
From: Uwe Kleine-König @ 2026-10-19  7:03 UTC (permalink / raw)
  To: Stephan Costa Barros
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Mike Looijmans,
	linux-iio, devicetree, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 395 bytes --]

Hello,

On Sun, Oct 18, 2026 at 09:12:47PM -0300, Stephan Costa Barros wrote:
> +#include <linux/mod_devicetable.h>
> [...]
> +#include <linux/spi/spi.h>

<linux/spi/spi.h> provides both of_device_id and spi_device_id. So there
is no need to also include <linux/mod_devicetable.h>. The latter pulls
in a lot of useless dependencies, so there is actually some harm in
using it.

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 0/2] iio: adc: add TI ADS1299
  2026-10-19  0:12 [PATCH 0/2] iio: adc: add TI ADS1299 Stephan Costa Barros
  2026-10-19  0:12 ` [PATCH 1/2] dt-bindings: " Stephan Costa Barros
  2026-10-19  0:12 ` [PATCH 2/2] iio: adc: ti-ads1299: add driver for " Stephan Costa Barros
@ 2026-10-19 10:16 ` Andy Shevchenko
  2 siblings, 0 replies; 5+ messages in thread
From: Andy Shevchenko @ 2026-10-19 10:16 UTC (permalink / raw)
  To: Stephan Costa Barros
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Mike Looijmans,
	linux-iio, devicetree, linux-kernel

On Sun, Oct 18, 2026 at 09:12:45PM -0300, Stephan Costa Barros wrote:
> This series adds a driver for the TI ADS1299 family (ADS1299, ADS1299-6
> and ADS1299-4): 24-bit, simultaneous-sampling biopotential front-ends
> for EEG, on SPI.

Why a brand new driver?

> This first version keeps to the generic IIO ABI: raw reads, the gain of
> each channel's PGA through its scale, the sampling frequency, and a
> kfifo buffer fed from the DRDY interrupt with a software timestamp.
> 
> The part has more, which I would like to add in follow-up series, each
> with its own ABI discussion: SRB1 and SRB2 reference routing, the bias
> drive amplifier, lead-off detection (through IIO_EV_TYPE_FAULT and
> IIO_EV_DIR_FAULT_OPENWIRE), the internal test signal, the temperature
> sensor and supply monitors, GPIO1 to GPIO4 as a gpiochip, the CLK pin
> as a clock output, and daisy chains. The binding already describes the
> hardware for those.
> 
> A question on structure. The driver started from ti-ads1298.c, and in
> this subset the two are close. What differs here is the PGA gain table
> (1 to 24 against 1 to 12 with 3), the data rates (no high-resolution
> mode), the fixed 4.5 V internal reference, the ID register layout and
> the 4 and 6 channel variants; above 0x14 the register maps also part
> (MISC1 and MISC2 here, PACE, RESP and WCT on the ADS1298). Most of what
> would follow, SRB1 and SRB2, the bias drive and lead-off on every
> channel, is specific to the ADS1299, so I kept a separate driver. If you
> would rather have the ADS1299 as a variant in ti-ads1298, I can rework
> it that way.

Okay, I think you can try to rework this as a series on top of the existing
code. I can't easily see if the above difference warrants a brand new driver.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-19 10:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19  0:12 [PATCH 0/2] iio: adc: add TI ADS1299 Stephan Costa Barros
2026-10-19  0:12 ` [PATCH 1/2] dt-bindings: " Stephan Costa Barros
2026-10-19  0:12 ` [PATCH 2/2] iio: adc: ti-ads1299: add driver for " Stephan Costa Barros
2026-10-19  7:03   ` Uwe Kleine-König
2026-10-19 10:16 ` [PATCH 0/2] iio: adc: add " Andy Shevchenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox