All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kurt Borja <kuurtb@gmail.com>
To: Kurt Borja <kuurtb@gmail.com>,
	Jonathan Cameron <jic23@kernel.org>,
	 Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	David Lechner <dlechner@baylibre.com>
Cc: "Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
Date: Sat, 10 Oct 2026 03:52:47 -0300	[thread overview]
Message-ID: <20261010-ads126x-v5-8-49e331cb2fed@gmail.com> (raw)
In-Reply-To: <20261010-ads126x-v5-0-49e331cb2fed@gmail.com>

Add triggered buffer support and a data-ready (DRDY) hardware trigger.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/Kconfig      |   2 +
 drivers/iio/adc/ti-ads1262.c | 299 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 301 insertions(+)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 06aa84a3f145..6d5df9c041ac 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1904,6 +1904,8 @@ config TI_ADS1262
 	tristate "Texas Instruments ADS1262"
 	depends on SPI
 	select REGMAP
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	help
 	  If you say yes here you get support for Texas Instruments ADS1262 and
 	  ADS1263 ADC chips.
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 49d4bf206be0..0606e712feec 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -32,6 +32,9 @@
 #include <asm/byteorder.h>
 
 #include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
 
 #define ADS1262_OPCODE_NOP			0x00
 #define ADS1262_OPCODE_RESET			0x06
@@ -190,6 +193,7 @@
 #define ADS1262_MON_CHANNEL_COUNT		4
 #define ADS1262_EXT_REF_COUNT			3
 #define ADS1262_REGMAP_WRITE_SZ			8
+#define ADS1262_SPI_XFER_SZ			13
 
 #define ADS1262_ADC1_RESOLUTION			32
 
@@ -216,6 +220,7 @@ struct ads1262_channel {
 struct ads1262 {
 	struct spi_device *spi;
 	struct regmap *regmap;
+	struct iio_trigger *trig;
 	struct gpio_desc *start_gpiod;
 	size_t num_channels;
 	struct ads1262_channel *channels __counted_by_ptr(num_channels);
@@ -224,6 +229,8 @@ struct ads1262 {
 	/* protects channel state */
 	struct mutex chan_lock;
 	struct completion drdy;
+	struct spi_message msg;
+	struct spi_transfer xfer;
 	unsigned long clk_rate_hz;
 	bool bipolar_supply;
 	int avdd_uV;
@@ -233,6 +240,11 @@ struct ads1262 {
 	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
 	int refp_uV[ADS1262_EXT_REF_COUNT];
 	int refn_uV[ADS1262_EXT_REF_COUNT];
+	IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
+				   ADS1262_FW_CHANNEL_COUNT +
+				   ADS1262_MON_CHANNEL_COUNT);
+	u8 tx[ADS1262_SPI_XFER_SZ] __aligned(IIO_DMA_MINALIGN);
+	u8 rx[ADS1262_SPI_XFER_SZ];
 };
 
 static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
@@ -833,10 +845,278 @@ static const struct iio_info ads1262_iio_info = {
 	.fwnode_xlate = ads1262_fwnode_xlate,
 };
 
+static int ads1262_buffer_postenable_mult(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+
+	if (indio_dev->trig == st->trig) {
+		dev_err_once(dev, "the DRDY trigger doesn't support multiple channel sampling\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * When multiple channels are selected, we use a single transfer to both
+	 * enable channels, start and then read conversions with a full-duplex
+	 * optimized method. The transfer buffer holds up to four contiguous
+	 * commands: two register write commands, and start and stop commands if
+	 * no START GPIO is provided.
+	 *
+	 * The buffer is arranged as follows:
+	 *
+	 *     byte 0-1: write protocol header
+	 *     byte 2-5: MODE0, MODE1, MODE2, INPMUX register data
+	 *     byte 6-7: write protocol header
+	 *     byte 8-10: IDACMUX, IDACMAG, REFMUX register data
+	 *     byte 11: START1 command
+	 *     byte 12: STOP1 command
+	 */
+	if (st->start_gpiod)
+		st->xfer.len = 11;
+	else
+		st->xfer.len = 13;
+
+	static_assert(13 <= ADS1262_SPI_XFER_SZ);
+
+	return spi_optimize_message(st->spi, &st->msg);
+}
+
+static int ads1262_buffer_postenable_one(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	unsigned long i;
+	int ret;
+
+	i = find_first_bit(indio_dev->active_scan_mask,
+			   iio_get_masklength(indio_dev));
+	ret = ads1262_channel_enable(st, &indio_dev->channels[i]);
+	if (ret)
+		return ret;
+
+	ret = ads1262_set_runmode(st, ADS1262_RUNMODE_CONTINUOUS);
+	if (ret)
+		return ret;
+
+	static_assert(5 <= ADS1262_SPI_XFER_SZ);
+
+	st->xfer.len = 5;
+	memset(st->tx, 0, st->xfer.len);
+	/*
+	 * When only one channel is selected, we can't really avoid concurrent
+	 * device activity from happening between the DRDY signal and data
+	 * retrieval, thus we read by command. The transfer buffer holds the
+	 * command (RDATA1) plus the 4 conversion bytes (5 bytes total).
+	 */
+	st->tx[0] = ADS1262_OPCODE_RDATA1;
+
+	ret = spi_optimize_message(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	ret = ads1262_dev_start(st);
+	if (ret) {
+		spi_unoptimize_message(&st->msg);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ads1262_buffer_postenable(struct iio_dev *indio_dev)
+{
+	int ret;
+
+	if (iio_validate_scan_mask_onehot(indio_dev,
+					  indio_dev->active_scan_mask))
+		ret = ads1262_buffer_postenable_one(indio_dev);
+	else
+		ret = ads1262_buffer_postenable_mult(indio_dev);
+
+	return ret;
+}
+
+static int ads1262_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	if (iio_validate_scan_mask_onehot(indio_dev,
+					  indio_dev->active_scan_mask)) {
+		ads1262_dev_stop(st);
+	} else {
+		regcache_drop_region(st->regmap, ADS1262_MODE0_REG,
+				     ADS1262_INPMUX_REG);
+		regcache_drop_region(st->regmap, ADS1262_IDACMUX_REG,
+				     ADS1262_REFMUX_REG);
+	}
+
+	spi_unoptimize_message(&st->msg);
+
+	return 0;
+}
+
+static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+	.postenable = ads1262_buffer_postenable,
+	.predisable = ads1262_buffer_predisable,
+};
+
+static void ads1262_channel_prep_tx(struct ads1262 *st,
+				    const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u8 runmode;
+
+	guard(mutex)(&st->chan_lock);
+
+	/*
+	 * Input chopping and IDAC rotation modes require the continuous
+	 * conversion mode.
+	 *
+	 * This condition only matters when we have an START GPIO, in which case
+	 * the pulse mode is preferred for its predictability: one conversion
+	 * per rising edge. Briefly pulsing the START GPIO (4 uS) should have
+	 * the same effect almost every time, unless the pulse lasts more than
+	 * ~208 uS, which should be rare even if the task is preempted.
+	 *
+	 * If we rely solely on conversion control commands, both modes are
+	 * equivalent because START1 and STOP1 commands are send contiguously on
+	 * the same transfer.
+	 */
+	if (chan->input_chop || chan->idac_chop)
+		runmode = ADS1262_RUNMODE_CONTINUOUS;
+	else
+		runmode = ADS1262_RUNMODE_PULSE;
+
+	st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
+	st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
+	st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+		    FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
+		    FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode) |
+		    FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+	st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, chan->filter);
+	st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+		    FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
+	st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
+		    FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
+
+	st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
+	st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
+	st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+		    FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
+	st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+		    FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
+	st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
+		     FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
+
+	/*
+	 * If we have an START GPIO, the transfer length is 11 so these last two
+	 * bytes are ignored.
+	 */
+	st->tx[11] = ADS1262_OPCODE_START1;
+	st->tx[12] = ADS1262_OPCODE_STOP1;
+}
+
+static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	unsigned int chan;
+	int i = -1;
+	int ret;
+
+	/*
+	 * This routine enables and reads channels in a full-duplex fashion.
+	 *
+	 * When a channel is enabled, the previous conversion is clocked out of
+	 * the shift data register on the same transfer (Section 9.4.7.1). This
+	 * allows for low latency software sequencing but forbids any
+	 * communication with the chip in-between or data corruption may occur,
+	 * hence the need to take the xfer_lock for the whole operation.
+	 */
+	guard(mutex)(&st->xfer_lock);
+
+	iio_for_each_active_channel(indio_dev, chan) {
+		ads1262_channel_prep_tx(st, &indio_dev->channels[chan]);
+
+		reinit_completion(&st->drdy);
+
+		ret = spi_sync(st->spi, &st->msg);
+		if (ret)
+			return ret;
+
+		if (st->start_gpiod) {
+			gpiod_set_value_cansleep(st->start_gpiod, 1);
+			fsleep(4);
+			gpiod_set_value_cansleep(st->start_gpiod, 0);
+		}
+
+		if (i > -1)
+			memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+		i++;
+
+		ret = ads1262_wait_for_conversion(st);
+		if (ret)
+			return ret;
+	}
+
+	memset(st->tx, 0, st->xfer.len);
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+
+	return 0;
+}
+
+static int ads1262_fill_buffer_one(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->xfer_lock);
+
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	/* In command mode the conversion data is found at offset 1 */
+	memcpy(st->scan_buffer, &st->rx[1], sizeof(*st->scan_buffer));
+
+	return 0;
+}
+
+static irqreturn_t ads1262_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ads1262 *st = iio_priv(indio_dev);
+	s64 ts = pf->timestamp;
+	unsigned int weight;
+	int ret;
+
+	weight = bitmap_weight(indio_dev->active_scan_mask,
+			       iio_get_masklength(indio_dev));
+
+	if (weight == 1)
+		ret = ads1262_fill_buffer_one(indio_dev);
+	else
+		ret = ads1262_fill_buffer_mult(indio_dev);
+	if (ret)
+		goto out_notify_done;
+
+	iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
+				    sizeof(st->scan_buffer), ts);
+
+out_notify_done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
 {
 	struct ads1262 *st = dev_id;
 
+	iio_trigger_poll(st->trig);
 	complete(&st->drdy);
 
 	return IRQ_HANDLED;
@@ -1585,6 +1865,9 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	st = iio_priv(indio_dev);
 	st->spi = spi;
 	init_completion(&st->drdy);
+	st->xfer.tx_buf = st->tx;
+	st->xfer.rx_buf = st->rx;
+	spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
 
 	ret = devm_mutex_init(dev, &st->chan_lock);
 	if (ret)
@@ -1628,6 +1911,22 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      ads1262_trigger_handler,
+					      &ads1262_buffer_ops);
+	if (ret)
+		return ret;
+
+	st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", indio_dev->name,
+					  iio_device_id(indio_dev));
+	if (!st->trig)
+		return -ENOMEM;
+	iio_trigger_set_drvdata(st->trig, st);
+	ret = devm_iio_trigger_register(dev, st->trig);
+	if (ret)
+		return ret;
+
 	/*
 	 * REVISIT: This chip has software polling capabilities, which could be
 	 * used to stop depending on the DRDY signal.

-- 
2.55.0


  parent reply	other threads:[~2026-10-10  6:53 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-10  6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-10-10  6:52 ` [PATCH v5 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
2026-10-10  6:52 ` [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-10-10  7:02   ` sashiko-bot
2026-10-12 21:02   ` Conor Dooley
2026-10-10  6:52 ` [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-10-10  7:05   ` sashiko-bot
2026-10-10  6:52 ` [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-10-10  7:07   ` sashiko-bot
2026-10-10  6:52 ` [PATCH v5 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
2026-10-10  6:52 ` [PATCH v5 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
2026-10-10  6:52 ` [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-10-10  7:09   ` sashiko-bot
2026-10-10  6:52 ` Kurt Borja [this message]
2026-10-10  7:09   ` [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling sashiko-bot
2026-10-10  6:52 ` [PATCH v5 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-10-10  6:52 ` [PATCH v5 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261010-ads126x-v5-8-49e331cb2fed@gmail.com \
    --to=kuurtb@gmail.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.