public inbox for linux-gpio@vger.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org>
Cc: radu.sabau@analog.com, "Lars-Peter Clausen" <lars@metafoo.de>,
	"Michael Hennerich" <Michael.Hennerich@analog.com>,
	"David Lechner" <dlechner@baylibre.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	"Uwe Kleine-König" <ukleinek@kernel.org>,
	"Liam Girdwood" <lgirdwood@gmail.com>,
	"Mark Brown" <broonie@kernel.org>,
	"Linus Walleij" <linusw@kernel.org>,
	"Bartosz Golaszewski" <brgl@kernel.org>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
	linux-gpio@vger.kernel.org
Subject: Re: [PATCH 3/4] iio: adc: ad4691: add triggered buffer support
Date: Thu, 5 Mar 2026 19:21:13 +0000	[thread overview]
Message-ID: <20260305192113.5ea46240@jic23-huawei> (raw)
In-Reply-To: <20260305-ad4692-multichannel-sar-adc-driver-v1-3-336229a8dcc7@analog.com>

On Thu, 05 Mar 2026 14:23:29 +0200
Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org> wrote:

> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add buffered capture support using the IIO triggered buffer framework.
> 
> For CNV Clock, CNV Burst, Autonomous and SPI Burst modes the GP0 pin
> is configured as DATA_READY output and an interrupt is registered on
> its falling edge. Each interrupt fires the trigger, which reads
> accumulated results from the internal accumulator registers via regmap.
> 
> For Manual Mode (pipelined SPI protocol) there is no DATA_READY
> signal, so an hrtimer-based IIO trigger is used instead. The timer
> period is derived from the requested sampling frequency and validated
> against the minimum SPI transfer time for the active channel count.

This needs an explanation of why you can't just use a normal hrtimer trigger.
Those always run the risk of being set too fast, but we normally don't care
about that corner case. We don't want to have the equivalent code in every
driver.

> The trigger handler walks the active scan mask issuing one 3-byte SPI
> transfer per channel (selecting the next channel while reading the
> previous result) and pushes samples to the IIO buffer.
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>

A few other comments inline.

> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index dee8bc312d44..ab48f336e46c 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c

>  
> @@ -287,6 +294,8 @@ struct ad4691_state {
>  
>  	struct regulator_bulk_data	regulators[AD4691_NUM_REGULATORS];
>  
> +	struct iio_trigger		*trig;
> +
>  	enum ad4691_adc_mode		adc_mode;
>  
>  	int				vref;
> @@ -297,6 +306,8 @@ struct ad4691_state {
>  	 */
>  	struct mutex			lock;
>  
> +	/* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
> +	struct hrtimer			sampling_timer;
>  	ktime_t				sampling_period;
>  
>  	/* DMA (thus cache coherency maintenance) may require the
> @@ -306,6 +317,11 @@ struct ad4691_state {
>  	 */
>  	unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)]	__aligned(IIO_DMA_MINALIGN);
>  	unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)]	__aligned(IIO_DMA_MINALIGN);
> +	/* Scan buffer for triggered buffer push (one sample + timestamp) */
> +	struct {
> +		u32 val;
> +		s64 ts __aligned(8);
> +	} scan __aligned(IIO_DMA_MINALIGN); 

Same question as earlier on whether we need more padding from aligning yet again.
Maybe we do for this one, I haven't checked.


>  };


> +
> +static irqreturn_t ad4691_irq(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	/*
> +	 * DATA_READY has asserted: stop conversions before reading so the
> +	 * accumulator does not continue sampling while the trigger handler
> +	 * processes the data. Then fire the IIO trigger to push the sample
> +	 * to the buffer.
> +	 *
> +	 * In direct (read_raw) mode the buffer is not enabled; read_raw uses
> +	 * a timed delay and stops conversions itself, so skip the trigger poll.
> +	 */
> +	ad4691_sampling_enable(st, false);
> +
> +	if (iio_buffer_enabled(indio_dev))

How did we get here otherwise?

> +		iio_trigger_poll(st->trig);
> +
> +	return IRQ_HANDLED;
> +}

> +
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> +	struct iio_poll_func *pf = p;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int val;
> +	int ret, i;
> +
> +	mutex_lock(&st->lock);
> +
> +	if (st->adc_mode == AD4691_MANUAL_MODE) {
> +		unsigned int prev_val;
> +		int prev_chan = -1;
> +
> +		/*
> +		 * MANUAL_MODE with CNV tied to CS: each transfer triggers a
> +		 * conversion AND returns the previous conversion's result.
> +		 * First transfer returns garbage, so we do N+1 transfers for
> +		 * N channels.
> +		 */
> +		iio_for_each_active_channel(indio_dev, i) {
> +			ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
> +			if (ret)
> +				goto done;
> +
> +			/* Push previous channel's data (skip first - garbage) */
> +			if (prev_chan >= 0) {
> +				st->scan.val = prev_val;
> +				iio_push_to_buffers_with_ts(indio_dev,
> +					&st->scan, sizeof(st->scan),
> +					iio_get_time_ns(indio_dev));

We don't push one channel at a time... You need to build up a scan locally
and push it in one go.

> +			}
> +			prev_val = val;
> +			prev_chan = i;
> +		}
> +
> +		/* Final NOOP transfer to get last channel's data */
> +		ret = ad4691_transfer(st, AD4691_NOOP, &val);
> +		if (ret)
> +			goto done;
> +
> +		st->scan.val = val;
> +		iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> +					    iio_get_time_ns(indio_dev));
> +		goto done;
> +	}
> +
> +	for (i = 0; i < st->chip->num_channels; i++) {
> +		if (BIT(i) & *indio_dev->active_scan_mask) {
> +			ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
> +			if (ret)
> +				goto done;
> +
> +			st->scan.val = val;
> +			iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> +						    iio_get_time_ns(indio_dev));
As above.

> +		}
> +	}
> +
> +	regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> +	/* START next conversion. */
> +	switch (st->adc_mode) {
> +	case AD4691_CNV_CLOCK_MODE:
> +	case AD4691_CNV_BURST_MODE:
> +	case AD4691_AUTONOMOUS_MODE:
> +	case AD4691_SPI_BURST_MODE:
> +		ad4691_sampling_enable(st, true);
> +		break;
> +	case AD4691_MANUAL_MODE:
> +	default:
> +		break;
> +	}
> +
> +	iio_trigger_notify_done(indio_dev->trig);
> +	mutex_unlock(&st->lock);

Why hold the lock over the notify done?  Having moved it up you can share the done label
code block and this one.

> +	return IRQ_HANDLED;
> +done:
> +	mutex_unlock(&st->lock);
> +	iio_trigger_notify_done(indio_dev->trig);
> +	return IRQ_HANDLED;
> +}
> +
>  static const struct iio_info ad4691_info = {
>  	.read_raw = &ad4691_read_raw,
>  	.read_avail = &ad4691_read_avail,
> @@ -1121,6 +1364,75 @@ static void ad4691_setup_channels(struct iio_dev *indio_dev,
>  	indio_dev->num_channels = st->chip->num_channels;
>  }
>  
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> +					 struct ad4691_state *st)
> +{
> +	struct device *dev = &st->spi->dev;
> +	int irq, ret;
> +
> +	st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> +					  indio_dev->name,
> +					  iio_device_id(indio_dev));
> +	if (!st->trig)
> +		return dev_err_probe(dev, -ENOMEM,
> +				     "Failed to allocate IIO trigger\n");
> +
> +	st->trig->ops = &ad4691_trigger_ops;
> +	iio_trigger_set_drvdata(st->trig, st);
> +
> +	ret = devm_iio_trigger_register(dev, st->trig);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> +
> +	indio_dev->trig = iio_trigger_get(st->trig);
> +
> +	switch (st->adc_mode) {
> +	case AD4691_CNV_CLOCK_MODE:
> +	case AD4691_CNV_BURST_MODE:
> +	case AD4691_AUTONOMOUS_MODE:
> +	case AD4691_SPI_BURST_MODE:
> +		/*
> +		 * DATA_READY asserts at end-of-conversion (or when the
> +		 * accumulator fills in AUTONOMOUS_MODE). The IRQ handler stops
> +		 * conversions and fires the IIO trigger so the trigger handler
> +		 * can read and push the sample to the buffer.
> +		 */
> +		irq = fwnode_irq_get_byname(dev_fwnode(dev), "DRDY");
> +		if (irq <= 0)
> +			return dev_err_probe(dev, irq ? irq : -ENOENT,
> +					     "failed to get DRDY interrupt\n");
> +
> +		ret = devm_request_threaded_irq(dev, irq, NULL,
> +						&ad4691_irq,
> +						IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
Interrupt direction is a question for firmware. A driver should
not be setting it. We have some historical bugs in this area that we can't
fix because board maybe relying on the driver overriding but we don't want to
introduce more of them!
> +						indio_dev->name, indio_dev);


  reply	other threads:[~2026-03-05 19:21 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-05 12:23 [PATCH 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-05 12:23 ` [PATCH 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-05 12:44   ` Krzysztof Kozlowski
2026-03-05 17:45   ` Jonathan Cameron
2026-03-06 11:55     ` Sabau, Radu bogdan
2026-03-07 18:48       ` David Lechner
2026-03-08 18:28         ` Jonathan Cameron
2026-03-09  8:57           ` Sabau, Radu bogdan
2026-03-09 14:34             ` David Lechner
2026-03-05 12:23 ` [PATCH 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
2026-03-05 19:12   ` Jonathan Cameron
2026-03-06 17:30   ` Markus Elfring
2026-03-05 12:23 ` [PATCH 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-03-05 19:21   ` Jonathan Cameron [this message]
2026-03-05 12:23 ` [PATCH 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
2026-03-05 19:26   ` Jonathan Cameron
2026-03-06 12:05 ` [PATCH 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Andy Shevchenko
2026-03-06 12:39   ` Sabau, Radu bogdan
2026-03-06 14:33     ` Andy Shevchenko

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=20260305192113.5ea46240@jic23-huawei \
    --to=jic23@kernel.org \
    --cc=Michael.Hennerich@analog.com \
    --cc=andy@kernel.org \
    --cc=brgl@kernel.org \
    --cc=broonie@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=devnull+radu.sabau.analog.com@kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=krzk+dt@kernel.org \
    --cc=lars@metafoo.de \
    --cc=lgirdwood@gmail.com \
    --cc=linusw@kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=radu.sabau@analog.com \
    --cc=robh@kernel.org \
    --cc=ukleinek@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox