Linux Documentation
 help / color / mirror / Atom feed
* [PATCH 0/5] docs: pt_BR: Complete PGP maintainer guide translation
From: Daniel Pereira @ 2026-03-27 12:55 UTC (permalink / raw)
  To: Jonathan Corbet; +Cc: linux-doc, Daniel Pereira

This series provides the complete Brazilian Portuguese (pt_BR) translation 
of the Maintainer PGP guide. The translation was performed in stages to 
ensure technical consistency and proper review of cryptographic terminology.

In addition to the translation, the first patch includes a minor update 
to the maintainer-handbooks index to properly include the KVM x86 
subsystem development process notes.

Key highlights of this series:
- Complete translation of the GnuPG setup, smartcard usage, and 
  Git/Patatt integration.
- Sphinx labels were updated with the '_pt' suffix to ensure unique 
  namespaces and avoid warnings during the HTML documentation build.
- Adherence to the 80-column line limit for better readability in 
  plain text.
- Verified with checkpatch.pl (0 errors, 0 warnings).
- Verified with 'make htmldocs' to ensure a clean build.

Daniel Pereira (5):
  docs: add maintainer-kvm-x86 to maintainer-handbooks index
  docs: pt_BR: start translation of the PGP maintainer guide
  docs: pt_BR: continue PGP guide translation
  docs: pt_BR: continue PGP guide: Git and maintenance
  docs: pt_BR: complete PGP guide translation

 Documentation/translations/pt_BR/index.rst    |   1 +
 .../pt_BR/process/maintainer-handbooks.rst    |   1 +
 .../pt_BR/process/maintainer-pgp-guide.rst    | 913 ++++++++++++++++++
 3 files changed, 915 insertions(+)
 create mode 100644 Documentation/translations/pt_BR/process/maintainer-pgp-guide.rst

-- 
2.47.3


^ permalink raw reply

* Re: [PATCH 0/4] hwmon: Add WITRN USB tester driver
From: Rong Zhang @ 2026-03-27 12:01 UTC (permalink / raw)
  To: Guenter Roeck, Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, linux-kernel, linux-doc
In-Reply-To: <a3a8eb58-b2fa-411b-81be-eb70dac86b4f@roeck-us.net>

Hi Guenter,

Thanks a lot for your review and applying patch 1 :-)

On Thu, 2026-03-26 at 17:05 -0700, Guenter Roeck wrote:
> On 3/26/26 12:19, Rong Zhang wrote:
> > WITRN produces a series of devices to monitor power characteristics of
> > USB connections and display those on a on-device display. Most of them
> > contain an additional port which exposes the measurements via USB HID.
> > 
> > These devices report sensor values in IEEE-754 float (binary32) format.
> > The driver must perform floating-point number to integer conversions to
> > provide hwmon channels. Meanwhile, they also report accumulative float
> > values, and simple division or multiplication turns them into useful
> > hwmon channels.
> > 
> > Patch 1 adds label support for 64-bit energy attributes, as the driver
> > needs it.
> > 
> > Patch 2 adds a helper module for floating-point to integer conversions,
> > so that the conversion, multification and division methods can be used
> > in this driver as well as other drivers (I am also working on another
> > USB tester driver that needs it).
> > 
> > Patch 3 adds a barebone HID driver for WITRN K2.
> > 
> > Patch 4 adds hwmon channels and attributes to the driver.
> > 
> > Signed-off-by: Rong Zhang <i@rong.moe>
> > ---
> > Rong Zhang (4):
> >        hwmon: Add label support for 64-bit energy attributes
> >        hwmon: New helper module for floating-point to integer conversions
> 
> Nack. This is not a hwmon problem and should reside in a driver or in lib/
> (if it is needed by multiple drivers).

Makes sense. I will try.

> 
> >        hwmon: Add barebone HID driver for WITRN
> 
> Nack. This is the wrong place for such a driver. It should reside somewhere
> in drivers/usb, or maybe in drivers/misc/.

Hmm, I decided to place it here because:

- It's not a hid_ll_driver but a dumb hid_driver, i.e., does no low
level things but just receives hid event from the HID core. It doesn't
even send any HID report to the device.

- There has been numerous hid_driver in drivers/hwmon/.

- There has been a similar USB tester driver in drivers/hwmon/, i.e.,
powerz. That's the major reason of my decision.

Could you kindly explain what kinds of driver can be accepted into
drivers/hwmon/?

> 
> >        hwmon: (witrn) Add monitoring support
> 
> This should be implemented as auxiliary driver.

Could you kindly elaborate? Did you mean that if the device supports
multiple functionalities they should be implemented as multiple
auxiliary drivers in different subsystems?

FYI, the USB tester doesn't provide any other meaningful feature that
fits other subsystems. The tester only provides two features through USB
HID: power measurements (this series), and raw PD packets sniffing.

As for the latter, the USB tester can sniff raw PD packets between the
source and sink if enabled in the device menu. It doesn't provide the
parsed result for packets, and the PC cannot ask the tester to send PD
packets. This doesn't fit UCSI at all, as a UCSI device operates at a
higher level and must accept commands. AFAIK such a dumb sniffer won't
fit any subsystem in the kernel. Hence, the only thing fits a subsystem
is its power measurements.

All measurements supported by the official utility for Windows can be
found in `struct witrn_sensor'. Other than that, all extra features
provided by the utility are implemented in software and I didn't see any
extra USB packets other than querying the serial number [1] when I was
randomly messing around with the utility [2].

I separated patch 3 and 4 just for easier review. If you are not in
favor of such a style, I will squash them.

[1]: In fact, the utility directly asks the device to return the content
on several specific ROM/RAM addresses, and then the utility calculates
the serial number with an unknown algorithm. Reading a ROM/RAM address
seems to be the only command that the device accepts from the USB host.

[2]: Yeah, their utility does not support PD packet capturing or
parsing. It seems that the manufacturer provides the PD sniffing feature
as is and expects users to capture it via hidraw or libusb and parse it
themselves.

> 
> Sashiko has a lot of feedback that you might want to address before
> resubmitting.
> 
> https://sashiko.dev/#/patchset/20260327-b4-hwmon-witrn-v1-0-8d2f1896c045%40rong.moe

Sashiko's feedback helps a lot. Thanks.

Thanks,
Rong

> 
> Thanks,
> Guenter
> 
> > 
> >   Documentation/hwmon/index.rst |   1 +
> >   Documentation/hwmon/witrn.rst |  53 ++++
> >   MAINTAINERS                   |   7 +
> >   drivers/hwmon/Kconfig         |  14 +
> >   drivers/hwmon/Makefile        |   2 +
> >   drivers/hwmon/hwmon-fp.c      | 262 ++++++++++++++++
> >   drivers/hwmon/hwmon-fp.h      | 212 +++++++++++++
> >   drivers/hwmon/hwmon.c         |   1 +
> >   drivers/hwmon/witrn.c         | 691 ++++++++++++++++++++++++++++++++++++++++++
> >   9 files changed, 1243 insertions(+)
> > ---
> > base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
> > change-id: 20260327-b4-hwmon-witrn-a629b9040250
> > 
> > Thanks,
> > Rong
> > 

^ permalink raw reply

* [PATCH] docs: fix grammar in contribution maturity model
From: Ariful Islam Shoikot @ 2026-03-27 11:49 UTC (permalink / raw)
  To: linux-doc; +Cc: Ariful Islam Shoikot

Replace the incorrect phrase "such those" with "such as those" to
improve clarity in the documentation.

Signed-off-by: Ariful Islam Shoikot <islamarifulshoikat@gmail.com>
---
 Documentation/process/contribution-maturity-model.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/process/contribution-maturity-model.rst b/Documentation/process/contribution-maturity-model.rst
index b87ab34de22c..798d1d9dd8b8 100644
--- a/Documentation/process/contribution-maturity-model.rst
+++ b/Documentation/process/contribution-maturity-model.rst
@@ -65,7 +65,7 @@ Level 3
   authored by engineers from other companies) as part of their job
   responsibilities
 * Contributing presentations or papers to Linux-related or academic
-  conferences (such those organized by the Linux Foundation, Usenix,
+  conferences (such as those organized by the Linux Foundation, Usenix,
   ACM, etc.), are considered part of an engineer’s work.
 * A Software Engineer’s community contributions will be considered in
   promotion and performance reviews.
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v5 00/15] hwspinlock: move device alloc into core and refactor includes
From: Wolfram Sang @ 2026-03-27 11:43 UTC (permalink / raw)
  To: linux-renesas-soc
  Cc: linux-kernel, Alexandre Torgue, Andy Shevchenko, Antonio Borneo,
	Arnd Bergmann, Baolin Wang, Bjorn Andersson, Boqun Feng,
	Chen-Yu Tsai, Chunyan Zhang, Danilo Krummrich, David Lechner,
	driver-core, Greg Kroah-Hartman, Ingo Molnar, Jernej Skrabec,
	Jonathan Cameron, Jonathan Corbet, Konrad Dybcio, Lee Jones,
	Linus Walleij, linux-arm-kernel, linux-arm-msm, linux-doc,
	linux-gpio, linux-iio, linux-omap, linux-remoteproc, linux-spi,
	linux-stm32, linux-sunxi, Mark Brown, Maxime Coquelin,
	Nuno Sá, Orson Zhai, Peter Zijlstra, Rafael J. Wysocki,
	Samuel Holland, Shuah Khan, Srinivas Kandagatla, Thomas Gleixner,
	Waiman Long, Wilken Gottwalt, Will Deacon
In-Reply-To: <20260319105947.6237-1-wsa+renesas@sang-engineering.com>

On Thu, Mar 19, 2026 at 11:59:22AM +0100, Wolfram Sang wrote:
> Changes since v4:
> 
> * update Documentation, too, when ABI gets changed (Thanks Antonio!)
> * rebased to 7.0-rc4
> * added more tags (Thanks!)
> 
> My ultimate goal is to allow hwspinlock provider drivers outside of the
> subsystem directory. It turned out that a simple split of the headers
> files into a public provider and a public consumer header file is not
> enough because core internal structures need to stay hidden. Even more,
> their opaqueness could and should even be increased. That would also
> allow the core to handle the de-/allocation of the hwspinlock device
> itself.
> 
> This series does all that. Patches 1-2 remove the meanwhile unused
> platform_data to ease further refactoring. Patches 3-9 abstract access
> to internal structures away using helpers. Patch 10 then moves
> hwspinlock device handling to the core, simplifying drivers. The
> remaining patches refactor the headers until the internal one is gone
> and the public ones are divided into provider and consumer parts. More
> details are given in the patch descriptions.
> 
> One note about using a callback to initialize hwspinlock priv: I also
> experimented with a dedicated 'set_priv' helper function. It felt a bit
> clumsy to me. Drivers would need to save the 'bank' pointer again and
> iterate over it. Because most drivers will only have a simple callback
> anyhow, it looked leaner to me.
> 
> This series has been tested on a Renesas SparrowHawk board (R-Car V4H)
> with a yet-to-be-upstreamed hwspinlock driver for the MFIS IP core. A
> branch can be found here (without the MFIS driver currently):
> 
> git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git renesas/hwspinlock/refactor-alloc-buildtest
> 
> Build bots reported success.

Sashiko found some valid issues[1], so I am already working on a v6.

[1] https://sashiko.dev/#/patchset/20260319105947.6237-1-wsa%2Brenesas%40sang-engineering.com


^ permalink raw reply

* Re: [PATCH v5 2/4] iio: adc: ad4691: add initial driver for AD4691 family
From: Andy Shevchenko @ 2026-03-27 11:36 UTC (permalink / raw)
  To: radu.sabau
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan, linux-iio, devicetree,
	linux-kernel, linux-pwm, linux-gpio, linux-doc
In-Reply-To: <20260327-ad4692-multichannel-sar-adc-driver-v5-2-11f789de47b8@analog.com>

On Fri, Mar 27, 2026 at 01:07:58PM +0200, Radu Sabau via B4 Relay wrote:

> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
> 
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
> 
> The chip idles in Autonomous Mode so that single-shot read_raw can use
> the internal oscillator without disturbing the hardware configuration.
> 
> Three voltage supply domains are managed: avdd (required), vio, and a
> reference supply on either the REF pin (ref-supply, external buffer)
> or the REFIN pin (refin-supply, uses the on-chip reference buffer;
> REFBUF_EN is set accordingly). Hardware reset is performed via
> the reset controller framework; a software reset through SPI_CONFIG_A
> is used as fallback when no hardware reset is available.
> 
> Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
> an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
> 16-bit transfer.

...

+ array_size.h

> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>

Hmm... Is it used? Or perhaps you need only
dev_printk.h
device/devres.h
?

> +#include <linux/err.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/spi/spi.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>

...

> +/*
> + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
> + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
> + * up to 500 kHz and use index 1 as their highest valid rate.
> + */
> +static const int ad4691_osc_freqs[] = {
> +	1000000,	/* 0x0: 1 MHz */
> +	500000,		/* 0x1: 500 kHz */
> +	400000,		/* 0x2: 400 kHz */
> +	250000,		/* 0x3: 250 kHz */
> +	200000,		/* 0x4: 200 kHz */
> +	167000,		/* 0x5: 167 kHz */
> +	133000,		/* 0x6: 133 kHz */
> +	125000,		/* 0x7: 125 kHz */
> +	100000,		/* 0x8: 100 kHz */
> +	50000,		/* 0x9: 50 kHz */
> +	25000,		/* 0xA: 25 kHz */
> +	12500,		/* 0xB: 12.5 kHz */
> +	10000,		/* 0xC: 10 kHz */
> +	5000,		/* 0xD: 5 kHz */
> +	2500,		/* 0xE: 2.5 kHz */
> +	1250,		/* 0xF: 1.25 kHz */

Instead of comments, make the code self-commented and robust:

/* ...the top comment... */
static const int ad4691_osc_freqs_Hz[] = {
	...
	[0xD] = 5000,
	[0xE] = 2500,
	[0xF] = 1250,
};

I would even use unit multipliers in some cases, but it might make the whole
table inconsistent, dunno.

> +};

From this it will be visible that the table is in Hz and each value is properly
indexed, even shuffling won't break the code.

...

> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
> +	unsigned int i;
> +
> +	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +	if (IIO_DEV_ACQUIRE_FAILED(claim))
> +		return -EBUSY;

> +	for (i = start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) {

	for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) {

> +		if (ad4691_osc_freqs[i] != freq)
> +			continue;
> +		return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> +					  AD4691_OSC_FREQ_MASK, i);
> +	}
> +
> +	return -EINVAL;
> +}

...

> +static int ad4691_read_avail(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     const int **vals, int *type,
> +			     int *length, long mask)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;

Yeah, in the table it's written as 1000000... But as I mentioned above, using
unit multipliers _there_ maybe not a good idea.

> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*vals = &ad4691_osc_freqs[start];
> +		*type = IIO_VAL_INT;
> +		*length = ARRAY_SIZE(ad4691_osc_freqs) - start;
> +		return IIO_AVAIL_LIST;
> +	default:
> +		return -EINVAL;
> +	}
> +}

...

> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> +				   struct iio_chan_spec const *chan, int *val)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int reg_val;
> +	int ret;
> +
> +	guard(mutex)(&st->lock);

> +	/*
> +	 * Use AUTONOMOUS mode for single-shot reads.
> +	 */

One line?

> +	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> +			   AD4691_STATE_RESET_ALL);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> +			   BIT(chan->channel));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> +			   (u16)~BIT(chan->channel));

Why do you need casting?

> +	if (ret)
> +		return ret;

> +	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Wait for at least 2 internal oscillator periods for the
> +	 * conversion to complete.
> +	 */
> +	fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]));

Way too long line. Use temporary variables for that to make it easier to parse.
Also add a (short) comment on how the OSC periods are being calculated.

> +	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	*val = reg_val;
> +
> +	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +	if (ret)
> +		return ret;
> +
> +	return IIO_VAL_INT;
> +}

...

> +	ret = devm_regulator_get_enable(dev, "avdd");
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to get and enable AVDD\n");
> +
> +	ret = devm_regulator_get_enable(dev, "vio");
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");

Can they be united to a bulk?

...

> +static int ad4691_reset(struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +	struct reset_control *rst;
> +
> +	rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> +	if (IS_ERR(rst))
> +		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
> +
> +	if (rst) {
> +		/*
> +		 * The GPIO is already asserted by reset_gpio_probe (GPIOD_OUT_HIGH).
> +		 * Wait for the reset pulse width required by the chip. See datasheet Table 5.

Too long, try to wrap around 80, and drop unneeded (confusing?) details.

> +		 */

		/*
		 * The GPIO is already asserted by reset_gpio_probe().
		 * Wait for the reset pulse width required by the chip.
		 * See datasheet Table 5.
		 */

> +		fsleep(300);
> +		return reset_control_deassert(rst);
> +	}
> +
> +	/* No hardware reset available, fall back to software reset. */
> +	return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
> +			    AD4691_SW_RESET);
> +}

...

> +	ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
> +				 AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN,
> +				 FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val) |
> +				 (st->refbuf_en ? AD4691_REFBUF_EN : 0));

With temporary variable it will become something like

	/* ...Comment on what is this... */
	val = FIELD_PREP(...);
	FIELD_MODIFY(...);

	ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
				 AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN, val);


> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");

...

> +	/*
> +	 * Set the internal oscillator to the highest rate this chip supports.
> +	 * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
> +	 * chips start at index 1 (500 kHz).
> +	 */
> +	ret = regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> +				 AD4691_OSC_FREQ_MASK,
> +				 (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1);

_assign_bits?

> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: Re: Re: [PATCH v5 2/3] RISC-V: KVM: Detect and expose supported HGATP G-stage modes
From: fangyu.yu @ 2026-03-27 11:11 UTC (permalink / raw)
  To: anup
  Cc: alex, andrew.jones, aou, atish.patra, corbet, fangyu.yu, guoren,
	kvm-riscv, kvm, linux-doc, linux-kernel, linux-riscv, palmer,
	pbonzini, pjw, radim.krcmar
In-Reply-To: <CAAhSdy1-OPSaigBarUGmNWfcDBqjine+Hm+Urdinv1nLooQMNA@mail.gmail.com>

>> >> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>> >>
>> >> Extend kvm_riscv_gstage_mode_detect() to probe all HGATP.MODE values
>> >> supported by the host and record them in a bitmask. Keep tracking the
>> >> maximum supported G-stage page table level for existing internal users.
>> >>
>> >> Also provide lightweight helpers to retrieve the supported-mode bitmask
>> >> and validate a requested HGATP.MODE against it.
>> >>
>> >> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>> >> ---
>> >>  arch/riscv/include/asm/kvm_gstage.h | 11 ++++++++
>> >>  arch/riscv/kvm/gstage.c             | 43 +++++++++++++++--------------
>> >>  2 files changed, 34 insertions(+), 20 deletions(-)
>> >>
>> >> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
>> >> index b12605fbca44..76c37b5dc02d 100644
>> >> --- a/arch/riscv/include/asm/kvm_gstage.h
>> >> +++ b/arch/riscv/include/asm/kvm_gstage.h
>> >> @@ -30,6 +30,7 @@ struct kvm_gstage_mapping {
>> >>  #endif
>> >>
>> >>  extern unsigned long kvm_riscv_gstage_max_pgd_levels;
>> >> +extern u32 kvm_riscv_gstage_mode_mask;
>> >
>> >s/u32/unsigned long/
>> >s/kvm_riscv_gstage_mode_mask/kvm_riscv_gstage_supported_mode_mask/
>> >
>>
>> Ack, will switch the type to unsigned long and rename it to
>> kvm_riscv_gstage_supported_mode_mask in the next revision.
>>
>> >>
>> >>  #define kvm_riscv_gstage_pgd_xbits     2
>> >>  #define kvm_riscv_gstage_pgd_size      (1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits))
>> >> @@ -75,4 +76,14 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
>> >>
>> >>  void kvm_riscv_gstage_mode_detect(void);
>> >>
>> >> +static inline u32 kvm_riscv_get_hgatp_mode_mask(void)
>> >> +{
>> >> +       return kvm_riscv_gstage_mode_mask;
>> >> +}
>> >> +
>> >> +static inline bool kvm_riscv_hgatp_mode_is_valid(unsigned long mode)
>> >> +{
>> >> +       return kvm_riscv_gstage_mode_mask & BIT(mode);
>> >> +}
>> >> +
>> >>  #endif
>> >> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
>> >> index 2d0045f502d1..328d4138f162 100644
>> >> --- a/arch/riscv/kvm/gstage.c
>> >> +++ b/arch/riscv/kvm/gstage.c
>> >> @@ -16,6 +16,8 @@ unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 3;
>> >>  #else
>> >>  unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 2;
>> >>  #endif
>> >> +/* Bitmask of supported HGATP.MODE encodings (BIT(HGATP_MODE_*)). */
>> >> +u32 kvm_riscv_gstage_mode_mask __ro_after_init;
>> >>
>> >>  #define gstage_pte_leaf(__ptep)        \
>> >>         (pte_val(*(__ptep)) & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC))
>> >> @@ -315,42 +317,43 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
>> >>         }
>> >>  }
>> >>
>> >> +static bool __init kvm_riscv_hgatp_mode_supported(unsigned long mode)
>> >> +{
>> >> +       csr_write(CSR_HGATP, mode << HGATP_MODE_SHIFT);
>> >> +       return ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == mode);
>> >> +}
>> >> +
>> >>  void __init kvm_riscv_gstage_mode_detect(void)
>> >>  {
>> >> +       kvm_riscv_gstage_mode_mask = 0;
>> >> +       kvm_riscv_gstage_max_pgd_levels = 0;
>> >> +
>> >>  #ifdef CONFIG_64BIT
>> >> -       /* Try Sv57x4 G-stage mode */
>> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV57X4 << HGATP_MODE_SHIFT);
>> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV57X4) {
>> >> -               kvm_riscv_gstage_max_pgd_levels = 5;
>> >> -               goto done;
>> >> +       /* Try Sv39x4 G-stage mode */
>> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV39X4)) {
>> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV39X4);
>> >> +               kvm_riscv_gstage_max_pgd_levels = 3;
>> >>         }
>> >>
>> >>         /* Try Sv48x4 G-stage mode */
>> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT);
>> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) {
>> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV48X4)) {
>> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV48X4);
>> >>                 kvm_riscv_gstage_max_pgd_levels = 4;
>> >> -               goto done;
>> >>         }
>> >>
>> >> -       /* Try Sv39x4 G-stage mode */
>> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT);
>> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV39X4) {
>> >> -               kvm_riscv_gstage_max_pgd_levels = 3;
>> >> -               goto done;
>> >> +       /* Try Sv57x4 G-stage mode */
>> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV57X4)) {
>> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV57X4);
>> >> +               kvm_riscv_gstage_max_pgd_levels = 5;
>> >>         }
>> >>  #else /* CONFIG_32BIT */
>> >>         /* Try Sv32x4 G-stage mode */
>> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT);
>> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV32X4) {
>> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV32X4)) {
>> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV32X4);
>> >>                 kvm_riscv_gstage_max_pgd_levels = 2;
>> >> -               goto done;
>> >>         }
>> >>  #endif
>> >>
>> >> -       /* KVM depends on !HGATP_MODE_OFF */
>> >> -       kvm_riscv_gstage_max_pgd_levels = 0;
>> >> -
>> >> -done:
>> >
>> >Here are some statements from RISC-V privilege specification:
>> >"Implementations that support Sv48 must also support Sv39."
>> >"Implementations that support Sv57 must also support Sv48."
>> >"The conversion of an Sv32x4, Sv39x4, Sv48x4, or Sv57x4 guest physical
>> >address is accomplished with the
>> >same algorithm used for Sv32, Sv39, Sv48, or Sv57, as presented in
>> >Section 12.3.2, except that:"
>> >"hgatp substitutes for the usual satp;"
>> >
>> >Based on above it is a waste to try each and every mode.
>> >For example: if mode Sv48x4 is supported then Sv39x4 is also supported.
>> >
>>
>> Radmi and I discussed this topic before; please refer to the following link:
>> https://lore.kernel.org/linux-riscv/20260131061238.52708-1-fangyu.yu@linux.alibaba.com/
>
>Privilege spec mandates Sv48 and Sv39 when Sv57 is supported
>so the current approach is not based on any assumption.

Thanks for the pointers from the priv spec. I agree that for selecting a
working G-stage mode (e.g. picking the highest supported mode), it’s
sufficient to probe from Sv57x4 downwards.

Now, I want to build an explicit capability mask of all HGATP.MODE encodings
that the hardware actually accepts, so that if the userspace config forces
a specific mode (e.g. Sv48x4), KVM can validate it directly and reject/
fallback when that exact mode is not supported.

As an alternative, we could also do the probing lazily: i.e. when userspace
requests a specific HGATP mode, we try programming that mode and fail the
request if it is not accepted. 

>Regards,
>Anup

Thanks,
Fangyu

^ permalink raw reply

* [PATCH v5 2/4] iio: adc: ad4691: add initial driver for AD4691 family
From: Radu Sabau via B4 Relay @ 2026-03-27 11:07 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc, Radu Sabau
In-Reply-To: <20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com>

From: Radu Sabau <radu.sabau@analog.com>

Add support for the Analog Devices AD4691 family of high-speed,
low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
AD4694 (8-ch, 1 MSPS).

The driver implements a custom regmap layer over raw SPI to handle the
device's mixed 1/2/3/4-byte register widths and uses the standard IIO
read_raw/write_raw interface for single-channel reads.

The chip idles in Autonomous Mode so that single-shot read_raw can use
the internal oscillator without disturbing the hardware configuration.

Three voltage supply domains are managed: avdd (required), vio, and a
reference supply on either the REF pin (ref-supply, external buffer)
or the REFIN pin (refin-supply, uses the on-chip reference buffer;
REFBUF_EN is set accordingly). Hardware reset is performed via
the reset controller framework; a software reset through SPI_CONFIG_A
is used as fallback when no hardware reset is available.

Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
16-bit transfer.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 MAINTAINERS              |   1 +
 drivers/iio/adc/Kconfig  |  11 +
 drivers/iio/adc/Makefile |   1 +
 drivers/iio/adc/ad4691.c | 690 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 703 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 438ca850fa1c..24e4502b8292 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L:	linux-iio@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F:	drivers/iio/adc/ad4691.c
 
 ANALOG DEVICES INC AD4695 DRIVER
 M:	Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 60038ae8dfc4..3685a03aa8dc 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -139,6 +139,17 @@ config AD4170_4
 	  To compile this driver as a module, choose M here: the module will be
 	  called ad4170-4.
 
+config AD4691
+	tristate "Analog Devices AD4691 Family ADC Driver"
+	depends on SPI
+	select REGMAP
+	help
+	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
+	  SPI analog to digital converters (ADC).
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called ad4691.
+
 config AD4695
 	tristate "Analog Device AD4695 ADC Driver"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index c76550415ff1..4ac1ea09d773 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
 obj-$(CONFIG_AD4130) += ad4130.o
 obj-$(CONFIG_AD4134) += ad4134.o
 obj-$(CONFIG_AD4170_4) += ad4170-4.o
+obj-$(CONFIG_AD4691) += ad4691.o
 obj-$(CONFIG_AD4695) += ad4695.o
 obj-$(CONFIG_AD4851) += ad4851.o
 obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
new file mode 100644
index 000000000000..f930efdb9d8c
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024-2026 Analog Devices, Inc.
+ * Author: Radu Sabau <radu.sabau@analog.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#define AD4691_VREF_uV_MIN			2400000
+#define AD4691_VREF_uV_MAX			5250000
+#define AD4691_VREF_2P5_uV_MAX			2750000
+#define AD4691_VREF_3P0_uV_MAX			3250000
+#define AD4691_VREF_3P3_uV_MAX			3750000
+#define AD4691_VREF_4P096_uV_MAX		4500000
+
+#define AD4691_SPI_CONFIG_A_REG			0x000
+#define AD4691_SW_RESET				(BIT(7) | BIT(0))
+
+#define AD4691_STATUS_REG			0x014
+#define AD4691_CLAMP_STATUS1_REG		0x01A
+#define AD4691_CLAMP_STATUS2_REG		0x01B
+#define AD4691_DEVICE_SETUP			0x020
+#define AD4691_LDO_EN				BIT(4)
+#define AD4691_REF_CTRL				0x021
+#define AD4691_REF_CTRL_MASK			GENMASK(4, 2)
+#define AD4691_REFBUF_EN			BIT(0)
+#define AD4691_OSC_FREQ_REG			0x023
+#define AD4691_OSC_FREQ_MASK			GENMASK(3, 0)
+#define AD4691_STD_SEQ_CONFIG			0x025
+#define AD4691_SPARE_CONTROL			0x02A
+
+#define AD4691_OSC_EN_REG			0x180
+#define AD4691_STATE_RESET_REG			0x181
+#define AD4691_STATE_RESET_ALL			0x01
+#define AD4691_ADC_SETUP			0x182
+#define AD4691_ADC_MODE_MASK			GENMASK(1, 0)
+#define AD4691_AUTONOMOUS_MODE			0x02
+/*
+ * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
+ * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte.
+ */
+#define AD4691_ACC_MASK_REG			0x185
+#define AD4691_ACC_DEPTH_IN(n)			(0x186 + (n))
+#define AD4691_GPIO_MODE1_REG			0x196
+#define AD4691_GPIO_MODE2_REG			0x197
+#define AD4691_GPIO_READ			0x1A0
+#define AD4691_ACC_STATUS_FULL1_REG		0x1B0
+#define AD4691_ACC_STATUS_FULL2_REG		0x1B1
+#define AD4691_ACC_STATUS_OVERRUN1_REG		0x1B2
+#define AD4691_ACC_STATUS_OVERRUN2_REG		0x1B3
+#define AD4691_ACC_STATUS_SAT1_REG		0x1B4
+#define AD4691_ACC_STATUS_SAT2_REG		0x1BE
+#define AD4691_ACC_SAT_OVR_REG(n)		(0x1C0 + (n))
+#define AD4691_AVG_IN(n)			(0x201 + (2 * (n)))
+#define AD4691_AVG_STS_IN(n)			(0x222 + (3 * (n)))
+#define AD4691_ACC_IN(n)			(0x252 + (3 * (n)))
+#define AD4691_ACC_STS_DATA(n)			(0x283 + (4 * (n)))
+
+enum ad4691_ref_ctrl {
+	AD4691_VREF_2P5   = 0,
+	AD4691_VREF_3P0   = 1,
+	AD4691_VREF_3P3   = 2,
+	AD4691_VREF_4P096 = 3,
+	AD4691_VREF_5P0   = 4,
+};
+
+struct ad4691_chip_info {
+	const struct iio_chan_spec *channels;
+	const char *name;
+	unsigned int num_channels;
+	unsigned int max_rate;
+};
+
+#define AD4691_CHANNEL(ch)						\
+	{								\
+		.type = IIO_VOLTAGE,					\
+		.indexed = 1,						\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)		\
+				    | BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+		.info_mask_separate_available =				\
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
+		.channel = ch,						\
+		.scan_index = ch,					\
+		.scan_type = {						\
+			.sign = 'u',					\
+			.realbits = 16,					\
+			.storagebits = 16,				\
+		},							\
+	}
+
+static const struct iio_chan_spec ad4691_channels[] = {
+	AD4691_CHANNEL(0),
+	AD4691_CHANNEL(1),
+	AD4691_CHANNEL(2),
+	AD4691_CHANNEL(3),
+	AD4691_CHANNEL(4),
+	AD4691_CHANNEL(5),
+	AD4691_CHANNEL(6),
+	AD4691_CHANNEL(7),
+	AD4691_CHANNEL(8),
+	AD4691_CHANNEL(9),
+	AD4691_CHANNEL(10),
+	AD4691_CHANNEL(11),
+	AD4691_CHANNEL(12),
+	AD4691_CHANNEL(13),
+	AD4691_CHANNEL(14),
+	AD4691_CHANNEL(15),
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+	AD4691_CHANNEL(0),
+	AD4691_CHANNEL(1),
+	AD4691_CHANNEL(2),
+	AD4691_CHANNEL(3),
+	AD4691_CHANNEL(4),
+	AD4691_CHANNEL(5),
+	AD4691_CHANNEL(6),
+	AD4691_CHANNEL(7),
+};
+
+/*
+ * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
+ * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
+ * up to 500 kHz and use index 1 as their highest valid rate.
+ */
+static const int ad4691_osc_freqs[] = {
+	1000000,	/* 0x0: 1 MHz */
+	500000,		/* 0x1: 500 kHz */
+	400000,		/* 0x2: 400 kHz */
+	250000,		/* 0x3: 250 kHz */
+	200000,		/* 0x4: 200 kHz */
+	167000,		/* 0x5: 167 kHz */
+	133000,		/* 0x6: 133 kHz */
+	125000,		/* 0x7: 125 kHz */
+	100000,		/* 0x8: 100 kHz */
+	50000,		/* 0x9: 50 kHz */
+	25000,		/* 0xA: 25 kHz */
+	12500,		/* 0xB: 12.5 kHz */
+	10000,		/* 0xC: 10 kHz */
+	5000,		/* 0xD: 5 kHz */
+	2500,		/* 0xE: 2.5 kHz */
+	1250,		/* 0xF: 1.25 kHz */
+};
+
+static const struct ad4691_chip_info ad4691_chip_info = {
+	.channels = ad4691_channels,
+	.name = "ad4691",
+	.num_channels = ARRAY_SIZE(ad4691_channels),
+	.max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4692_chip_info = {
+	.channels = ad4691_channels,
+	.name = "ad4692",
+	.num_channels = ARRAY_SIZE(ad4691_channels),
+	.max_rate = 1 * HZ_PER_MHZ,
+};
+
+static const struct ad4691_chip_info ad4693_chip_info = {
+	.channels = ad4693_channels,
+	.name = "ad4693",
+	.num_channels = ARRAY_SIZE(ad4693_channels),
+	.max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4694_chip_info = {
+	.channels = ad4693_channels,
+	.name = "ad4694",
+	.num_channels = ARRAY_SIZE(ad4693_channels),
+	.max_rate = 1 * HZ_PER_MHZ,
+};
+
+struct ad4691_state {
+	const struct ad4691_chip_info *info;
+	struct regmap *regmap;
+	int vref_uV;
+	bool refbuf_en;
+	bool ldo_en;
+	/*
+	 * Synchronize access to members of the driver state, and ensure
+	 * atomicity of consecutive SPI operations.
+	 */
+	struct mutex lock;
+};
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct spi_device *spi = context;
+	u8 tx[2], rx[4];
+	int ret;
+
+	/* Set bit 15 to mark the operation as READ. */
+	put_unaligned_be16(0x8000 | reg, tx);
+
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 1);
+		if (ret)
+			return ret;
+		*val = rx[0];
+		return 0;
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 2);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be16(rx);
+		return 0;
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 3);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be24(rx);
+		return 0;
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 4);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be32(rx);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct spi_device *spi = context;
+	u8 tx[4];
+
+	put_unaligned_be16(reg, tx);
+
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
+	case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
+		if (val > 0xFF)
+			return -EINVAL;
+		tx[2] = val;
+		return spi_write_then_read(spi, tx, 3, NULL, 0);
+	case AD4691_ACC_MASK_REG:
+	case AD4691_STD_SEQ_CONFIG:
+		if (val > 0xFFFF)
+			return -EINVAL;
+		put_unaligned_be16(val, &tx[2]);
+		return spi_write_then_read(spi, tx, 4, NULL, 0);
+	default:
+		return -EINVAL;
+	}
+}
+
+static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case AD4691_STATUS_REG:
+	case AD4691_CLAMP_STATUS1_REG:
+	case AD4691_CLAMP_STATUS2_REG:
+	case AD4691_GPIO_READ:
+	case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
+	case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config ad4691_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 32,
+	.reg_read = ad4691_reg_read,
+	.reg_write = ad4691_reg_write,
+	.volatile_reg = ad4691_volatile_reg,
+	.readable_reg = ad4691_readable_reg,
+	.writeable_reg = ad4691_writeable_reg,
+	.max_register = AD4691_ACC_STS_DATA(15),
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
+{
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
+	if (ret)
+		return ret;
+
+	*val = ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
+	return IIO_VAL_INT;
+}
+
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+	unsigned int i;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	for (i = start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) {
+		if (ad4691_osc_freqs[i] != freq)
+			continue;
+		return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
+					  AD4691_OSC_FREQ_MASK, i);
+	}
+
+	return -EINVAL;
+}
+
+static int ad4691_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type,
+			     int *length, long mask)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*vals = &ad4691_osc_freqs[start];
+		*type = IIO_VAL_INT;
+		*length = ARRAY_SIZE(ad4691_osc_freqs) - start;
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_single_shot_read(struct iio_dev *indio_dev,
+				   struct iio_chan_spec const *chan, int *val)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int reg_val;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	/*
+	 * Use AUTONOMOUS mode for single-shot reads.
+	 */
+	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+			   AD4691_STATE_RESET_ALL);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   BIT(chan->channel));
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+			   (u16)~BIT(chan->channel));
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
+	if (ret)
+		return ret;
+
+	/*
+	 * Wait for at least 2 internal oscillator periods for the
+	 * conversion to complete.
+	 */
+	fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]));
+
+	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), &reg_val);
+	if (ret)
+		return ret;
+
+	*val = reg_val;
+
+	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+static int ad4691_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan, int *val,
+			   int *val2, long info)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW: {
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		return ad4691_single_shot_read(indio_dev, chan, val);
+	}
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ad4691_get_sampling_freq(st, val);
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->vref_uV / (MICRO / MILLI);
+		*val2 = chan->scan_type.realbits;
+		return IIO_VAL_FRACTIONAL_LOG2;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ad4691_set_sampling_freq(indio_dev, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+			     unsigned int writeval, unsigned int *readval)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	guard(mutex)(&st->lock);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad4691_info = {
+	.read_raw = &ad4691_read_raw,
+	.write_raw = &ad4691_write_raw,
+	.read_avail = &ad4691_read_avail,
+	.debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_regulator_setup(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	int ret;
+
+	ret = devm_regulator_get_enable(dev, "avdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get and enable AVDD\n");
+
+	ret = devm_regulator_get_enable(dev, "vio");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
+
+	ret = devm_regulator_get_enable(dev, "ldo-in");
+	if (ret == -ENODEV)
+		st->ldo_en = true;
+	else if (ret)
+		return dev_err_probe(dev, ret, "Failed to get and enable LDO-IN\n");
+
+	st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+	if (st->vref_uV == -ENODEV) {
+		st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
+		st->refbuf_en = true;
+	}
+	if (st->vref_uV < 0)
+		return dev_err_probe(dev, st->vref_uV,
+				     "Failed to get reference supply\n");
+
+	if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
+		return dev_err_probe(dev, -EINVAL,
+				     "vref(%d) must be in the range [%u...%u]\n",
+				     st->vref_uV, AD4691_VREF_uV_MIN,
+				     AD4691_VREF_uV_MAX);
+
+	return 0;
+}
+
+static int ad4691_reset(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	struct reset_control *rst;
+
+	rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+	if (IS_ERR(rst))
+		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
+
+	if (rst) {
+		/*
+		 * The GPIO is already asserted by reset_gpio_probe (GPIOD_OUT_HIGH).
+		 * Wait for the reset pulse width required by the chip. See datasheet Table 5.
+		 */
+		fsleep(300);
+		return reset_control_deassert(rst);
+	}
+
+	/* No hardware reset available, fall back to software reset. */
+	return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
+			    AD4691_SW_RESET);
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	enum ad4691_ref_ctrl ref_val;
+	int ret;
+
+	switch (st->vref_uV) {
+	case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
+		ref_val = AD4691_VREF_2P5;
+		break;
+	case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX:
+		ref_val = AD4691_VREF_3P0;
+		break;
+	case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX:
+		ref_val = AD4691_VREF_3P3;
+		break;
+	case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX:
+		ref_val = AD4691_VREF_4P096;
+		break;
+	case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX:
+		ref_val = AD4691_VREF_5P0;
+		break;
+	default:
+		return dev_err_probe(dev, -EINVAL,
+				     "Unsupported vref voltage: %d uV\n",
+				     st->vref_uV);
+	}
+
+	ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
+				 AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN,
+				 FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val) |
+				 (st->refbuf_en ? AD4691_REFBUF_EN : 0));
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+	ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP,
+				 AD4691_LDO_EN, st->ldo_en);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n");
+
+	/*
+	 * Set the internal oscillator to the highest rate this chip supports.
+	 * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
+	 * chips start at index 1 (500 kHz).
+	 */
+	ret = regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
+				 AD4691_OSC_FREQ_MASK,
+				 (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
+
+	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				 AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+	return 0;
+}
+
+static int ad4691_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct ad4691_state *st;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->info = spi_get_device_match_data(spi);
+
+	ret = devm_mutex_init(dev, &st->lock);
+	if (ret)
+		return ret;
+
+	st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
+	if (IS_ERR(st->regmap))
+		return dev_err_probe(dev, PTR_ERR(st->regmap),
+				     "Failed to initialize regmap\n");
+
+	ret = ad4691_regulator_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ad4691_reset(st);
+	if (ret)
+		return ret;
+
+	ret = ad4691_config(st);
+	if (ret)
+		return ret;
+
+	indio_dev->name = st->info->name;
+	indio_dev->info = &ad4691_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	indio_dev->channels = st->info->channels;
+	indio_dev->num_channels = st->info->num_channels;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+	{ .compatible = "adi,ad4691", .data = &ad4691_chip_info },
+	{ .compatible = "adi,ad4692", .data = &ad4692_chip_info },
+	{ .compatible = "adi,ad4693", .data = &ad4693_chip_info },
+	{ .compatible = "adi,ad4694", .data = &ad4694_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+	{ "ad4691", (kernel_ulong_t)&ad4691_chip_info },
+	{ "ad4692", (kernel_ulong_t)&ad4692_chip_info },
+	{ "ad4693", (kernel_ulong_t)&ad4693_chip_info },
+	{ "ad4694", (kernel_ulong_t)&ad4694_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ad4691_id);
+
+static struct spi_driver ad4691_driver = {
+	.driver = {
+		.name = "ad4691",
+		.of_match_table = ad4691_of_match,
+	},
+	.probe = ad4691_probe,
+	.id_table = ad4691_id,
+};
+module_spi_driver(ad4691_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0



^ permalink raw reply related

* [PATCH v5 3/4] iio: adc: ad4691: add triggered buffer support
From: Radu Sabau via B4 Relay @ 2026-03-27 11:07 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc, Radu Sabau
In-Reply-To: <20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com>

From: Radu Sabau <radu.sabau@analog.com>

Add buffered capture support using the IIO triggered buffer framework.

CNV Burst Mode: the GP pin identified by interrupt-names in the device
tree is configured as DATA_READY output. The IRQ handler stops
conversions and fires the IIO trigger; the trigger handler executes a
pre-built SPI message that reads all active channels from the AVG_IN
accumulator registers and then resets accumulator state and restarts
conversions for the next cycle.

Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
reads the previous result and starts the next conversion (pipelined
N+1 scheme). At preenable time a pre-built, optimised SPI message of
N+1 transfers is constructed (N channel reads plus one NOOP to drain
the pipeline). The trigger handler executes the message in a single
spi_sync() call and collects the results. An external trigger (e.g.
iio-trig-hrtimer) is required to drive the trigger at the desired
sample rate.

Both modes share the same trigger handler and push a complete scan —
one u16 slot per channel at its scan_index position, followed by a
timestamp — to the IIO buffer via iio_push_to_buffers_with_ts().

The CNV Burst Mode sampling frequency (PWM period) is exposed as a
buffer-level attribute via IIO_DEVICE_ATTR.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/adc/Kconfig  |   2 +
 drivers/iio/adc/ad4691.c | 616 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 612 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 3685a03aa8dc..d498f16c0816 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -142,6 +142,8 @@ config AD4170_4
 config AD4691
 	tristate "Analog Devices AD4691 Family ADC Driver"
 	depends on SPI
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	select REGMAP
 	help
 	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index f930efdb9d8c..b5a7646b46ca 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -4,14 +4,18 @@
  * Author: Radu Sabau <radu.sabau@analog.com>
  */
 #include <linux/bitfield.h>
+#include <linux/bitmap.h>
 #include <linux/bitops.h>
 #include <linux/cleanup.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/interrupt.h>
 #include <linux/math.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
@@ -19,7 +23,12 @@
 #include <linux/units.h>
 #include <linux/unaligned.h>
 
+#include <linux/iio/buffer.h>
 #include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
 
 #define AD4691_VREF_uV_MIN			2400000
 #define AD4691_VREF_uV_MAX			5250000
@@ -28,6 +37,8 @@
 #define AD4691_VREF_3P3_uV_MAX			3750000
 #define AD4691_VREF_4P096_uV_MAX		4500000
 
+#define AD4691_CNV_DUTY_CYCLE_NS		380
+
 #define AD4691_SPI_CONFIG_A_REG			0x000
 #define AD4691_SW_RESET				(BIT(7) | BIT(0))
 
@@ -35,6 +46,7 @@
 #define AD4691_CLAMP_STATUS1_REG		0x01A
 #define AD4691_CLAMP_STATUS2_REG		0x01B
 #define AD4691_DEVICE_SETUP			0x020
+#define AD4691_MANUAL_MODE			BIT(2)
 #define AD4691_LDO_EN				BIT(4)
 #define AD4691_REF_CTRL				0x021
 #define AD4691_REF_CTRL_MASK			GENMASK(4, 2)
@@ -42,13 +54,18 @@
 #define AD4691_OSC_FREQ_REG			0x023
 #define AD4691_OSC_FREQ_MASK			GENMASK(3, 0)
 #define AD4691_STD_SEQ_CONFIG			0x025
+#define AD4691_SEQ_ALL_CHANNELS_OFF		0x00
 #define AD4691_SPARE_CONTROL			0x02A
 
+#define AD4691_NOOP				0x00
+#define AD4691_ADC_CHAN(ch)			((0x10 + (ch)) << 3)
+
 #define AD4691_OSC_EN_REG			0x180
 #define AD4691_STATE_RESET_REG			0x181
 #define AD4691_STATE_RESET_ALL			0x01
 #define AD4691_ADC_SETUP			0x182
 #define AD4691_ADC_MODE_MASK			GENMASK(1, 0)
+#define AD4691_CNV_BURST_MODE			0x01
 #define AD4691_AUTONOMOUS_MODE			0x02
 /*
  * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
@@ -58,6 +75,8 @@
 #define AD4691_ACC_DEPTH_IN(n)			(0x186 + (n))
 #define AD4691_GPIO_MODE1_REG			0x196
 #define AD4691_GPIO_MODE2_REG			0x197
+#define AD4691_GP_MODE_MASK			GENMASK(3, 0)
+#define AD4691_GP_MODE_DATA_READY		0x06
 #define AD4691_GPIO_READ			0x1A0
 #define AD4691_ACC_STATUS_FULL1_REG		0x1B0
 #define AD4691_ACC_STATUS_FULL2_REG		0x1B1
@@ -91,9 +110,11 @@ struct ad4691_chip_info {
 		.type = IIO_VOLTAGE,					\
 		.indexed = 1,						\
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)		\
-				    | BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+				    | BIT(IIO_CHAN_INFO_SAMP_FREQ)	\
+				    | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),	\
 		.info_mask_separate_available =				\
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ)	\
+				    | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),	\
 		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
 		.channel = ch,						\
 		.scan_index = ch,					\
@@ -121,6 +142,7 @@ static const struct iio_chan_spec ad4691_channels[] = {
 	AD4691_CHANNEL(13),
 	AD4691_CHANNEL(14),
 	AD4691_CHANNEL(15),
+	IIO_CHAN_SOFT_TIMESTAMP(16),
 };
 
 static const struct iio_chan_spec ad4693_channels[] = {
@@ -132,6 +154,7 @@ static const struct iio_chan_spec ad4693_channels[] = {
 	AD4691_CHANNEL(5),
 	AD4691_CHANNEL(6),
 	AD4691_CHANNEL(7),
+	IIO_CHAN_SOFT_TIMESTAMP(8),
 };
 
 /*
@@ -158,6 +181,14 @@ static const int ad4691_osc_freqs[] = {
 	1250,		/* 0xF: 1.25 kHz */
 };
 
+static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
+
+/*
+ * Valid ACC_DEPTH values where the effective divisor equals the count.
+ * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
+ */
+static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
+
 static const struct ad4691_chip_info ad4691_chip_info = {
 	.channels = ad4691_channels,
 	.name = "ad4691",
@@ -189,16 +220,55 @@ static const struct ad4691_chip_info ad4694_chip_info = {
 struct ad4691_state {
 	const struct ad4691_chip_info *info;
 	struct regmap *regmap;
+
+	struct pwm_device *conv_trigger;
+	int irq;
+
+	bool manual_mode;
+
 	int vref_uV;
+	u8 osr[16];
 	bool refbuf_en;
 	bool ldo_en;
+	u32 cnv_period_ns;
 	/*
 	 * Synchronize access to members of the driver state, and ensure
 	 * atomicity of consecutive SPI operations.
 	 */
 	struct mutex lock;
+	/*
+	 * Per-buffer-enable lifetime resources:
+	 * Manual Mode - a pre-built SPI message that clocks out N+1
+	 *		 transfers in one go.
+	 * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
+	 *		    transfers in one go.
+	 */
+	struct spi_message scan_msg;
+	struct spi_transfer *scan_xfers;
+	__be16 *scan_tx;
+	__be16 *scan_rx;
+	/* Scan buffer: one slot per channel plus timestamp */
+	struct {
+		u16 vals[16];
+		aligned_s64 ts;
+	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+/*
+ * Configure the given GP pin (0-3) as DATA_READY output.
+ * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
+ * Even pins occupy bits [3:0], odd pins bits [7:4].
+ */
+static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
+{
+	unsigned int shift = 4 * (gp_num % 2);
+
+	return regmap_update_bits(st->regmap,
+				  AD4691_GPIO_MODE1_REG + gp_num / 2,
+				  AD4691_GP_MODE_MASK << shift,
+				  AD4691_GP_MODE_DATA_READY << shift);
+}
+
 static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
 {
 	struct spi_device *spi = context;
@@ -359,6 +429,29 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
 	return -EINVAL;
 }
 
+static int ad4691_set_oversampling_ratio(struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan,
+					 int osr)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i++) {
+		if (ad4691_oversampling_ratios[i] != osr)
+			continue;
+
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		st->osr[chan->scan_index] = osr;
+		return regmap_write(st->regmap,
+				    AD4691_ACC_DEPTH_IN(chan->scan_index), osr);
+	}
+
+	return -EINVAL;
+}
+
 static int ad4691_read_avail(struct iio_dev *indio_dev,
 			     struct iio_chan_spec const *chan,
 			     const int **vals, int *type,
@@ -373,6 +466,11 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
 		*type = IIO_VAL_INT;
 		*length = ARRAY_SIZE(ad4691_osc_freqs) - start;
 		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		*vals = ad4691_oversampling_ratios;
+		*type = IIO_VAL_INT;
+		*length = ARRAY_SIZE(ad4691_oversampling_ratios);
+		return IIO_AVAIL_LIST;
 	default:
 		return -EINVAL;
 	}
@@ -405,6 +503,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
 	if (ret)
 		return ret;
 
+	ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->scan_index),
+			   st->osr[chan->scan_index]);
+	if (ret)
+		return ret;
+
 	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
 	if (ret)
 		return ret;
@@ -414,10 +517,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
 		return ret;
 
 	/*
-	 * Wait for at least 2 internal oscillator periods for the
-	 * conversion to complete.
+	 * Wait for at least 2 internal oscillator periods per accumulation
+	 * depth for the conversion to complete.
 	 */
-	fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]));
+	fsleep(DIV_ROUND_UP((unsigned long)st->osr[chan->scan_index] * 2 * USEC_PER_SEC,
+			    ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]));
 
 	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
 	if (ret)
@@ -452,6 +556,9 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
 	}
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		return ad4691_get_sampling_freq(st, val);
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		*val = st->osr[chan->scan_index];
+		return IIO_VAL_INT;
 	case IIO_CHAN_INFO_SCALE:
 		*val = st->vref_uV / (MICRO / MILLI);
 		*val2 = chan->scan_type.realbits;
@@ -468,6 +575,8 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
 	switch (mask) {
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		return ad4691_set_sampling_freq(indio_dev, val);
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		return ad4691_set_oversampling_ratio(indio_dev, chan, val);
 	default:
 		return -EINVAL;
 	}
@@ -486,6 +595,386 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
 	return regmap_write(st->regmap, reg, writeval);
 }
 
+static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq)
+{
+	if (!freq)
+		return -EINVAL;
+
+	st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
+	return 0;
+}
+
+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
+{
+	struct pwm_state conv_state = {
+		.period     = st->cnv_period_ns,
+		.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
+		.polarity   = PWM_POLARITY_NORMAL,
+		.enabled    = enable,
+	};
+
+	return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
+}
+
+/*
+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
+ *
+ * Configures the ADC hardware registers for the mode selected at probe
+ * (CNV_BURST or MANUAL). Called from buffer preenable before starting
+ * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
+ */
+static int ad4691_enter_conversion_mode(struct ad4691_state *st)
+{
+	int ret;
+
+	if (st->manual_mode)
+		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+					  AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
+
+	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				 AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
+	if (ret)
+		return ret;
+
+	return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+			    AD4691_STATE_RESET_ALL);
+}
+
+/*
+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
+ *
+ * Called from buffer postdisable to restore the chip to the
+ * idle state used by read_raw. Clears the sequencer and resets state.
+ */
+static int ad4691_exit_conversion_mode(struct ad4691_state *st)
+{
+	if (st->manual_mode)
+		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+					  AD4691_MANUAL_MODE, 0);
+
+	return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				  AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+}
+
+static void ad4691_free_scan_bufs(struct ad4691_state *st)
+{
+	kfree(st->scan_xfers);
+	kfree(st->scan_tx);
+	kfree(st->scan_rx);
+	st->scan_xfers = NULL;
+	st->scan_tx = NULL;
+	st->scan_rx = NULL;
+}
+
+static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+					      indio_dev->masklength);
+	unsigned int n_xfers = n_active + 1;
+	unsigned int k, i;
+	int ret;
+
+	st->scan_xfers = kcalloc(n_xfers, sizeof(*st->scan_xfers), GFP_KERNEL);
+	if (!st->scan_xfers)
+		return -ENOMEM;
+
+	st->scan_tx = kcalloc(n_xfers, sizeof(*st->scan_tx), GFP_KERNEL);
+	if (!st->scan_tx) {
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	st->scan_rx = kcalloc(n_xfers, sizeof(*st->scan_rx), GFP_KERNEL);
+	if (!st->scan_rx) {
+		kfree(st->scan_tx);
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&st->scan_msg);
+
+	k = 0;
+	iio_for_each_active_channel(indio_dev, i) {
+		st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
+		st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+		st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+		st->scan_xfers[k].len = sizeof(__be16);
+		st->scan_xfers[k].cs_change = 1;
+		spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+		k++;
+	}
+
+	/* Final NOOP transfer to retrieve last channel's result. */
+	st->scan_tx[k] = cpu_to_be16(AD4691_NOOP);
+	st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+	st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+	st->scan_xfers[k].len = sizeof(__be16);
+	spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+
+	st->scan_msg.spi = spi;
+
+	ret = spi_optimize_message(spi, &st->scan_msg);
+	if (ret) {
+		ad4691_free_scan_bufs(st);
+		return ret;
+	}
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret) {
+		spi_unoptimize_message(&st->scan_msg);
+		ad4691_free_scan_bufs(st);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ad4691_exit_conversion_mode(st);
+	spi_unoptimize_message(&st->scan_msg);
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
+	.preenable = &ad4691_manual_buffer_preenable,
+	.postdisable = &ad4691_manual_buffer_postdisable,
+};
+
+static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+					      indio_dev->masklength);
+	unsigned int bit, k, i;
+	int ret;
+
+	st->scan_xfers = kcalloc(2 * n_active, sizeof(*st->scan_xfers), GFP_KERNEL);
+	if (!st->scan_xfers)
+		return -ENOMEM;
+
+	st->scan_tx = kcalloc(n_active, sizeof(*st->scan_tx), GFP_KERNEL);
+	if (!st->scan_tx) {
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	st->scan_rx = kcalloc(n_active, sizeof(*st->scan_rx), GFP_KERNEL);
+	if (!st->scan_rx) {
+		kfree(st->scan_tx);
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&st->scan_msg);
+
+	/*
+	 * Each AVG_IN read needs two transfers: a 2-byte address write phase
+	 * followed by a 2-byte data read phase. CS toggles between channels
+	 * (cs_change=1 on the read phase of all but the last channel).
+	 */
+	k = 0;
+	iio_for_each_active_channel(indio_dev, i) {
+		st->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));
+		st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
+		st->scan_xfers[2 * k].len = sizeof(__be16);
+		spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
+		st->scan_xfers[2 * k + 1].rx_buf = &st->scan_rx[k];
+		st->scan_xfers[2 * k + 1].len = sizeof(__be16);
+		if (k < n_active - 1)
+			st->scan_xfers[2 * k + 1].cs_change = 1;
+		spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
+		k++;
+	}
+
+	st->scan_msg.spi = spi;
+
+	ret = spi_optimize_message(spi, &st->scan_msg);
+	if (ret) {
+		ad4691_free_scan_bufs(st);
+		return ret;
+	}
+
+	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+			   (u16)~bitmap_read(indio_dev->active_scan_mask, 0,
+					     indio_dev->masklength));
+	if (ret)
+		goto err;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   bitmap_read(indio_dev->active_scan_mask, 0,
+				       indio_dev->masklength));
+	if (ret)
+		goto err;
+
+	iio_for_each_active_channel(indio_dev, bit) {
+		ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
+				   st->osr[bit]);
+		if (ret)
+			goto err;
+	}
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret)
+		goto err;
+
+	ret = ad4691_sampling_enable(st, true);
+	if (ret)
+		goto err;
+
+	enable_irq(st->irq);
+	return 0;
+err:
+	spi_unoptimize_message(&st->scan_msg);
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int ret;
+
+	disable_irq(st->irq);
+
+	ret = ad4691_sampling_enable(st, false);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   AD4691_SEQ_ALL_CHANNELS_OFF);
+	if (ret)
+		return ret;
+
+	ret = ad4691_exit_conversion_mode(st);
+	spi_unoptimize_message(&st->scan_msg);
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
+	.preenable = &ad4691_cnv_burst_buffer_preenable,
+	.postdisable = &ad4691_cnv_burst_buffer_postdisable,
+};
+
+static ssize_t sampling_frequency_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int freq, ret;
+
+	ret = kstrtoint(buf, 10, &freq);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = ad4691_set_pwm_freq(st, freq);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static IIO_DEVICE_ATTR(sampling_frequency, 0644,
+		       sampling_frequency_show,
+		       sampling_frequency_store, 0);
+
+static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
+	&iio_dev_attr_sampling_frequency,
+	NULL
+};
+
+static irqreturn_t ad4691_irq(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	/*
+	 * GPx 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.
+	 */
+	ad4691_sampling_enable(st, false);
+	iio_trigger_poll(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops ad4691_trigger_ops = {
+	.validate_device = iio_trigger_validate_own_device,
+};
+
+static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int i, k = 0;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = spi_sync(st->scan_msg.spi, &st->scan_msg);
+	if (ret)
+		return ret;
+
+	if (st->manual_mode) {
+		iio_for_each_active_channel(indio_dev, i) {
+			st->scan.vals[i] = be16_to_cpu(st->scan_rx[k + 1]);
+			k++;
+		}
+	} else {
+		iio_for_each_active_channel(indio_dev, i) {
+			st->scan.vals[i] = be16_to_cpu(st->scan_rx[k]);
+			k++;
+		}
+
+		ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+				   AD4691_STATE_RESET_ALL);
+		if (ret)
+			return ret;
+
+		ret = ad4691_sampling_enable(st, true);
+		if (ret)
+			return ret;
+	}
+
+	iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+				    timestamp);
+	return 0;
+}
+
+static irqreturn_t ad4691_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+
+	ad4691_read_scan(indio_dev, pf->timestamp);
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
 static const struct iio_info ad4691_info = {
 	.read_raw = &ad4691_read_raw,
 	.write_raw = &ad4691_write_raw,
@@ -493,6 +982,18 @@ static const struct iio_info ad4691_info = {
 	.debugfs_reg_access = &ad4691_reg_access,
 };
 
+static int ad4691_pwm_setup(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+
+	st->conv_trigger = devm_pwm_get(dev, "cnv");
+	if (IS_ERR(st->conv_trigger))
+		return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
+				     "Failed to get cnv pwm\n");
+
+	return ad4691_set_pwm_freq(st, st->info->max_rate);
+}
+
 static int ad4691_regulator_setup(struct ad4691_state *st)
 {
 	struct device *dev = regmap_get_device(st->regmap);
@@ -557,8 +1058,25 @@ static int ad4691_config(struct ad4691_state *st)
 {
 	struct device *dev = regmap_get_device(st->regmap);
 	enum ad4691_ref_ctrl ref_val;
+	unsigned int gp_num;
 	int ret;
 
+	/*
+	 * Determine buffer conversion mode from DT: if a PWM is provided it
+	 * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS
+	 * and each SPI transfer triggers a conversion (MANUAL_MODE).
+	 * Both modes idle in AUTONOMOUS mode so that read_raw can use the
+	 * internal oscillator without disturbing the hardware configuration.
+	 */
+	if (device_property_present(dev, "pwms")) {
+		st->manual_mode = false;
+		ret = ad4691_pwm_setup(st);
+		if (ret)
+			return ret;
+	} else {
+		st->manual_mode = true;
+	}
+
 	switch (st->vref_uV) {
 	case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
 		ref_val = AD4691_VREF_2P5;
@@ -609,7 +1127,87 @@ static int ad4691_config(struct ad4691_state *st)
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
 
-	return 0;
+	if (st->manual_mode)
+		return 0;
+
+	for (gp_num = 0; gp_num < ARRAY_SIZE(ad4691_gp_names); gp_num++) {
+		if (fwnode_irq_get_byname(dev_fwnode(dev),
+					  ad4691_gp_names[gp_num]) > 0)
+			break;
+	}
+	if (gp_num >= ARRAY_SIZE(ad4691_gp_names))
+		return dev_err_probe(dev, -ENODEV,
+				     "No valid GP interrupt found\n");
+
+	return ad4691_gpio_setup(st, gp_num);
+}
+
+static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
+					 struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	struct iio_trigger *trig;
+	unsigned int i;
+	int irq, ret;
+
+	trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+				      indio_dev->name,
+				      iio_device_id(indio_dev));
+	if (!trig)
+		return -ENOMEM;
+
+	trig->ops = &ad4691_trigger_ops;
+	iio_trigger_set_drvdata(trig, st);
+
+	ret = devm_iio_trigger_register(dev, trig);
+	if (ret)
+		return dev_err_probe(dev, ret, "IIO trigger register failed\n");
+
+	indio_dev->trig = iio_trigger_get(trig);
+
+	if (!st->manual_mode) {
+		/*
+		 * The GP pin named in interrupt-names asserts at end-of-conversion.
+		 * The IRQ handler stops conversions and fires the IIO trigger so
+		 * the trigger handler can read and push the sample to the buffer.
+		 * The IRQ is kept disabled until the buffer is enabled.
+		 */
+		irq = -ENODEV;
+		for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
+			irq = fwnode_irq_get_byname(dev_fwnode(dev),
+						    ad4691_gp_names[i]);
+			if (irq > 0)
+				break;
+		}
+		if (irq <= 0)
+			return dev_err_probe(dev, irq < 0 ? irq : -ENODEV,
+					     "failed to get GP interrupt\n");
+
+		st->irq = irq;
+
+		/*
+		 * IRQ is kept disabled until the buffer is enabled to prevent
+		 * spurious DATA_READY events before the SPI message is set up.
+		 */
+		ret = devm_request_threaded_irq(dev, irq, NULL,
+						&ad4691_irq,
+						IRQF_ONESHOT | IRQF_NO_AUTOEN,
+						indio_dev->name, indio_dev);
+		if (ret)
+			return ret;
+
+		return devm_iio_triggered_buffer_setup_ext(dev, indio_dev,
+							   &iio_pollfunc_store_time,
+							   &ad4691_trigger_handler,
+							   IIO_BUFFER_DIRECTION_IN,
+							   &ad4691_cnv_burst_buffer_setup_ops,
+							   ad4691_buffer_attrs);
+	}
+
+	return devm_iio_triggered_buffer_setup(dev, indio_dev,
+					       &iio_pollfunc_store_time,
+					       &ad4691_trigger_handler,
+					       &ad4691_manual_buffer_setup_ops);
 }
 
 static int ad4691_probe(struct spi_device *spi)
@@ -626,6 +1224,8 @@ static int ad4691_probe(struct spi_device *spi)
 	st = iio_priv(indio_dev);
 	st->info = spi_get_device_match_data(spi);
 
+	memset(st->osr, 1, sizeof(st->osr));
+
 	ret = devm_mutex_init(dev, &st->lock);
 	if (ret)
 		return ret;
@@ -654,6 +1254,10 @@ static int ad4691_probe(struct spi_device *spi)
 	indio_dev->channels = st->info->channels;
 	indio_dev->num_channels = st->info->num_channels;
 
+	ret = ad4691_setup_triggered_buffer(indio_dev, st);
+	if (ret)
+		return ret;
+
 	return devm_iio_device_register(dev, indio_dev);
 }
 

-- 
2.43.0



^ permalink raw reply related

* [PATCH v5 4/4] iio: adc: ad4691: add SPI offload support
From: Radu Sabau via B4 Relay @ 2026-03-27 11:08 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc, Radu Sabau
In-Reply-To: <20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com>

From: Radu Sabau <radu.sabau@analog.com>

Add SPI offload support to enable DMA-based, CPU-independent data
acquisition using the SPI Engine offload framework.

When an SPI offload is available (devm_spi_offload_get() succeeds),
the driver registers a DMA engine IIO buffer and uses dedicated buffer
setup operations. If no offload is available the existing software
triggered buffer path is used unchanged.

Both CNV Burst Mode and Manual Mode support offload, but use different
trigger mechanisms:

CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY
signal on the GP pin specified by the trigger-source consumer reference
in the device tree (one cell = GP pin number 0-3). For this mode the
driver acts as both an SPI offload consumer (DMA RX stream, message
optimization) and a trigger source provider: it registers the
GP/DATA_READY output via devm_spi_offload_trigger_register() so the
offload framework can match the '#trigger-source-cells' phandle and
automatically fire the SPI Engine DMA transfer at end-of-conversion.

Manual Mode: the SPI Engine is triggered by a periodic trigger at
the configured sampling frequency. The pre-built SPI message uses
the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued
for N active channels (the first result is discarded as garbage from
the pipeline flush) and the remaining N results are captured by DMA.

All offload transfers use 32-bit frames (bits_per_word=32, len=4) for
DMA word alignment. This patch promotes the channel scan_type from
storagebits=16 (triggered-buffer path) to storagebits=32 to match the
DMA word size; the triggered-buffer paths are updated to the same layout
for consistency. CNV Burst Mode channel data arrives in the lower 16
bits of the 32-bit word (shift=0); Manual Mode data arrives in the upper
16 bits (shift=16), matching the 4-byte SPI transfer layout
[data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array
encodes the shift=16 scan type for manual mode.

Add driver documentation under Documentation/iio/ad4691.rst covering
operating modes, oversampling, reference voltage, SPI offload paths,
and buffer data layout; register in MAINTAINERS and index.rst

Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 Documentation/iio/ad4691.rst | 259 ++++++++++++++++++++++++++
 Documentation/iio/index.rst  |   1 +
 MAINTAINERS                  |   1 +
 drivers/iio/adc/Kconfig      |   1 +
 drivers/iio/adc/ad4691.c     | 426 ++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 680 insertions(+), 8 deletions(-)

diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
new file mode 100644
index 000000000000..36f0c841605a
--- /dev/null
+++ b/Documentation/iio/ad4691.rst
@@ -0,0 +1,259 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD4691 driver
+=============
+
+ADC driver for Analog Devices Inc. AD4691 family of multichannel SAR ADCs.
+The module name is ``ad4691``.
+
+
+Supported devices
+=================
+
+The following chips are supported by this driver:
+
+* `AD4691 <https://www.analog.com/en/products/ad4691.html>`_ — 16-channel, 500 kSPS
+* `AD4692 <https://www.analog.com/en/products/ad4692.html>`_ — 16-channel, 1 MSPS
+* `AD4693 <https://www.analog.com/en/products/ad4693.html>`_ — 8-channel, 500 kSPS
+* `AD4694 <https://www.analog.com/en/products/ad4694.html>`_ — 8-channel, 1 MSPS
+
+
+IIO channels
+============
+
+Each physical ADC input maps to one IIO voltage channel. The AD4691 and AD4692
+expose 16 channels (``voltage0`` through ``voltage15``); the AD4693 and AD4694
+expose 8 channels (``voltage0`` through ``voltage7``).
+
+All channels share a common scale (``in_voltage_scale``), derived from the
+reference voltage. Each channel independently exposes:
+
+* ``in_voltageN_raw`` — single-shot ADC result
+* ``in_voltageN_sampling_frequency`` — internal oscillator frequency used for
+  single-shot reads and CNV Burst Mode buffered captures
+* ``in_voltageN_sampling_frequency_available`` — list of valid oscillator
+  frequencies
+* ``in_voltageN_oversampling_ratio`` — per-channel hardware accumulation depth
+* ``in_voltageN_oversampling_ratio_available`` — list of valid ratios
+
+
+Operating modes
+===============
+
+The driver supports two operating modes, auto-detected from the device tree at
+probe time. Both modes transition to and from an internal Autonomous Mode idle
+state when the IIO buffer is enabled and disabled.
+
+Manual Mode
+-----------
+
+Selected when no ``pwms`` property is present in the device tree. The CNV pin
+is tied to the SPI chip-select: every CS assertion both triggers a new
+conversion and returns the result of the previous one (pipelined N+1 scheme).
+
+To read N channels the driver issues N+1 SPI transfers in a single optimised
+message:
+
+* Transfers 0 to N-1 each carry ``AD4691_ADC_CHAN(n)`` in the TX byte to
+  select the next channel; the RX byte of transfer ``k+1`` contains the result
+  of the channel selected in transfer ``k``.
+* Transfer N is a NOOP (0x00) to flush the last conversion result out of the
+  pipeline.
+
+The external IIO trigger (``pollfunc_store_time``) drives the trigger handler,
+which executes the pre-built SPI message and pushes the scan to the buffer.
+
+CNV Burst Mode
+--------------
+
+Selected when a ``pwms`` property is present in the device tree. The PWM drives
+the CNV pin independently of SPI at the configured conversion rate, and a GP
+pin (identified by ``interrupt-names``) asserts DATA_READY at end-of-burst to
+signal that the AVG_IN result registers are ready to be read.
+
+The IRQ handler stops the PWM, fires the IIO trigger, and the trigger handler
+reads all active ``AVG_IN(n)`` registers in a single optimised SPI message and
+pushes the scan to the buffer.
+
+The buffer sampling frequency (i.e. the PWM rate) is controlled by the
+``sampling_frequency`` attribute on the IIO buffer. Valid values span from the
+chip's minimum oscillator rate up to its maximum conversion rate
+(500 kSPS for AD4691/AD4693, 1 MSPS for AD4692/AD4694).
+
+Autonomous Mode (idle / single-shot)
+-------------------------------------
+
+The chip idles in Autonomous Mode whenever the IIO buffer is disabled. In this
+state, ``read_raw`` requests (``in_voltageN_raw``) use the internal oscillator
+to perform a single conversion on the requested channel and read back the
+result from the ``AVG_IN(N)`` register. The oscillator is started and stopped
+for each read to save power.
+
+
+Oversampling
+============
+
+Each channel has an independent hardware accumulator (ACC_DEPTH_IN) that
+averages a configurable number of successive conversions before DATA_READY
+asserts. The result is always returned as a 16-bit mean from the ``AVG_IN``
+register, so the IIO ``realbits`` and ``storagebits`` are unaffected by the
+oversampling ratio.
+
+Valid ratios are 1, 2, 4, 8, 16 and 32. The default is 1 (no averaging).
+
+.. code-block:: bash
+
+    # Set oversampling ratio to 16 on channel 0
+    echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ratio
+
+When OSR > 1 the effective conversion rate for ``read_raw`` is reduced
+accordingly, since the driver waits for 2 × OSR oscillator periods before
+reading the result.
+
+
+Reference voltage
+=================
+
+The driver supports two reference configurations, mutually exclusive:
+
+* **External reference** (``ref-supply``): a voltage between 2.4 V and 5.25 V
+  supplied externally. The internal reference buffer is disabled.
+* **Buffered internal reference** (``refin-supply``): An internal reference
+  buffer is used. The driver enables ``REFBUF_EN`` in the REF_CTRL register
+  when this supply is used.
+
+Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the
+device tree.
+
+The reference voltage determines the full-scale range:
+
+.. code-block::
+
+    full-scale = Vref / 2^16  (per LSB)
+
+
+LDO supply
+==========
+
+The chip contains an internal LDO that powers part of the analog front-end.
+The LDO input can be driven externally via the ``ldo-in-supply`` regulator. If
+that supply is absent, the driver enables the internal LDO path (``LDO_EN``
+bit in DEVICE_SETUP).
+
+
+Reset
+=====
+
+The driver supports two reset mechanisms:
+
+* **Hardware reset** (``reset-gpios`` in device tree): the GPIO is already
+  asserted at driver probe by the reset controller framework. The driver waits
+  for the required 300 µs reset pulse width and then deasserts.
+* **Software reset** (fallback when ``reset-gpios`` is absent): the driver
+  writes the software-reset pattern to the SPI_CONFIG_A register.
+
+
+GP pins and interrupts
+======================
+
+The chip exposes up to four general-purpose (GP) pins that can be configured as
+interrupt outputs. In CNV Burst Mode (non-offload), one GP pin must be wired to
+an interrupt-capable SoC input and declared in the device tree using the
+``interrupts`` and ``interrupt-names`` properties.
+
+The ``interrupt-names`` value identifies which GP pin is used (``"gp0"``
+through ``"gp3"``). The driver configures that pin as a DATA_READY output in
+the GPIO_MODE register.
+
+Example device tree fragment::
+
+    adc@0 {
+        compatible = "adi,ad4692";
+        ...
+        interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
+        interrupt-parent = <&gpio0>;
+        interrupt-names = "gp0";
+    };
+
+
+SPI offload support
+===================
+
+When a SPI offload engine (e.g. the AXI SPI Engine) is present, the driver
+uses DMA-backed transfers for CPU-independent, high-throughput data capture.
+SPI offload is detected automatically at probe via ``devm_spi_offload_get()``;
+if no offload hardware is available the driver falls back to the software
+triggered-buffer path.
+
+Two SPI offload sub-modes exist, corresponding to the two operating modes:
+
+CNV Burst offload
+-----------------
+
+Used when a ``pwms`` property is present and SPI offload is available.
+
+The PWM drives CNV at the configured rate. On DATA_READY the SPI offload
+engine automatically executes a pre-built message that reads all active
+``AVG_IN`` registers and streams the data directly to an IIO DMA buffer with
+no CPU involvement. A final state-reset transfer re-arms DATA_READY for the
+next burst.
+
+The GP pin used as DATA_READY trigger is supplied by the trigger-source
+consumer (via ``#trigger-source-cells``) at buffer enable time; no
+``interrupt-names`` entry is required in this path.
+
+The buffer sampling frequency is controlled by the ``sampling_frequency``
+attribute on the IIO buffer (same as the non-offload CNV Burst path).
+
+Manual offload
+--------------
+
+Used when no ``pwms`` property is present and SPI offload is available.
+
+A periodic SPI offload trigger controls the conversion rate. On each trigger
+period, the SPI engine executes an N+1 transfer message (same pipelined scheme
+as software Manual Mode) and streams the data directly to the IIO DMA buffer.
+
+The ``sampling_frequency`` attribute on the IIO buffer controls the trigger
+rate (in Hz). The default is the chip's maximum conversion rate.
+
+
+Buffer data format
+==================
+
+The IIO buffer data format (``in_voltageN_type``) depends on the active path:
+
++-------------------------+-------------+-------------+-------+
+| Path                    | storagebits | realbits    | shift |
++=========================+=============+=============+=======+
+| Triggered buffer        | 16          | 16          | 0     |
++-------------------------+-------------+-------------+-------+
+| CNV Burst offload (DMA) | 32          | 16          | 0     |
++-------------------------+-------------+-------------+-------+
+| Manual offload (DMA)    | 32          | 16          | 16    |
++-------------------------+-------------+-------------+-------+
+
+In the triggered-buffer path the driver unpacks the 16-bit result in software
+before pushing to the buffer, so ``storagebits`` is 16.
+
+In the DMA offload paths the DMA engine writes 32-bit words directly into the
+IIO DMA buffer:
+
+* **CNV Burst offload**: the SPI engine reads AVG_IN registers with a 2-byte
+  address phase followed by a 2-byte data phase; the 16-bit result lands in
+  the lower half of the 32-bit word (``shift=0``).
+* **Manual offload**: each 32-bit SPI word carries the channel byte in the
+  first byte; the 16-bit result is returned in the upper half of the 32-bit
+  word (``shift=16``).
+
+The ``in_voltageN_type`` sysfs attribute reflects the active scan type.
+
+
+Unimplemented features
+======================
+
+* GPIO controller functionality of the GP pins
+* Clamp status and overrange events
+* Raw accumulator (ACC_IN) and accumulator status registers
+* ADC_BUSY and overrun status interrupts
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..007e0a1fcc5a 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -23,6 +23,7 @@ Industrial I/O Kernel Drivers
    ad4000
    ad4030
    ad4062
+   ad4691
    ad4695
    ad7191
    ad7380
diff --git a/MAINTAINERS b/MAINTAINERS
index 24e4502b8292..875ea2455d91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L:	linux-iio@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F:	Documentation/iio/ad4691.rst
 F:	drivers/iio/adc/ad4691.c
 
 ANALOG DEVICES INC AD4695 DRIVER
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index d498f16c0816..93f090e9a562 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -144,6 +144,7 @@ config AD4691
 	depends on SPI
 	select IIO_BUFFER
 	select IIO_TRIGGERED_BUFFER
+	select IIO_BUFFER_DMAENGINE
 	select REGMAP
 	help
 	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index b5a7646b46ca..d24f06207a11 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -9,6 +9,7 @@
 #include <linux/cleanup.h>
 #include <linux/delay.h>
 #include <linux/device.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
 #include <linux/math.h>
@@ -20,10 +21,14 @@
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 #include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
 #include <linux/units.h>
 #include <linux/unaligned.h>
 
 #include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dma.h>
+#include <linux/iio/buffer-dmaengine.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 #include <linux/iio/trigger.h>
@@ -38,6 +43,7 @@
 #define AD4691_VREF_4P096_uV_MAX		4500000
 
 #define AD4691_CNV_DUTY_CYCLE_NS		380
+#define AD4691_CNV_HIGH_TIME_NS			430
 
 #define AD4691_SPI_CONFIG_A_REG			0x000
 #define AD4691_SW_RESET				(BIT(7) | BIT(0))
@@ -90,6 +96,8 @@
 #define AD4691_ACC_IN(n)			(0x252 + (3 * (n)))
 #define AD4691_ACC_STS_DATA(n)			(0x283 + (4 * (n)))
 
+#define AD4691_OFFLOAD_BITS_PER_WORD		32
+
 enum ad4691_ref_ctrl {
 	AD4691_VREF_2P5   = 0,
 	AD4691_VREF_3P0   = 1,
@@ -105,6 +113,31 @@ struct ad4691_chip_info {
 	unsigned int max_rate;
 };
 
+enum {
+	AD4691_SCAN_TYPE_NORMAL,         /* triggered buffer:  storagebits=16, shift=0  */
+	AD4691_SCAN_TYPE_OFFLOAD_CNV,    /* CNV burst offload: storagebits=32, shift=0  */
+	AD4691_SCAN_TYPE_OFFLOAD_MANUAL, /* manual offload:    storagebits=32, shift=16 */
+};
+
+static const struct iio_scan_type ad4691_scan_types[] = {
+	[AD4691_SCAN_TYPE_NORMAL] = {
+		.sign = 'u',
+		.realbits = 16,
+		.storagebits = 16,
+	},
+	[AD4691_SCAN_TYPE_OFFLOAD_CNV] = {
+		.sign = 'u',
+		.realbits = 16,
+		.storagebits = 32,
+	},
+	[AD4691_SCAN_TYPE_OFFLOAD_MANUAL] = {
+		.sign = 'u',
+		.realbits = 16,
+		.storagebits = 32,
+		.shift = 16,
+	},
+};
+
 #define AD4691_CHANNEL(ch)						\
 	{								\
 		.type = IIO_VOLTAGE,					\
@@ -118,11 +151,9 @@ struct ad4691_chip_info {
 		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
 		.channel = ch,						\
 		.scan_index = ch,					\
-		.scan_type = {						\
-			.sign = 'u',					\
-			.realbits = 16,					\
-			.storagebits = 16,				\
-		},							\
+		.has_ext_scan_type = 1,					\
+		.ext_scan_type = ad4691_scan_types,			\
+		.num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),	\
 	}
 
 static const struct iio_chan_spec ad4691_channels[] = {
@@ -217,6 +248,17 @@ static const struct ad4691_chip_info ad4694_chip_info = {
 	.max_rate = 1 * HZ_PER_MHZ,
 };
 
+struct ad4691_offload_state {
+	struct spi_offload *spi;
+	struct spi_offload_trigger *trigger;
+	u64 trigger_hz;
+	struct spi_message msg;
+	/* Max 16 channel xfers + 1 state-reset or NOOP */
+	struct spi_transfer xfer[17];
+	u8 tx_cmd[17][4];
+	u8 tx_reset[4];
+};
+
 struct ad4691_state {
 	const struct ad4691_chip_info *info;
 	struct regmap *regmap;
@@ -247,6 +289,8 @@ struct ad4691_state {
 	struct spi_transfer *scan_xfers;
 	__be16 *scan_tx;
 	__be16 *scan_rx;
+	/* NULL when no SPI offload hardware is present */
+	struct ad4691_offload_state *offload;
 	/* Scan buffer: one slot per channel plus timestamp */
 	struct {
 		u16 vals[16];
@@ -269,6 +313,46 @@ static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
 				  AD4691_GP_MODE_DATA_READY << shift);
 }
 
+static const struct spi_offload_config ad4691_offload_config = {
+	.capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+			    SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger,
+					 enum spi_offload_trigger_type type,
+					 u64 *args, u32 nargs)
+{
+	return type == SPI_OFFLOAD_TRIGGER_DATA_READY &&
+	       nargs == 1 && args[0] <= 3;
+}
+
+static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
+					  enum spi_offload_trigger_type type,
+					  u64 *args, u32 nargs)
+{
+	struct ad4691_state *st = spi_offload_trigger_get_priv(trigger);
+
+	if (nargs != 1)
+		return -EINVAL;
+
+	return ad4691_gpio_setup(st, (unsigned int)args[0]);
+}
+
+static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger,
+					   struct spi_offload_trigger_config *config)
+{
+	if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = {
+	.match    = ad4691_offload_trigger_match,
+	.request  = ad4691_offload_trigger_request,
+	.validate = ad4691_offload_trigger_validate,
+};
+
 static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
 {
 	struct spi_device *spi = context;
@@ -559,10 +643,17 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
 	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
 		*val = st->osr[chan->scan_index];
 		return IIO_VAL_INT;
-	case IIO_CHAN_INFO_SCALE:
+	case IIO_CHAN_INFO_SCALE: {
+		const struct iio_scan_type *scan_type;
+
+		scan_type = iio_get_current_scan_type(indio_dev, chan);
+		if (IS_ERR(scan_type))
+			return PTR_ERR(scan_type);
+
 		*val = st->vref_uV / (MICRO / MILLI);
-		*val2 = chan->scan_type.realbits;
+		*val2 = scan_type->realbits;
 		return IIO_VAL_FRACTIONAL_LOG2;
+	}
 	default:
 		return -EINVAL;
 	}
@@ -866,6 +957,208 @@ static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
 	.postdisable = &ad4691_cnv_burst_buffer_postdisable,
 };
 
+static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct ad4691_offload_state *offload = st->offload;
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	struct spi_offload_trigger_config config = {
+		.type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+	};
+	unsigned int bit, k;
+	int ret;
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret)
+		return ret;
+
+	memset(offload->xfer, 0, sizeof(offload->xfer));
+
+	/*
+	 * N+1 transfers for N channels. Each CS-low period triggers
+	 * a conversion AND returns the previous result (pipelined).
+	 *   TX: [AD4691_ADC_CHAN(n), 0x00, 0x00, 0x00]
+	 *   RX: [data_hi, data_lo, 0x00, 0x00]   (shift=16)
+	 * Transfer 0 RX is garbage; transfers 1..N carry real data.
+	 */
+	k = 0;
+	iio_for_each_active_channel(indio_dev, bit) {
+		offload->tx_cmd[k][0] = AD4691_ADC_CHAN(bit);
+		offload->xfer[k].tx_buf = offload->tx_cmd[k];
+		offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+		offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+		offload->xfer[k].cs_change = 1;
+		offload->xfer[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS;
+		offload->xfer[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+		/* First transfer RX is garbage — skip it. */
+		if (k > 0)
+			offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+		k++;
+	}
+
+	/* Final NOOP to flush pipeline and capture last channel. */
+	offload->tx_cmd[k][0] = AD4691_NOOP;
+	offload->xfer[k].tx_buf = offload->tx_cmd[k];
+	offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+	offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+	offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+	k++;
+
+	spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
+	offload->msg.offload = offload->spi;
+
+	ret = spi_optimize_message(spi, &offload->msg);
+	if (ret)
+		goto err_exit_conversion;
+
+	config.periodic.frequency_hz = offload->trigger_hz;
+	ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
+	if (ret)
+		goto err_unoptimize;
+
+	return 0;
+
+err_unoptimize:
+	spi_unoptimize_message(&offload->msg);
+err_exit_conversion:
+	ad4691_exit_conversion_mode(st);
+	return ret;
+}
+
+static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct ad4691_offload_state *offload = st->offload;
+
+	spi_offload_trigger_disable(offload->spi, offload->trigger);
+	spi_unoptimize_message(&offload->msg);
+
+	return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setup_ops = {
+	.postenable = &ad4691_manual_offload_buffer_postenable,
+	.predisable = &ad4691_manual_offload_buffer_predisable,
+};
+
+static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct ad4691_offload_state *offload = st->offload;
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	struct spi_offload_trigger_config config = {
+		.type = SPI_OFFLOAD_TRIGGER_DATA_READY,
+	};
+	unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+					      indio_dev->masklength);
+	unsigned int bit, k;
+	int ret;
+
+	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+			   (u16)~bitmap_read(indio_dev->active_scan_mask, 0,
+					     indio_dev->masklength));
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   bitmap_read(indio_dev->active_scan_mask, 0,
+				       indio_dev->masklength));
+	if (ret)
+		return ret;
+
+	iio_for_each_active_channel(indio_dev, bit) {
+		ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
+				   st->osr[bit]);
+		if (ret)
+			return ret;
+	}
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret)
+		return ret;
+
+	memset(offload->xfer, 0, sizeof(offload->xfer));
+
+	/*
+	 * N transfers to read N AVG_IN registers plus one state-reset
+	 * transfer (no RX) to re-arm DATA_READY.
+	 *   TX: [reg_hi | 0x80, reg_lo, 0x00, 0x00]
+	 *   RX: [0x00, 0x00, data_hi, data_lo]   (shift=0)
+	 */
+	k = 0;
+	iio_for_each_active_channel(indio_dev, bit) {
+		unsigned int reg = AD4691_AVG_IN(bit);
+
+		offload->tx_cmd[k][0] = (reg >> 8) | 0x80;
+		offload->tx_cmd[k][1] = reg & 0xFF;
+		offload->xfer[k].tx_buf = offload->tx_cmd[k];
+		offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+		offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+		offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+		if (k < n_active - 1)
+			offload->xfer[k].cs_change = 1;
+		k++;
+	}
+
+	/* State reset to re-arm DATA_READY for the next scan. */
+	offload->tx_reset[0] = AD4691_STATE_RESET_REG >> 8;
+	offload->tx_reset[1] = AD4691_STATE_RESET_REG & 0xFF;
+	offload->tx_reset[2] = AD4691_STATE_RESET_ALL;
+	offload->xfer[k].tx_buf = offload->tx_reset;
+	offload->xfer[k].len = sizeof(offload->tx_reset);
+	offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+	k++;
+
+	spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
+	offload->msg.offload = offload->spi;
+
+	ret = spi_optimize_message(spi, &offload->msg);
+	if (ret)
+		goto err_exit_conversion;
+
+	ret = ad4691_sampling_enable(st, true);
+	if (ret)
+		goto err_unoptimize;
+
+	ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
+	if (ret)
+		goto err_sampling_disable;
+
+	return 0;
+
+err_sampling_disable:
+	ad4691_sampling_enable(st, false);
+err_unoptimize:
+	spi_unoptimize_message(&offload->msg);
+err_exit_conversion:
+	ad4691_exit_conversion_mode(st);
+	return ret;
+}
+
+static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct ad4691_offload_state *offload = st->offload;
+	int ret;
+
+	spi_offload_trigger_disable(offload->spi, offload->trigger);
+
+	ret = ad4691_sampling_enable(st, false);
+	if (ret)
+		return ret;
+
+	spi_unoptimize_message(&offload->msg);
+
+	return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_setup_ops = {
+	.postenable = &ad4691_cnv_burst_offload_buffer_postenable,
+	.predisable = &ad4691_cnv_burst_offload_buffer_predisable,
+};
+
 static ssize_t sampling_frequency_show(struct device *dev,
 				       struct device_attribute *attr,
 				       char *buf)
@@ -873,6 +1166,9 @@ static ssize_t sampling_frequency_show(struct device *dev,
 	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
 	struct ad4691_state *st = iio_priv(indio_dev);
 
+	if (st->manual_mode && st->offload)
+		return sysfs_emit(buf, "%llu\n", st->offload->trigger_hz);
+
 	return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
 }
 
@@ -890,6 +1186,20 @@ static ssize_t sampling_frequency_store(struct device *dev,
 
 	guard(mutex)(&st->lock);
 
+	if (st->manual_mode && st->offload) {
+		struct spi_offload_trigger_config config = {
+			.type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+			.periodic = { .frequency_hz = freq },
+		};
+
+		ret = spi_offload_trigger_validate(st->offload->trigger, &config);
+		if (ret)
+			return ret;
+
+		st->offload->trigger_hz = config.periodic.frequency_hz;
+		return len;
+	}
+
 	ret = ad4691_set_pwm_freq(st, freq);
 	if (ret)
 		return ret;
@@ -975,10 +1285,23 @@ static irqreturn_t ad4691_trigger_handler(int irq, void *p)
 	return IRQ_HANDLED;
 }
 
+static int ad4691_get_current_scan_type(const struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	if (!st->offload)
+		return AD4691_SCAN_TYPE_NORMAL;
+	if (st->manual_mode)
+		return AD4691_SCAN_TYPE_OFFLOAD_MANUAL;
+	return AD4691_SCAN_TYPE_OFFLOAD_CNV;
+}
+
 static const struct iio_info ad4691_info = {
 	.read_raw = &ad4691_read_raw,
 	.write_raw = &ad4691_write_raw,
 	.read_avail = &ad4691_read_avail,
+	.get_current_scan_type = &ad4691_get_current_scan_type,
 	.debugfs_reg_access = &ad4691_reg_access,
 };
 
@@ -1130,6 +1453,15 @@ static int ad4691_config(struct ad4691_state *st)
 	if (st->manual_mode)
 		return 0;
 
+	/*
+	 * In the offload CNV Burst path the GP pin is supplied by the trigger
+	 * consumer via #trigger-source-cells; gpio_setup is called from
+	 * ad4691_offload_trigger_request() instead. For the non-offload path
+	 * derive the pin from the first interrupt-names entry (e.g. "gp0").
+	 */
+	if (device_property_present(dev, "#trigger-source-cells"))
+		return 0;
+
 	for (gp_num = 0; gp_num < ARRAY_SIZE(ad4691_gp_names); gp_num++) {
 		if (fwnode_irq_get_byname(dev_fwnode(dev),
 					  ad4691_gp_names[gp_num]) > 0)
@@ -1210,9 +1542,75 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
 					       &ad4691_manual_buffer_setup_ops);
 }
 
+static int ad4691_setup_offload(struct iio_dev *indio_dev,
+				struct ad4691_state *st,
+				struct spi_offload *spi_offload)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	struct ad4691_offload_state *offload;
+	struct dma_chan *rx_dma;
+	int ret;
+
+	offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
+	if (!offload)
+		return -ENOMEM;
+
+	offload->spi = spi_offload;
+	st->offload = offload;
+
+	if (st->manual_mode) {
+		offload->trigger =
+			devm_spi_offload_trigger_get(dev, offload->spi,
+						     SPI_OFFLOAD_TRIGGER_PERIODIC);
+		if (IS_ERR(offload->trigger))
+			return dev_err_probe(dev, PTR_ERR(offload->trigger),
+					     "Failed to get periodic offload trigger\n");
+
+		offload->trigger_hz = st->info->max_rate;
+	} else {
+		struct spi_offload_trigger_info trigger_info = {
+			.fwnode = dev_fwnode(dev),
+			.ops    = &ad4691_offload_trigger_ops,
+			.priv   = st,
+		};
+
+		ret = devm_spi_offload_trigger_register(dev, &trigger_info);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to register offload trigger\n");
+
+		offload->trigger =
+			devm_spi_offload_trigger_get(dev, offload->spi,
+						     SPI_OFFLOAD_TRIGGER_DATA_READY);
+		if (IS_ERR(offload->trigger))
+			return dev_err_probe(dev, PTR_ERR(offload->trigger),
+					     "Failed to get DATA_READY offload trigger\n");
+	}
+
+	rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, offload->spi);
+	if (IS_ERR(rx_dma))
+		return dev_err_probe(dev, PTR_ERR(rx_dma),
+				     "Failed to get offload RX DMA channel\n");
+
+	if (st->manual_mode)
+		indio_dev->setup_ops = &ad4691_manual_offload_buffer_setup_ops;
+	else
+		indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops;
+
+	ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
+							  IIO_BUFFER_DIRECTION_IN);
+	if (ret)
+		return ret;
+
+	indio_dev->buffer->attrs = ad4691_buffer_attrs;
+
+	return 0;
+}
+
 static int ad4691_probe(struct spi_device *spi)
 {
 	struct device *dev = &spi->dev;
+	struct spi_offload *spi_offload;
 	struct iio_dev *indio_dev;
 	struct ad4691_state *st;
 	int ret;
@@ -1247,6 +1645,13 @@ static int ad4691_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	spi_offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
+	ret = PTR_ERR_OR_ZERO(spi_offload);
+	if (ret == -ENODEV)
+		spi_offload = NULL;
+	else if (ret)
+		return dev_err_probe(dev, ret, "Failed to get SPI offload\n");
+
 	indio_dev->name = st->info->name;
 	indio_dev->info = &ad4691_info;
 	indio_dev->modes = INDIO_DIRECT_MODE;
@@ -1254,7 +1659,10 @@ static int ad4691_probe(struct spi_device *spi)
 	indio_dev->channels = st->info->channels;
 	indio_dev->num_channels = st->info->num_channels;
 
-	ret = ad4691_setup_triggered_buffer(indio_dev, st);
+	if (spi_offload)
+		ret = ad4691_setup_offload(indio_dev, st, spi_offload);
+	else
+		ret = ad4691_setup_triggered_buffer(indio_dev, st);
 	if (ret)
 		return ret;
 
@@ -1292,3 +1700,5 @@ module_spi_driver(ad4691_driver);
 MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
 MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMA_BUFFER");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");

-- 
2.43.0



^ permalink raw reply related

* [PATCH v5 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family
From: Radu Sabau via B4 Relay @ 2026-03-27 11:07 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc, Radu Sabau

This series adds support for the Analog Devices AD4691 family of
high-speed, low-power multichannel successive approximation register
(SAR) ADCs with an SPI-compatible serial interface.

The family includes:
  - AD4691: 16-channel, 500 kSPS
  - AD4692: 16-channel, 1 MSPS
  - AD4693: 8-channel, 500 kSPS
  - AD4694: 8-channel, 1 MSPS

The devices support two operating modes, auto-detected from the device
tree:
  - CNV Burst Mode: external PWM drives CNV independently of SPI;
                    DATA_READY on a GP pin signals end of conversion
  - Manual Mode: CNV tied to SPI CS; each SPI transfer reads
                 the previous conversion result and starts the
                 next (pipelined N+1 scheme)

A new driver is warranted rather than extending ad4695: the AD4691
data path uses an accumulator-register model — results are read from
AVG_IN registers, with ACC_MASK, ADC_SETUP, DEVICE_SETUP, and
GPIO_MODE registers controlling the sequencer — none of which exist
in AD4695. CNV Burst Mode (PWM drives CNV independently of SPI) and
Manual Mode (pipelined N+1 transfers) also have no equivalent in
AD4695's command-embedded single-cycle protocol.

The series is structured as follows:
  1/4 - DT bindings (YAML schema) and MAINTAINERS entry
  2/4 - Initial driver: register map via custom regmap callbacks,
        IIO read_raw/write_raw, both operating modes, single-channel
        reads via internal oscillator (Autonomous Mode)
  3/4 - Triggered buffer support: IRQ-driven (DATA_READY on a GP pin
        selected via interrupt-names) for CNV Burst Mode; external IIO
        trigger for Manual Mode to handle the pipelined N+1 SPI protocol
  4/4 - SPI Engine offload support: DMA-backed high-throughput
        capture path using the SPI offload subsystem

Datasheets:
  https://www.analog.com/en/products/ad4691.html
  https://www.analog.com/en/products/ad4692.html
  https://www.analog.com/en/products/ad4693.html
  https://www.analog.com/en/products/ad4694.html

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
Changes in v5:
- Reorder datasheets numerically
- Fix interrupt-names: use enum with minItems/maxItems
- Remove if/then block requiring interrupts — driver detail, not hardware constraint
- Remove redundant .shift = 0 from channel macro
- Write max_rate comparison as 1 * HZ_PER_MHZ
- Invert set_sampling_freq loop to use continue
- Fix fsleep() line break; remove blank line in read_raw
- Reorder supply init: vio immediately after avdd
- Move comment rewrites and OSC_FREQ_REG condition into the base driver patch
- Add bit-15 READ comment in reg_read
- Rewrite ldo-in handling with cleaner if/else-if pattern
- Drop redundant refbuf_en = false; invert if (!rst) in reset
- Drop reset_control_assert() — GPIO already asserted at probe
- Use regmap_update_bits/assign_bits in config
- Remove tab-column alignment of state struct members
- Declare osc_freqs[] as const int, eliminating explicit casts
- Drop obvious AUTONOMOUS mode comment
- Rename ACC_COUNT_LIMIT → ACC_DEPTH_IN to match datasheet
- Use bitmap_weight()/bitmap_read() for active_scan_mask access;
  add #include <linux/bitmap.h>
- Fix channel macro line-continuation tab alignment
- Use IIO_CHAN_SOFT_TIMESTAMP(8) for 8-channel variants
- Use aligned_s64 ts in scan struct
- Add comment explaining start-index removal in set_sampling_freq
- Remove trailing comma after NULL in buffer_attrs[]
- Add IRQF_NO_AUTOEN rationale comment
- Remove unreachable manual_mode guards in sampling_frequency_show/store
- Remove st->trig; use indio_dev->trig directly
- Move max_speed_hz param to the offload patch where it is used
- Use DIV_ROUND_UP for CNV period; use compound pwm_state initializer
- Move offload fields into a separately allocated sub-struct
- Build TX words via u8* byte-fill; fixes sparse __be32 warnings
- Add three scan types (NORMAL/OFFLOAD_CNV/OFFLOAD_MANUAL) with
  get_current_scan_type; triggered buffer path uses storagebits=16
- Fix IIO_CHAN_INFO_SCALE: use iio_get_current_scan_type() for realbits
- Add MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER")
- Add Documentation/iio/ad4691.rst
- Link to v4: https://lore.kernel.org/r/20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com

Changes in v4:
- dt-bindings: add avdd-supply (required) and ldo-in-supply (optional);
  rename vref-supply → ref-supply, vrefin-supply → refin-supply;
  corrected reset-gpios polarity (active-high → active-low); remove
  clocks and pwm-names; extend interrupts to up to 4 GP pins with
  interrupt-names "gp0".."gp3"; reduce #trigger-source-cells to
  const: 1 (GP pin number); add gpio-controller / #gpio-cells = <2>;
  drop adi,ad4691.h header; update binding examples
- driver: rename CNV Clock Mode → CNV Burst Mode throughout
- driver: add avdd-supply (required) and ldo-in-supply; track ref vs.
  refin supply for REFBUF_EN; set LDO_EN in DEVICE_SETUP when ldo-in
  is present; add software reset fallback via SPI_CONFIG_A register
- driver: merge ACC_MASK1_REG / ACC_MASK2_REG into ACC_MASK_REG with
  a single ADDR_DESCENDING 16-bit SPI write
- driver: remove clocks usage; set PWM rate directly without ref clock
- driver: rename chip info structs (ad4691_chip_info etc.); rename
  *chip → *info in state struct; replace adc_mode enum with manual_mode
  bool; replace ktime sampling_period with u32 cnv_period_ns
- driver: move IIO_CHAN_INFO_SAMP_FREQ to info_mask_separate with an
  available list for the internal oscillator frequency
- driver: use regcache MAPLE instead of RBTREE
- triggered buffer: derive DATA_READY GP pin from interrupt-names in
  firmware ("gp0".."gp3") instead of assuming GP0
- triggered buffer: use regmap_update_bits for DEVICE_SETUP mode toggle
  to avoid clobbering LDO_EN when toggling MANUAL_MODE bit
- triggered buffer: split buffer setup ops into separate Manual and
  CNV Burst variants (mirrors offload path structure)
- SPI offload: promote channel storagebits from 16 to 32 to match DMA
  word size; introduce ad4691_manual_channels[] with shift=16 (data in
  upper 16 bits of the 32-bit word); update triggered-buffer paths to
  the same layout for consistency
- SPI offload: derive GP pin from trigger-source args[0] instead of
  hardcoding GP0; split offload buffer setup ops per mode
- replace put_unaligned_be32() + FIELD_PREP() with cpu_to_be32() and
  plain bit-shift ops for SPI offload message construction
- multiple reviewer-requested code style and correctness fixes
  (Andy Shevchenko, Nuno Sá, Uwe Kleine-König, David Lechner)
- Link to v3: https://lore.kernel.org/r/20260313-ad4692-multichannel-sar-adc-driver-v3-0-b4d14d81a181@analog.com

Changes in v3:
- Replace GPIO reset handling with reset controller framework
- Replace two regmap_write() calls for ACC_MASK1/ACC_MASK2 with regmap_bulk_write()
- Move conv_us declaration closer to its first use
- Derive spi_device/dev from regmap instead of storing st->spi
- ad4691_trigger_handler(): use guard(mutex)() and iio_for_each_active_channel()
- ad4691_setup_triggered_buffer(): return -ENOMEM/-ENOENT directly instead of
  wrapping in dev_err_probe(); fix fwnode_irq_get() check (irq <= 0 → irq < 0)
- Add GENMASK defines for SPI offload 32-bit message layout; replace manual
  bit-shifts with put_unaligned_be32() + FIELD_PREP()
- Use DIV_ROUND_CLOSEST_ULL() instead of div64_u64()
- ad4691_set_sampling_freq(): fix indentation; drop unnecessary else after return
- ad4691_probe(): use PTR_ERR_OR_ZERO() for devm_spi_offload_get()
- Link to v2: https://lore.kernel.org/r/20260310-ad4692-multichannel-sar-adc-driver-v2-0-d9bb8aeb5e17@analog.com

Changes in v2:
- Drop adi,spi-mode DT property; operating mode now auto-detected
  from pwms presence (CNV Clock Mode if present, Manual Mode if not)
- Reduce from 5 operating modes to 2 (CNV Clock Mode, Manual Mode);
  Autonomous, SPI Burst and CNV Burst modes removed as user-selectable
  modes; Autonomous Mode is now the internal idle/single-shot state
- Single-shot read_raw always uses internal oscillator (Autonomous
  Mode), independent of the configured buffer mode
- Replace bulk regulator API with devm_regulator_get_enable() and
  devm_regulator_get_enable_read_voltage()
- Use guard(mutex) and IIO_DEV_ACQUIRE_DIRECT_MODE scoped helpers
- Replace enum + indexed chip_info array with named chip_info structs
- Remove product_id field and hardware ID check from probe
- Factor IIO_CHAN_INFO_RAW body into ad4691_single_shot_read() helper
- Use fwnode_irq_get(dev_fwnode(dev), 0); drop interrupt-names from
  DT binding
- Use devm_clk_get_enabled(dev, NULL); drop clock-names from DT
  binding
- Use spi_write_then_read() for DMA-safe register writes
- Use put_unaligned_be16() for SPI header construction
- fsleep() instead of usleep_range() in single-shot path
- storagebits 24->32 for manual-mode channels (uniform DMA layout)
- Collect full scan into vals[16], single iio_push_to_buffers_with_ts()
- Use pf->timestamp instead of iio_get_time_ns() in trigger handler
- Remove IRQF_TRIGGER_FALLING (comes from firmware/DT)
- Fix offload xfer array size ([17]: N channels + 1 state reset)
- Drop third DT binding example per reviewer request
- Link to v1: https://lore.kernel.org/r/20260305-ad4692-multichannel-sar-adc-driver-v1-0-336229a8dcc7@analog.com

---
Radu Sabau (4):
      dt-bindings: iio: adc: add AD4691 family
      iio: adc: ad4691: add initial driver for AD4691 family
      iio: adc: ad4691: add triggered buffer support
      iio: adc: ad4691: add SPI offload support

 .../devicetree/bindings/iio/adc/adi,ad4691.yaml    |  162 ++
 Documentation/iio/ad4691.rst                       |  259 +++
 Documentation/iio/index.rst                        |    1 +
 MAINTAINERS                                        |    9 +
 drivers/iio/adc/Kconfig                            |   14 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/ad4691.c                           | 1704 ++++++++++++++++++++
 7 files changed, 2150 insertions(+)
---
base-commit: 11439c4635edd669ae435eec308f4ab8a0804808
change-id: 20260302-ad4692-multichannel-sar-adc-driver-78e4d44d24b2

Best regards,
-- 
Radu Sabau <radu.sabau@analog.com>



^ permalink raw reply

* [PATCH v5 1/4] dt-bindings: iio: adc: add AD4691 family
From: Radu Sabau via B4 Relay @ 2026-03-27 11:07 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc, Radu Sabau
In-Reply-To: <20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com>

From: Radu Sabau <radu.sabau@analog.com>

Add DT bindings for the Analog Devices AD4691 family of multichannel
SAR ADCs (AD4691, AD4692, AD4693, AD4694).

The binding describes the hardware connections:

- Power domains: avdd-supply (required), vio-supply, ref-supply or
  refin-supply (external reference; the REFIN path enables the
  internal reference buffer), and an optional ldo-in-supply, that if
  absent, means the on-chip internal LDO will be used.

- Optional PWM on the CNV pin selects CNV Burst Mode; when absent,
  Manual Mode is assumed with CNV tied to SPI CS.

- An optional reset GPIO (reset-gpios) for hardware reset.

- Up to four GP pins (gp0..gp3) usable as interrupt sources,
  identified in firmware via interrupt-names "gp0".."gp3".

- gpio-controller with #gpio-cells = <2> for GP pin GPIO usage.

- #trigger-source-cells = <1>: one cell selecting the GP pin number
  (0-3) used as the SPI offload trigger source.

Two binding examples are provided: CNV Burst Mode with SPI offload
(DMA data acquisition driven by DATA_READY on a GP pin), and Manual
Mode for CPU-driven triggered-buffer or single-shot capture.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 .../devicetree/bindings/iio/adc/adi,ad4691.yaml    | 162 +++++++++++++++++++++
 MAINTAINERS                                        |   7 +
 2 files changed, 169 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
new file mode 100644
index 000000000000..81d2ca4e0e22
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.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/adi,ad4691.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD4691 Family Multichannel SAR ADCs
+
+maintainers:
+  - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+  The AD4691 family are high-speed, low-power, multichannel successive
+  approximation register (SAR) analog-to-digital converters (ADCs) with
+  an SPI-compatible serial interface. The ADC supports CNV Burst Mode,
+  where an external PWM drives the CNV pin, and Manual Mode, where CNV
+  is directly tied to the SPI chip-select.
+
+  Datasheets:
+    * https://www.analog.com/en/products/ad4691.html
+    * https://www.analog.com/en/products/ad4692.html
+    * https://www.analog.com/en/products/ad4693.html
+    * https://www.analog.com/en/products/ad4694.html
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    enum:
+      - adi,ad4691
+      - adi,ad4692
+      - adi,ad4693
+      - adi,ad4694
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 40000000
+
+  spi-cpol: true
+  spi-cpha: true
+
+  avdd-supply:
+    description: Analog power supply (4.5V to 5.5V).
+
+  ldo-in-supply:
+    description: LDO input supply. When absent, the internal LDO is used.
+
+  vio-supply:
+    description: I/O voltage supply (1.71V to 1.89V or VDD).
+
+  ref-supply:
+    description: External reference voltage supply (2.4V to 5.25V).
+
+  refin-supply:
+    description: Internal reference buffer input supply.
+
+  reset-gpios:
+    description:
+      GPIO line controlling the hardware reset pin (active-low).
+    maxItems: 1
+
+  pwms:
+    description:
+      PWM connected to the CNV pin. When present, selects CNV Burst Mode where
+      the PWM drives the conversion rate. When absent, Manual Mode is used
+      (CNV tied to SPI CS).
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt lines connected to the ADC GP pins. Each GP pin can be
+      physically wired to an interrupt-capable input on the SoC.
+    maxItems: 4
+
+  interrupt-names:
+    description: Names of the interrupt lines, matching the GP pin names.
+    minItems: 1
+    maxItems: 4
+    items:
+      enum:
+        - gp0
+        - gp1
+        - gp2
+        - gp3
+
+  gpio-controller: true
+
+  '#gpio-cells':
+    const: 2
+
+  '#trigger-source-cells':
+    description:
+      This node can act as a trigger source. The single cell in a consumer
+      reference specifies the GP pin number (0-3) used as the trigger output.
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - avdd-supply
+  - vio-supply
+
+allOf:
+  # ref-supply and refin-supply are mutually exclusive, one is required
+  - oneOf:
+      - required:
+          - ref-supply
+      - required:
+          - refin-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    /* AD4692 in CNV Burst Mode with SPI offload */
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "adi,ad4692";
+            reg = <0>;
+            spi-cpol;
+            spi-cpha;
+            spi-max-frequency = <40000000>;
+
+            avdd-supply = <&avdd_supply>;
+            vio-supply = <&vio_supply>;
+            ref-supply = <&ref_5v>;
+
+            reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
+
+            pwms = <&pwm_gen 0 0>;
+
+            #trigger-source-cells = <1>;
+        };
+    };
+
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    /* AD4692 in Manual Mode (CNV tied to SPI CS) */
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "adi,ad4692";
+            reg = <0>;
+            spi-cpol;
+            spi-cpha;
+            spi-max-frequency = <31250000>;
+
+            avdd-supply = <&avdd_supply>;
+            vio-supply = <&vio_supply>;
+            refin-supply = <&refin_supply>;
+
+            reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..438ca850fa1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1484,6 +1484,13 @@ W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ad4170-4.yaml
 F:	drivers/iio/adc/ad4170-4.c
 
+ANALOG DEVICES INC AD4691 DRIVER
+M:	Radu Sabau <radu.sabau@analog.com>
+L:	linux-iio@vger.kernel.org
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+
 ANALOG DEVICES INC AD4695 DRIVER
 M:	Michael Hennerich <michael.hennerich@analog.com>
 M:	Nuno Sá <nuno.sa@analog.com>

-- 
2.43.0



^ permalink raw reply related

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Andy Shevchenko @ 2026-03-27 10:57 UTC (permalink / raw)
  To: David Laight
  Cc: Petr Mladek, rodrigo.alencar, linux-kernel, linux-iio, devicetree,
	linux-doc, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Lars-Peter Clausen, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
	Steven Rostedt, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260327104440.079343c9@pumpkin>

On Fri, Mar 27, 2026 at 10:44:40AM +0000, David Laight wrote:
> On Fri, 27 Mar 2026 11:17:16 +0200
> Andy Shevchenko <andriy.shevchenko@linux.intel.com> wrote:

...

> > TBH, I am skeptical about this approach. My main objection is max_chars
> > parameter. If we want to limit the input strictly to the given number of
> > characters, we have to copy the string and then just use kstrto*() in a normal
> > way. The whole idea of that parameter is to be able to parse the fractional
> > part of the float number as 'iiiii.fffff', where 'i' is for integer part, and
> > 'f' for the fractional. Since we have *endp, we may simply check that.
> > 
> > In case if we want to parse only, say, 6 digits and input is longer there are
> > a few options (in my personal preferences, the first is the better):
> > - consider the input invalid
> > - parse it as is up to the maximum and then do ceil() or floor() on top of that
> > - copy only necessary amount of the (sub)string and parse that.
> 
> Isn't there a bigger problem?
> If you want a max of 6 digits you need to correctly parse 3.1 3.159265
> 3.159256358979 3.0001 3.000159 3.00015926535 3.000100 (etc).
> That seems to always require checking the length and then multiply/divide
> by 10.

Yep.

> Then there is 'round to even' which rounds these two in opposite directions:
>    4.500000000000000000000000000000000000000000000000000
>    4.500000000000000000000000000000000000000000000000001

These are wrong inputs and if we want to have them cut, it will be just a cut.
(Yeah, which will have different result for negative numbers.)

> I suspect you really want a completely different function for reading
> fractional parts of floating point numbers.
> It isn't as though the actual digit conversion is hard.
> 
> > The problem with precision is that we need to also consider floor() or ceil()
> > and I don't think this should be burden of the library as it's individual
> > preference of each of the callers (users). At least for the starter, we will
> > see if it's only one approach is used, we may incorporate it into the library
> > code.
> > 
> > The easiest way out is to just consider the input invalid if it overflows the
> > given type (s32 or s64).
> > 
> > But we need to have an agreement what will be the representation of the
> > fixed-width float numbers in the kernel? Currently IIO uses
> > 	struct float // name is crafted for simplicity
> > 	{
> > 		int integer;
> > 		int fraction;
> > 	}
> > 
> > This parser wants AFAIU to have at the end of the day something like
> > 
> > 	struct float
> > 	{
> > 		s64 integer;
> > 		s64 fraction;
> > 	}
> > 
> > but also wants to have the fraction part be limited in some cases to s32
> > or so:
> > 
> > 	struct float
> > 	{
> > 		s64 integer;
> > 		s32 fraction; // precision may be lost if input is longer
> > 	}
> 
> Are those 'fraction' counts of (say) 10^-6 (like times in seconds+usecs)
> or true binary values where the value could be treated as a u64 (or u128)
> for addition and subtraction.

It depends. IIO has scale on top of that, so the fraction part can be 10⁻³,
10⁻⁶, 10⁻⁹. I don't remember by heart if the ABI requires all digits to be
placed, I think we don't require that.

> So parse the latter you don't need to know the length
> (and it can be converted the to former by multiplying by 10^6).
> 
> > Maybe we want to have kstrtof32() and kstrtof64() for these two cases?
> > 
> > With that we will always consider the fraction part as 32- or 64-bit,
> > imply floor() on the fraction for the sake of simplicity and require
> > it to be NUL-terminated with possible trailing '\n'.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: David Laight @ 2026-03-27 10:44 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Petr Mladek, rodrigo.alencar, linux-kernel, linux-iio, devicetree,
	linux-doc, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Lars-Peter Clausen, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
	Steven Rostedt, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <acZLHAT5qJyjKTsp@ashevche-desk.local>

On Fri, 27 Mar 2026 11:17:16 +0200
Andy Shevchenko <andriy.shevchenko@linux.intel.com> wrote:

> On Fri, Mar 27, 2026 at 09:45:17AM +0100, Petr Mladek wrote:
> > On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:  
> 
> ...
> 
> > > +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> > > +					     unsigned int base, size_t max_chars,
> > > +					     unsigned long long *res);  
> > 
> > Sigh, naming is hard. I personally find it a bit confusing that the
> > name is too similar to the unsafe API.
> > 
> > IMHO, the semantic of the new API is closer to kstrtoull().
> > It just limits the size, so I would call it kstrntoull().  
> 
> It's not. kstrto*() quite strict about the input, this one is actually relaxed
> variant, so I wouldn't mix these two groups.
> 
> > Also I would use int as the return parameter, see below.  
> 
> ...
> 
> TBH, I am skeptical about this approach. My main objection is max_chars
> parameter. If we want to limit the input strictly to the given number of
> characters, we have to copy the string and then just use kstrto*() in a normal
> way. The whole idea of that parameter is to be able to parse the fractional
> part of the float number as 'iiiii.fffff', where 'i' is for integer part, and
> 'f' for the fractional. Since we have *endp, we may simply check that.
> 
> In case if we want to parse only, say, 6 digits and input is longer there are
> a few options (in my personal preferences, the first is the better):
> - consider the input invalid
> - parse it as is up to the maximum and then do ceil() or floor() on top of that
> - copy only necessary amount of the (sub)string and parse that.

Isn't there a bigger problem?
If you want a max of 6 digits you need to correctly parse 3.1 3.159265
3.159256358979 3.0001 3.000159 3.00015926535 3.000100 (etc).
That seems to always require checking the length and then multiply/divide
by 10.

Then there is 'round to even' which rounds these two in opposite directions:
   4.500000000000000000000000000000000000000000000000000
   4.500000000000000000000000000000000000000000000000001

I suspect you really want a completely different function for reading
fractional parts of floating point numbers.
It isn't as though the actual digit conversion is hard.

> 
> The problem with precision is that we need to also consider floor() or ceil()
> and I don't think this should be burden of the library as it's individual
> preference of each of the callers (users). At least for the starter, we will
> see if it's only one approach is used, we may incorporate it into the library
> code.
> 
> The easiest way out is to just consider the input invalid if it overflows the
> given type (s32 or s64).
> 
> But we need to have an agreement what will be the representation of the
> fixed-width float numbers in the kernel? Currently IIO uses
> 	struct float // name is crafted for simplicity
> 	{
> 		int integer;
> 		int fraction;
> 	}
> 
> This parser wants AFAIU to have at the end of the day something like
> 
> 	struct float
> 	{
> 		s64 integer;
> 		s64 fraction;
> 	}
> 
> but also wants to have the fraction part be limited in some cases to s32
> or so:
> 
> 	struct float
> 	{
> 		s64 integer;
> 		s32 fraction; // precision may be lost if input is longer
> 	}

Are those 'fraction' counts of (say) 10^-6 (like times in seconds+usecs)
or true binary values where the value could be treated as a u64 (or u128)
for addition and subtraction.
So parse the latter you don't need to know the length
(and it can be converted the to former by multiplying by 10^6).

	David

> 
> Maybe we want to have kstrtof32() and kstrtof64() for these two cases?
> 
> With that we will always consider the fraction part as 32- or 64-bit,
> imply floor() on the fraction for the sake of simplicity and require
> it to be NUL-terminated with possible trailing '\n'.
> 


^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Andy Shevchenko @ 2026-03-27 10:21 UTC (permalink / raw)
  To: Rodrigo Alencar
  Cc: Petr Mladek, rodrigo.alencar, linux-kernel, linux-iio, devicetree,
	linux-doc, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Lars-Peter Clausen, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
	Steven Rostedt, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <4uijfg4efzaapu3esobez55hfwqzszwagpeb4lxej2ybmifu76@s3c57fmnsme6>

On Fri, Mar 27, 2026 at 10:11:56AM +0000, Rodrigo Alencar wrote:
> On 26/03/27 11:17AM, Andy Shevchenko wrote:
> > On Fri, Mar 27, 2026 at 09:45:17AM +0100, Petr Mladek wrote:
> > > On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:

...

> > > > +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> > > > +					     unsigned int base, size_t max_chars,
> > > > +					     unsigned long long *res);
> > > 
> > > Sigh, naming is hard. I personally find it a bit confusing that the
> > > name is too similar to the unsafe API.
> > > 
> > > IMHO, the semantic of the new API is closer to kstrtoull().
> > > It just limits the size, so I would call it kstrntoull().
> > 
> > It's not. kstrto*() quite strict about the input, this one is actually relaxed
> > variant, so I wouldn't mix these two groups.
> > 
> > > Also I would use int as the return parameter, see below.

...

> > TBH, I am skeptical about this approach. My main objection is max_chars
> > parameter. If we want to limit the input strictly to the given number of
> > characters, we have to copy the string and then just use kstrto*() in a normal
> > way. The whole idea of that parameter is to be able to parse the fractional
> > part of the float number as 'iiiii.fffff', where 'i' is for integer part, and
> > 'f' for the fractional. Since we have *endp, we may simply check that.
> 
> A max_chars would not be only useful for that. It can prevent out-of-bounds
> reads when the input isn't NUL-terminated (like buffers, file chunks,
> network packets, memory-mapped data, ....). Even if there is a NUL later in
> memory, a regular strtoull() function may consume characters that are outside
> the field one intends to parse.

Okay, but is it the current case or just an attempt to solve the problem that
doesn't exist (yet)?

> > In case if we want to parse only, say, 6 digits and input is longer there are
> > a few options (in my personal preferences, the first is the better):
> > - consider the input invalid
> > - parse it as is up to the maximum and then do ceil() or floor() on top of that
> > - copy only necessary amount of the (sub)string and parse that.
> 
> Yes, my use case is the fixed point parsing, but I suppose we are implementing
> things here for reuse.

Yes, I'm full for reuse, but I want to have it balanced between complexity,
existing use cases and possible reuse in the future.

> Also, the default behavior of the previous fixed point
> parsing in IIO is flooring the result, which leads to the same result as
> ignoring further digits.

Correct, I also lean to implying floor() (as you can read below).

> > The problem with precision is that we need to also consider floor() or ceil()
> > and I don't think this should be burden of the library as it's individual
> > preference of each of the callers (users). At least for the starter, we will
> > see if it's only one approach is used, we may incorporate it into the library
> > code.
> > 
> > The easiest way out is to just consider the input invalid if it overflows the
> > given type (s32 or s64).
> > 
> > But we need to have an agreement what will be the representation of the
> > fixed-width float numbers in the kernel? Currently IIO uses
> > 	struct float // name is crafted for simplicity
> > 	{
> > 		int integer;
> > 		int fraction;
> > 	}
> 
> Yes, but to represent things like that, an assumption is made to the precision that
> "fraction" carries.

Correct.

> > This parser wants AFAIU to have at the end of the day something like
> > 
> > 	struct float
> > 	{
> > 		s64 integer;
> > 		s64 fraction;
> > 	}
> > 
> > but also wants to have the fraction part be limited in some cases to s32
> > or so:
> > 
> > 	struct float
> > 	{
> > 		s64 integer;
> > 		s32 fraction; // precision may be lost if input is longer
> > 	}
> > 
> > Maybe we want to have kstrtof32() and kstrtof64() for these two cases?
> > 
> > With that we will always consider the fraction part as 32- or 64-bit,
> > imply floor() on the fraction for the sake of simplicity and require
> > it to be NUL-terminated with possible trailing '\n'.
> 
> I think this is a good idea, but calling it float or fixed point itself
> is a bit confusing as float often refers to the IEEE 754 standard and
> fixed point types is often expressed in Q-format.

Yeah... I am lack of better naming.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v2] bootconfig: Apply early options from embedded config
From: Breno Leitao @ 2026-03-27 10:18 UTC (permalink / raw)
  To: Masami Hiramatsu
  Cc: Jonathan Corbet, Shuah Khan, linux-kernel, linux-trace-kernel,
	linux-doc, oss, paulmck, rostedt, kernel-team
In-Reply-To: <20260326233042.f52cfc127ec934d52713bce1@kernel.org>

On Thu, Mar 26, 2026 at 11:30:42PM +0900, Masami Hiramatsu wrote:
> On Wed, 25 Mar 2026 23:22:04 +0900
> Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
>
> > > +	/*
> > > +	 * Keys that do not match any early_param() handler are silently
> > > +	 * ignored — do_early_param() always returns 0.
> > > +	 */
> > > +	xbc_node_for_each_key_value(root, knode, val) {
> >
> > [sashiko comment]
> > | Does this loop handle array values correctly?
> > | xbc_node_for_each_key_value() only assigns the first value of an array to
> > | the val pointer before advancing to the next key. It does not iterate over
> > | the child nodes of the array.
> > | If the bootconfig contains a multi-value key like
> > | kernel.console = "ttyS0", "tty0", will the subsequent values in the array
> > | be silently dropped instead of passed to the early_param handlers?
> >
> > Also, good catch :) we need to use xbc_node_for_each_array_value()
> > for inner loop.
>
> FYI, xbc_snprint_cmdline() translates the arraied parameter as
> multiple parameters. For example,
>
> foo = bar, buz;
>
> will be converted to
>
> foo=bar foo=buz
>
> Thus, I think we should do the same thing below;
>
> >
> > > +		if (xbc_node_compose_key_after(root, knode, xbc_namebuf, XBC_KEYLEN_MAX) < 0)
> > > +			continue;
> > > +
> > > +		/*
> > > +		 * We need to copy const char *val to a char pointer,
> > > +		 * which is what do_early_param() need, given it might
> > > +		 * call strsep(), strtok() later.
> > > +		 */
> > > +		ret = strscpy(val_buf, val, sizeof(val_buf));
> > > +		if (ret < 0) {
> > > +			pr_warn("ignoring bootconfig value '%s', too long\n",
> > > +				xbc_namebuf);
> > > +			continue;
> > > +		}
> > > +		do_early_param(xbc_namebuf, val_buf, NULL, NULL);
>
> So instead of this;
>
> xbc_array_for_each_value(vnode, val) {
> 	do_early_param(xbc_namebuf, val, NULL, NULL);
> }
>
> Maybe it is a good timing to recondier unifying kernel cmdline and bootconfig
> from API viewpoint.

I'm not familiar with the history on this topic. Has unifying the APIs been
previously considered and set aside?

Given all the feedback on this series, I see three types of issues to address:

1) Minor patch improvements
2) Architecture-specific super early parameters being parsed before bootconfig
   is available
3) Unifying kernel cmdline and bootconfig interfaces

Which of these areas would you recommend I prioritize?

Thanks for the guidance,
--breno

^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Rodrigo Alencar @ 2026-03-27 10:11 UTC (permalink / raw)
  To: Andy Shevchenko, Petr Mladek
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	Jonathan Cameron, David Lechner, Andy Shevchenko,
	Lars-Peter Clausen, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
	Steven Rostedt, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <acZLHAT5qJyjKTsp@ashevche-desk.local>

On 26/03/27 11:17AM, Andy Shevchenko wrote:
> On Fri, Mar 27, 2026 at 09:45:17AM +0100, Petr Mladek wrote:
> > On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:
> 
> ...
> 
> > > +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> > > +					     unsigned int base, size_t max_chars,
> > > +					     unsigned long long *res);
> > 
> > Sigh, naming is hard. I personally find it a bit confusing that the
> > name is too similar to the unsafe API.
> > 
> > IMHO, the semantic of the new API is closer to kstrtoull().
> > It just limits the size, so I would call it kstrntoull().
> 
> It's not. kstrto*() quite strict about the input, this one is actually relaxed
> variant, so I wouldn't mix these two groups.
> 
> > Also I would use int as the return parameter, see below.
> 
> ...
> 
> TBH, I am skeptical about this approach. My main objection is max_chars
> parameter. If we want to limit the input strictly to the given number of
> characters, we have to copy the string and then just use kstrto*() in a normal
> way. The whole idea of that parameter is to be able to parse the fractional
> part of the float number as 'iiiii.fffff', where 'i' is for integer part, and
> 'f' for the fractional. Since we have *endp, we may simply check that.

A max_chars would not be only useful for that. It can prevent out-of-bounds
reads when the input isn't NUL-terminated (like buffers, file chunks,
network packets, memory-mapped data, ....). Even if there is a NUL later in
memory, a regular strtoull() function may consume characters that are outside
the field one intends to parse.
 
> In case if we want to parse only, say, 6 digits and input is longer there are
> a few options (in my personal preferences, the first is the better):
> - consider the input invalid
> - parse it as is up to the maximum and then do ceil() or floor() on top of that
> - copy only necessary amount of the (sub)string and parse that.

Yes, my use case is the fixed point parsing, but I suppose we are implementing
things here for reuse. Also, the default behavior of the previous fixed point
parsing in IIO is flooring the result, which leads to the same result as
ignoring further digits.

> The problem with precision is that we need to also consider floor() or ceil()
> and I don't think this should be burden of the library as it's individual
> preference of each of the callers (users). At least for the starter, we will
> see if it's only one approach is used, we may incorporate it into the library
> code.
> 
> The easiest way out is to just consider the input invalid if it overflows the
> given type (s32 or s64).
> 
> But we need to have an agreement what will be the representation of the
> fixed-width float numbers in the kernel? Currently IIO uses
> 	struct float // name is crafted for simplicity
> 	{
> 		int integer;
> 		int fraction;
> 	}

Yes, but to represent things like that, an assumption is made to the precision that
"fraction" carries.

> 
> This parser wants AFAIU to have at the end of the day something like
> 
> 	struct float
> 	{
> 		s64 integer;
> 		s64 fraction;
> 	}
> 
> but also wants to have the fraction part be limited in some cases to s32
> or so:
> 
> 	struct float
> 	{
> 		s64 integer;
> 		s32 fraction; // precision may be lost if input is longer
> 	}
> 
> Maybe we want to have kstrtof32() and kstrtof64() for these two cases?
> 
> With that we will always consider the fraction part as 32- or 64-bit,
> imply floor() on the fraction for the sake of simplicity and require
> it to be NUL-terminated with possible trailing '\n'.

I think this is a good idea, but calling it float or fixed point itself
is a bit confusing as float often refers to the IEEE 754 standard and
fixed point types is often expressed in Q-format.

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply

* Re: [PATCH v2] bootconfig: Apply early options from embedded config
From: Breno Leitao @ 2026-03-27 10:06 UTC (permalink / raw)
  To: Masami Hiramatsu
  Cc: Jonathan Corbet, Shuah Khan, linux-kernel, linux-trace-kernel,
	linux-doc, oss, paulmck, rostedt, kernel-team
In-Reply-To: <20260325232204.05edbb21c7602b6408ca007b@kernel.org>

Hi Masami,

On Wed, Mar 25, 2026 at 11:22:04PM +0900, Masami Hiramatsu wrote:
> On Wed, 25 Mar 2026 03:05:38 -0700
> Breno Leitao <leitao@debian.org> wrote:

> > +/*
> > + * bootconfig_apply_early_params - dispatch kernel.* keys from the embedded
> > + * bootconfig as early_param() calls.
> > + *
> > + * early_param() handlers must run before most of the kernel initialises
> > + * (e.g. before the GIC driver reads irqchip.gicv3_pseudo_nmi).  A bootconfig
> > + * attached to the initrd arrives too late for this because the initrd is not
> > + * mapped yet when early params are processed.  The embedded bootconfig lives
> > + * in the kernel image itself (.init.data), so it is always reachable.
> > + *
> > + * This function is called from setup_boot_config() which runs in
> > + * start_kernel() before parse_early_param(), making the timing correct.
> > + */
> > +static void __init bootconfig_apply_early_params(void)
>
> [sashiko comment]
> | Does this run early enough for architectural parameters?
> | While setup_boot_config() runs before parse_early_param() in start_kernel(),
> | it runs after setup_arch(). setup_boot_config() relies on xbc_init() which
> | uses the memblock allocator, requiring setup_arch() to have already
> | initialized it.
> | However, the kernel expects many early parameters (like mem=, earlycon,
> | noapic, and iommu) to be parsed during setup_arch() via the architecture's
> | call to parse_early_param(). Since setup_arch() completes before
> | setup_boot_config() runs, will these architectural early parameters be
> | silently ignored because the decisions they influence were already
> | finalized?
>
> This is the major reason that I did not support early parameter
> in bootconfig. Some archs initialize kernel_cmdline in setup_arch()
> and setup early parameters in it.

Would it be feasible to document which parameters are architecture-specific
and must be processed during setup_arch()?

We could potentially introduce a third parameter category alongside the
existing early_param() and __setup():

	* early_param()
	* __setup()
	* early_arch_param() (New)

This would allow bootconfig to support __setup() and early_param() while
explicitly excluding early_arch_param() from bootconfig processing.

This would move break down the early parameters in those that can be
easily handled.

> To fix this, we need to change setup_arch() for each architecture so
> that it calls this bootconfig_apply_early_params().

Could we instead integrate this into parse_early_param() itself? That
approach would avoid the need to modify each architecture individually.

Thanks for looking at it,
--breno

^ permalink raw reply

* Re: [PATCH] ALSA: docs: fix dead link to Intel HD-audio spec
From: Takashi Iwai @ 2026-03-27  9:31 UTC (permalink / raw)
  To: Ninad Naik
  Cc: perex, tiwai, corbet, skhan, rhys, rdunlap, linux-sound,
	linux-doc, linux-kernel, me
In-Reply-To: <20260321140212.5026-1-ninadnaik07@gmail.com>

On Sat, 21 Mar 2026 15:02:11 +0100,
Ninad Naik wrote:
> 
> The existing link redirects to a generic page. Update the link to the
> specification document.
> 
> Signed-off-by: Ninad Naik <ninadnaik07@gmail.com>

Applied to for-next branch now.


thanks,

Takashi

^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Rodrigo Alencar @ 2026-03-27  9:24 UTC (permalink / raw)
  To: Petr Mladek, rodrigo.alencar
  Cc: linux-kernel, linux-iio, devicetree, linux-doc, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Steven Rostedt, Andy Shevchenko,
	Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <acZDneLrIPOmU5ci@pathway.suse.cz>

On 26/03/27 09:45AM, Petr Mladek wrote:
> On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > 
> > Expose simple_strntoull(), by addressing its FIXME, i.e. its prototype is
> > slightly changed so that -ERANGE or -EINVAL can be evaluated by the user.
> > Flow of the function is not changed and error value is returned in the
> > end. Unsafe internal wrapper is created to reduce amount of changes.
> > 
> > --- a/include/linux/kstrtox.h
> > +++ b/include/linux/kstrtox.h
> > @@ -148,4 +148,8 @@ extern long simple_strtol(const char *,char **,unsigned int);
> >  extern unsigned long long simple_strtoull(const char *,char **,unsigned int);
> >  extern long long simple_strtoll(const char *,char **,unsigned int);
> >  
> > +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> > +					     unsigned int base, size_t max_chars,
> > +					     unsigned long long *res);
> 
> Sigh, naming is hard. I personally find it a bit confusing that the
> name is too similar to the unsafe API.
> 
> IMHO, the semantic of the new API is closer to kstrtoull().
> It just limits the size, so I would call it kstrntoull().
> 
> Also I would use int as the return parameter, see below.

Thanks for look into this one.

kstrntoull() was what I used in v8:
https://lore.kernel.org/r/20260303-adf41513-iio-driver-v8-0-8dd2417cc465@analog.com

There was a discussion around the naming:
https://lore.kernel.org/all/4mtdzxfj656sjr66npabfvrr7yd7q26l2unhsihjtniz4ossfj@g3qnzonoary6/

please suggest how the function prototype should look like.

...

> > +/* unsafe_strntoull ignores simple_strntoull() return value and endp const qualifier */
> > +inline
> > +static unsigned long long unsafe_strntoull(const char *startp, char **endp,
> > +					   unsigned int base, size_t max_chars)
> > +{
> > +	unsigned long long result;
> > +	const char *cp;
> > +
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wunused-result"
> > +	simple_strntoull(startp, &cp, base, max_chars, &result);
> > +#pragma GCC diagnostic pop
> > +
> >  	if (endp)
> >  		*endp = (char *)cp;
> 
> IMHO, we do not need local "cp". We could simply pass the endp
> to the new simple_strntoull. Or do I miss anything?

Basically the unsafe version drops the const qualifier and compiler
complains that pointer types do not match. Maybe an extra warning can
be suppressed there.

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Andy Shevchenko @ 2026-03-27  9:17 UTC (permalink / raw)
  To: Petr Mladek
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	Jonathan Cameron, David Lechner, Andy Shevchenko,
	Lars-Peter Clausen, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
	Steven Rostedt, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <acZDneLrIPOmU5ci@pathway.suse.cz>

On Fri, Mar 27, 2026 at 09:45:17AM +0100, Petr Mladek wrote:
> On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:

...

> > +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> > +					     unsigned int base, size_t max_chars,
> > +					     unsigned long long *res);
> 
> Sigh, naming is hard. I personally find it a bit confusing that the
> name is too similar to the unsafe API.
> 
> IMHO, the semantic of the new API is closer to kstrtoull().
> It just limits the size, so I would call it kstrntoull().

It's not. kstrto*() quite strict about the input, this one is actually relaxed
variant, so I wouldn't mix these two groups.

> Also I would use int as the return parameter, see below.

...

TBH, I am skeptical about this approach. My main objection is max_chars
parameter. If we want to limit the input strictly to the given number of
characters, we have to copy the string and then just use kstrto*() in a normal
way. The whole idea of that parameter is to be able to parse the fractional
part of the float number as 'iiiii.fffff', where 'i' is for integer part, and
'f' for the fractional. Since we have *endp, we may simply check that.

In case if we want to parse only, say, 6 digits and input is longer there are
a few options (in my personal preferences, the first is the better):
- consider the input invalid
- parse it as is up to the maximum and then do ceil() or floor() on top of that
- copy only necessary amount of the (sub)string and parse that.

The problem with precision is that we need to also consider floor() or ceil()
and I don't think this should be burden of the library as it's individual
preference of each of the callers (users). At least for the starter, we will
see if it's only one approach is used, we may incorporate it into the library
code.

The easiest way out is to just consider the input invalid if it overflows the
given type (s32 or s64).

But we need to have an agreement what will be the representation of the
fixed-width float numbers in the kernel? Currently IIO uses
	struct float // name is crafted for simplicity
	{
		int integer;
		int fraction;
	}

This parser wants AFAIU to have at the end of the day something like

	struct float
	{
		s64 integer;
		s64 fraction;
	}

but also wants to have the fraction part be limited in some cases to s32
or so:

	struct float
	{
		s64 integer;
		s32 fraction; // precision may be lost if input is longer
	}

Maybe we want to have kstrtof32() and kstrtof64() for these two cases?

With that we will always consider the fraction part as 32- or 64-bit,
imply floor() on the fraction for the sake of simplicity and require
it to be NUL-terminated with possible trailing '\n'.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: Re: [PATCH v5 2/3] RISC-V: KVM: Detect and expose supported HGATP G-stage modes
From: Anup Patel @ 2026-03-27  9:00 UTC (permalink / raw)
  To: fangyu.yu
  Cc: alex, andrew.jones, aou, atish.patra, corbet, guoren, kvm-riscv,
	kvm, linux-doc, linux-kernel, linux-riscv, palmer, pbonzini, pjw,
	radim.krcmar
In-Reply-To: <20260327015555.79223-1-fangyu.yu@linux.alibaba.com>

On Fri, Mar 27, 2026 at 7:26 AM <fangyu.yu@linux.alibaba.com> wrote:
>
> >> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> >>
> >> Extend kvm_riscv_gstage_mode_detect() to probe all HGATP.MODE values
> >> supported by the host and record them in a bitmask. Keep tracking the
> >> maximum supported G-stage page table level for existing internal users.
> >>
> >> Also provide lightweight helpers to retrieve the supported-mode bitmask
> >> and validate a requested HGATP.MODE against it.
> >>
> >> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> >> ---
> >>  arch/riscv/include/asm/kvm_gstage.h | 11 ++++++++
> >>  arch/riscv/kvm/gstage.c             | 43 +++++++++++++++--------------
> >>  2 files changed, 34 insertions(+), 20 deletions(-)
> >>
> >> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> >> index b12605fbca44..76c37b5dc02d 100644
> >> --- a/arch/riscv/include/asm/kvm_gstage.h
> >> +++ b/arch/riscv/include/asm/kvm_gstage.h
> >> @@ -30,6 +30,7 @@ struct kvm_gstage_mapping {
> >>  #endif
> >>
> >>  extern unsigned long kvm_riscv_gstage_max_pgd_levels;
> >> +extern u32 kvm_riscv_gstage_mode_mask;
> >
> >s/u32/unsigned long/
> >s/kvm_riscv_gstage_mode_mask/kvm_riscv_gstage_supported_mode_mask/
> >
>
> Ack, will switch the type to unsigned long and rename it to
> kvm_riscv_gstage_supported_mode_mask in the next revision.
>
> >>
> >>  #define kvm_riscv_gstage_pgd_xbits     2
> >>  #define kvm_riscv_gstage_pgd_size      (1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits))
> >> @@ -75,4 +76,14 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
> >>
> >>  void kvm_riscv_gstage_mode_detect(void);
> >>
> >> +static inline u32 kvm_riscv_get_hgatp_mode_mask(void)
> >> +{
> >> +       return kvm_riscv_gstage_mode_mask;
> >> +}
> >> +
> >> +static inline bool kvm_riscv_hgatp_mode_is_valid(unsigned long mode)
> >> +{
> >> +       return kvm_riscv_gstage_mode_mask & BIT(mode);
> >> +}
> >> +
> >>  #endif
> >> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> >> index 2d0045f502d1..328d4138f162 100644
> >> --- a/arch/riscv/kvm/gstage.c
> >> +++ b/arch/riscv/kvm/gstage.c
> >> @@ -16,6 +16,8 @@ unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 3;
> >>  #else
> >>  unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 2;
> >>  #endif
> >> +/* Bitmask of supported HGATP.MODE encodings (BIT(HGATP_MODE_*)). */
> >> +u32 kvm_riscv_gstage_mode_mask __ro_after_init;
> >>
> >>  #define gstage_pte_leaf(__ptep)        \
> >>         (pte_val(*(__ptep)) & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC))
> >> @@ -315,42 +317,43 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
> >>         }
> >>  }
> >>
> >> +static bool __init kvm_riscv_hgatp_mode_supported(unsigned long mode)
> >> +{
> >> +       csr_write(CSR_HGATP, mode << HGATP_MODE_SHIFT);
> >> +       return ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == mode);
> >> +}
> >> +
> >>  void __init kvm_riscv_gstage_mode_detect(void)
> >>  {
> >> +       kvm_riscv_gstage_mode_mask = 0;
> >> +       kvm_riscv_gstage_max_pgd_levels = 0;
> >> +
> >>  #ifdef CONFIG_64BIT
> >> -       /* Try Sv57x4 G-stage mode */
> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV57X4 << HGATP_MODE_SHIFT);
> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV57X4) {
> >> -               kvm_riscv_gstage_max_pgd_levels = 5;
> >> -               goto done;
> >> +       /* Try Sv39x4 G-stage mode */
> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV39X4)) {
> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV39X4);
> >> +               kvm_riscv_gstage_max_pgd_levels = 3;
> >>         }
> >>
> >>         /* Try Sv48x4 G-stage mode */
> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT);
> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) {
> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV48X4)) {
> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV48X4);
> >>                 kvm_riscv_gstage_max_pgd_levels = 4;
> >> -               goto done;
> >>         }
> >>
> >> -       /* Try Sv39x4 G-stage mode */
> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT);
> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV39X4) {
> >> -               kvm_riscv_gstage_max_pgd_levels = 3;
> >> -               goto done;
> >> +       /* Try Sv57x4 G-stage mode */
> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV57X4)) {
> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV57X4);
> >> +               kvm_riscv_gstage_max_pgd_levels = 5;
> >>         }
> >>  #else /* CONFIG_32BIT */
> >>         /* Try Sv32x4 G-stage mode */
> >> -       csr_write(CSR_HGATP, HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT);
> >> -       if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV32X4) {
> >> +       if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV32X4)) {
> >> +               kvm_riscv_gstage_mode_mask |= BIT(HGATP_MODE_SV32X4);
> >>                 kvm_riscv_gstage_max_pgd_levels = 2;
> >> -               goto done;
> >>         }
> >>  #endif
> >>
> >> -       /* KVM depends on !HGATP_MODE_OFF */
> >> -       kvm_riscv_gstage_max_pgd_levels = 0;
> >> -
> >> -done:
> >
> >Here are some statements from RISC-V privilege specification:
> >"Implementations that support Sv48 must also support Sv39."
> >"Implementations that support Sv57 must also support Sv48."
> >"The conversion of an Sv32x4, Sv39x4, Sv48x4, or Sv57x4 guest physical
> >address is accomplished with the
> >same algorithm used for Sv32, Sv39, Sv48, or Sv57, as presented in
> >Section 12.3.2, except that:"
> >"hgatp substitutes for the usual satp;"
> >
> >Based on above it is a waste to try each and every mode.
> >For example: if mode Sv48x4 is supported then Sv39x4 is also supported.
> >
>
> Radmi and I discussed this topic before; please refer to the following link:
> https://lore.kernel.org/linux-riscv/20260131061238.52708-1-fangyu.yu@linux.alibaba.com/

Privilege spec mandates Sv48 and Sv39 when Sv57 is supported
so the current approach is not based on any assumption.

Regards,
Anup

^ permalink raw reply

* Re: [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Petr Mladek @ 2026-03-27  8:45 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-kernel, linux-iio, devicetree, linux-doc, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Steven Rostedt, Andy Shevchenko,
	Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260320-adf41513-iio-driver-v9-2-132f0d076374@analog.com>

On Fri 2026-03-20 16:27:27, Rodrigo Alencar via B4 Relay wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Expose simple_strntoull(), by addressing its FIXME, i.e. its prototype is
> slightly changed so that -ERANGE or -EINVAL can be evaluated by the user.
> Flow of the function is not changed and error value is returned in the
> end. Unsafe internal wrapper is created to reduce amount of changes.
> 
> --- a/include/linux/kstrtox.h
> +++ b/include/linux/kstrtox.h
> @@ -148,4 +148,8 @@ extern long simple_strtol(const char *,char **,unsigned int);
>  extern unsigned long long simple_strtoull(const char *,char **,unsigned int);
>  extern long long simple_strtoll(const char *,char **,unsigned int);
>  
> +extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
> +					     unsigned int base, size_t max_chars,
> +					     unsigned long long *res);

Sigh, naming is hard. I personally find it a bit confusing that the
name is too similar to the unsafe API.

IMHO, the semantic of the new API is closer to kstrtoull().
It just limits the size, so I would call it kstrntoull().

Also I would use int as the return parameter, see below.


>  #endif	/* _LINUX_KSTRTOX_H */
> diff --git a/lib/vsprintf.c b/lib/vsprintf.c
> index 800b8ac49f53..6fb880f4013b 100644
> --- a/lib/vsprintf.c
> +++ b/lib/vsprintf.c
> @@ -75,25 +75,66 @@ enum hash_pointers_policy {
>  };
>  static enum hash_pointers_policy hash_pointers_mode __initdata;
>  
> +/**
> + * simple_strntoull - convert a string to an unsigned long long with a character limit
> + *
> + * @startp: The start of the string
> + * @endp: A pointer to the end of the parsed string will be placed here

I would write:

  * @endp: A pointer to the end of the parsed string (output)

> + * @base: The number base to use
> + * @max_chars: The maximum number of characters to parse
> + * @res: Where to write the result of the conversion on success

Nit: I would omit "on success" *res value is set to 0 on failure.
     Instead, I would write:

  * @res: Result of the conversion (output)

> + *
> + * Returns amount of processed characters on success, -ERANGE on overflow and
> + * -EINVAL on parsing error.
> + */
>  noinline
> -static unsigned long long simple_strntoull(const char *startp, char **endp, unsigned int base, size_t max_chars)
> +ssize_t simple_strntoull(const char *startp, const char **endp,
> +			 unsigned int base, size_t max_chars,
> +			 unsigned long long *res)

It might be enoungh to use "int" for the return value. The number
of proceed characters is pretty limited by definition. And it
would be similar to vsnprintf(), kstrtoull(), ...

I guess that you wanted to match the "size_t max_chars" parameter.
It makes some sense as well.

Please, use "int" especially if we agreed to call the new API
kstrntoull().

>  {
>  	const char *cp;
> -	unsigned long long result = 0ULL;
>  	size_t prefix_chars;
>  	unsigned int rv;
> +	ssize_t ret;
>  
>  	cp = _parse_integer_fixup_radix(startp, &base);
>  	prefix_chars = cp - startp;
>  	if (prefix_chars < max_chars) {
> -		rv = _parse_integer_limit(cp, base, &result, max_chars - prefix_chars);
> -		/* FIXME */
> +		rv = _parse_integer_limit(cp, base, res, max_chars - prefix_chars);
> +		if (rv & KSTRTOX_OVERFLOW)
> +			ret = -ERANGE;
> +		else if (rv == 0)
> +			ret = -EINVAL;
> +		else
> +			ret = rv + prefix_chars;
>  		cp += (rv & ~KSTRTOX_OVERFLOW);
>  	} else {
>  		/* Field too short for prefix + digit, skip over without converting */
>  		cp = startp + max_chars;
> +		ret = -EINVAL;
> +		*res = 0ULL;
>  	}
>  
> +	if (endp)
> +		*endp = cp;
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(simple_strntoull);
> +
> +/* unsafe_strntoull ignores simple_strntoull() return value and endp const qualifier */
> +inline
> +static unsigned long long unsafe_strntoull(const char *startp, char **endp,
> +					   unsigned int base, size_t max_chars)
> +{
> +	unsigned long long result;
> +	const char *cp;
> +
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wunused-result"
> +	simple_strntoull(startp, &cp, base, max_chars, &result);
> +#pragma GCC diagnostic pop
> +
>  	if (endp)
>  		*endp = (char *)cp;

IMHO, we do not need local "cp". We could simply pass the endp
to the new simple_strntoull. Or do I miss anything?

Best Regards,
Petr

^ permalink raw reply

* Re: [PATCH] arm64/kvm: Enable eager hugepage splitting if HDBSS is available
From: Tian Zheng @ 2026-03-27  7:40 UTC (permalink / raw)
  To: Leonardo Bras
  Cc: maz, oupton, catalin.marinas, corbet, pbonzini, will, yuzenghui,
	wangzhou1, liuyonglong, Jonathan.Cameron, yezhenyu2, linuxarm,
	joey.gouly, kvmarm, kvm, linux-arm-kernel, linux-doc,
	linux-kernel, skhan, suzuki.poulose
In-Reply-To: <acQna2hLwdr1juTN@devkitleo>


On 3/26/2026 2:20 AM, Leonardo Bras wrote:
> FEAT_HDBSS speeds up guest memory dirty tracking by avoiding a page fault
> and saving the entry in a tracking structure.
>
> That may be a problem when we have guest memory backed by hugepages or
> transparent huge pages, as it's not possible to do on-demand hugepage
> splitting, relying only on eager hugepage splitting.
>
> So, at stage2 initialization, enable eager hugepage splitting with
> chunk = PAGE_SIZE if the system supports HDBSS.
>
> Signed-off-by: Leonardo Bras <leo.bras@arm.com>
> ---
>   arch/arm64/kvm/mmu.c | 8 ++++++--
>   1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 070a01e53fcb..bdfa72b7c073 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -993,22 +993,26 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
>   
>   	mmu->last_vcpu_ran = alloc_percpu(typeof(*mmu->last_vcpu_ran));
>   	if (!mmu->last_vcpu_ran) {
>   		err = -ENOMEM;
>   		goto out_destroy_pgtable;
>   	}
>   
>   	for_each_possible_cpu(cpu)
>   		*per_cpu_ptr(mmu->last_vcpu_ran, cpu) = -1;
>   
> -	 /* The eager page splitting is disabled by default */
> -	mmu->split_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;
> +	 /* The eager page splitting is disabled by default if system has no HDBSS */
> +	if (system_supports_hacdbs())
> +		mmu->split_page_chunk_size = PAGE_SIZE;
> +	else
> +		mmu->split_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;
> +
>   	mmu->split_page_cache.gfp_zero = __GFP_ZERO;
>   
>   	mmu->pgd_phys = __pa(pgt->pgd);
>   
>   	if (kvm_is_nested_s2_mmu(kvm, mmu))
>   		kvm_init_nested_s2_mmu(mmu);
>   
>   	return 0;
>   
>   out_destroy_pgtable:


Thanks again for sending this patch. I'll integrate it into the next 
version and run some tests.



^ permalink raw reply

* Re: [PATCH v3 4/5] KVM: arm64: Enable HDBSS support and handle HDBSSF events
From: Tian Zheng @ 2026-03-27  7:35 UTC (permalink / raw)
  To: Leonardo Bras
  Cc: maz, oupton, catalin.marinas, corbet, pbonzini, will, yuzenghui,
	wangzhou1, liuyonglong, Jonathan.Cameron, yezhenyu2, linuxarm,
	joey.gouly, kvmarm, kvm, linux-arm-kernel, linux-doc,
	linux-kernel, skhan, suzuki.poulose
In-Reply-To: <acQj5grOdZT8LUGp@devkitleo>


On 3/26/2026 2:05 AM, Leonardo Bras wrote:
> Hello Tian,
>
> I am currently working on HACDBS enablement(which will be rebased on top of
> this patchset) and due to the fact HACDBS and HDBSS are kind of
> complementary I will sometimes come with some questions for issues I have
> faced myself on that part. :)
>
> (see below)


Of course! Happy to exchange ideas and learn together.


>
> On Wed, Feb 25, 2026 at 12:04:20PM +0800, Tian Zheng wrote:
>> From: eillon <yezhenyu2@huawei.com>
>>
>> HDBSS is enabled via an ioctl from userspace (e.g. QEMU) at the start of
>> migration. This feature is only supported in VHE mode.
>>
>> Initially, S2 PTEs doesn't contain the DBM attribute. During migration,
>> write faults are handled by user_mem_abort, which relaxes permissions
>> and adds the DBM bit when HDBSS is active. Once DBM is set, subsequent
>> writes no longer trap, as the hardware automatically transitions the page
>> from writable-clean to writable-dirty.
>>
>> KVM does not scan S2 page tables to consume DBM. Instead, when HDBSS is
>> enabled, the hardware observes the clean->dirty transition and records
>> the corresponding page into the HDBSS buffer.
>>
>> During sync_dirty_log, KVM kicks all vCPUs to force VM-Exit, ensuring
>> that check_vcpu_requests flushes the HDBSS buffer and propagates the
>> accumulated dirty information into the userspace-visible dirty bitmap.
>>
>> Add fault handling for HDBSS including buffer full, external abort, and
>> general protection fault (GPF).
>>
>> Signed-off-by: eillon <yezhenyu2@huawei.com>
>> Signed-off-by: Tian Zheng <zhengtian10@huawei.com>
>> ---
>>   arch/arm64/include/asm/esr.h      |   5 ++
>>   arch/arm64/include/asm/kvm_host.h |  17 +++++
>>   arch/arm64/include/asm/kvm_mmu.h  |   1 +
>>   arch/arm64/include/asm/sysreg.h   |  11 ++++
>>   arch/arm64/kvm/arm.c              | 102 ++++++++++++++++++++++++++++++
>>   arch/arm64/kvm/hyp/vhe/switch.c   |  19 ++++++
>>   arch/arm64/kvm/mmu.c              |  70 ++++++++++++++++++++
>>   arch/arm64/kvm/reset.c            |   3 +
>>   8 files changed, 228 insertions(+)
>>
>> diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
>> index 81c17320a588..2e6b679b5908 100644
>> --- a/arch/arm64/include/asm/esr.h
>> +++ b/arch/arm64/include/asm/esr.h
>> @@ -437,6 +437,11 @@
>>   #ifndef __ASSEMBLER__
>>   #include <asm/types.h>
>>
>> +static inline bool esr_iss2_is_hdbssf(unsigned long esr)
>> +{
>> +	return ESR_ELx_ISS2(esr) & ESR_ELx_HDBSSF;
>> +}
>> +
>>   static inline unsigned long esr_brk_comment(unsigned long esr)
>>   {
>>   	return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;
>> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
>> index 5d5a3bbdb95e..57ee6b53e061 100644
>> --- a/arch/arm64/include/asm/kvm_host.h
>> +++ b/arch/arm64/include/asm/kvm_host.h
>> @@ -55,12 +55,17 @@
>>   #define KVM_REQ_GUEST_HYP_IRQ_PENDING	KVM_ARCH_REQ(9)
>>   #define KVM_REQ_MAP_L1_VNCR_EL2		KVM_ARCH_REQ(10)
>>   #define KVM_REQ_VGIC_PROCESS_UPDATE	KVM_ARCH_REQ(11)
>> +#define KVM_REQ_FLUSH_HDBSS			KVM_ARCH_REQ(12)
>>
>>   #define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
>>   				     KVM_DIRTY_LOG_INITIALLY_SET)
>>
>>   #define KVM_HAVE_MMU_RWLOCK
>>
>> +/* HDBSS entry field definitions */
>> +#define HDBSS_ENTRY_VALID BIT(0)
>> +#define HDBSS_ENTRY_IPA GENMASK_ULL(55, 12)
>> +
>>   /*
>>    * Mode of operation configurable with kvm-arm.mode early param.
>>    * See Documentation/admin-guide/kernel-parameters.txt for more information.
>> @@ -84,6 +89,7 @@ int __init kvm_arm_init_sve(void);
>>   u32 __attribute_const__ kvm_target_cpu(void);
>>   void kvm_reset_vcpu(struct kvm_vcpu *vcpu);
>>   void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu);
>> +void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu);
>>
>>   struct kvm_hyp_memcache {
>>   	phys_addr_t head;
>> @@ -405,6 +411,8 @@ struct kvm_arch {
>>   	 * the associated pKVM instance in the hypervisor.
>>   	 */
>>   	struct kvm_protected_vm pkvm;
>> +
>> +	bool enable_hdbss;
>>   };
>>
>>   struct kvm_vcpu_fault_info {
>> @@ -816,6 +824,12 @@ struct vcpu_reset_state {
>>   	bool		reset;
>>   };
>>
>> +struct vcpu_hdbss_state {
>> +	phys_addr_t base_phys;
>> +	u32 size;
>> +	u32 next_index;
>> +};
>> +
>>   struct vncr_tlb;
>>
>>   struct kvm_vcpu_arch {
>> @@ -920,6 +934,9 @@ struct kvm_vcpu_arch {
>>
>>   	/* Per-vcpu TLB for VNCR_EL2 -- NULL when !NV */
>>   	struct vncr_tlb	*vncr_tlb;
>> +
>> +	/* HDBSS registers info */
>> +	struct vcpu_hdbss_state hdbss;
>>   };
>>
>>   /*
>> diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
>> index d968aca0461a..3fea8cfe8869 100644
>> --- a/arch/arm64/include/asm/kvm_mmu.h
>> +++ b/arch/arm64/include/asm/kvm_mmu.h
>> @@ -183,6 +183,7 @@ int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,
>>
>>   int kvm_handle_guest_sea(struct kvm_vcpu *vcpu);
>>   int kvm_handle_guest_abort(struct kvm_vcpu *vcpu);
>> +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu);
>>
>>   phys_addr_t kvm_mmu_get_httbr(void);
>>   phys_addr_t kvm_get_idmap_vector(void);
>> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
>> index f4436ecc630c..d11f4d0dd4e7 100644
>> --- a/arch/arm64/include/asm/sysreg.h
>> +++ b/arch/arm64/include/asm/sysreg.h
>> @@ -1039,6 +1039,17 @@
>>
>>   #define GCS_CAP(x)	((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \
>>   					       GCS_CAP_VALID_TOKEN)
>> +
>> +/*
>> + * Definitions for the HDBSS feature
>> + */
>> +#define HDBSS_MAX_SIZE		HDBSSBR_EL2_SZ_2MB
>> +
>> +#define HDBSSBR_EL2(baddr, sz)	(((baddr) & GENMASK(55, 12 + sz)) | \
>> +				 FIELD_PREP(HDBSSBR_EL2_SZ_MASK, sz))
>> +
>> +#define HDBSSPROD_IDX(prod)	FIELD_GET(HDBSSPROD_EL2_INDEX_MASK, prod)
>> +
>>   /*
>>    * Definitions for GICv5 instructions]
>>    */
>> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
>> index 29f0326f7e00..d64da05e25c4 100644
>> --- a/arch/arm64/kvm/arm.c
>> +++ b/arch/arm64/kvm/arm.c
>> @@ -125,6 +125,87 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu)
>>   	return kvm_vcpu_exiting_guest_mode(vcpu) == IN_GUEST_MODE;
>>   }
>>
>> +void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu)
>> +{
>> +	struct page *hdbss_pg;
>> +
>> +	hdbss_pg = phys_to_page(vcpu->arch.hdbss.base_phys);
>> +	if (hdbss_pg)
>> +		__free_pages(hdbss_pg, vcpu->arch.hdbss.size);
>> +
>> +	vcpu->arch.hdbss.size = 0;
>> +}
>> +
>> +static int kvm_cap_arm_enable_hdbss(struct kvm *kvm,
>> +				    struct kvm_enable_cap *cap)
>> +{
>> +	unsigned long i;
>> +	struct kvm_vcpu *vcpu;
>> +	struct page *hdbss_pg = NULL;
>> +	__u64 size = cap->args[0];
>> +	bool enable = cap->args[1] ? true : false;
>> +
>> +	if (!system_supports_hdbss())
>> +		return -EINVAL;
>> +
>> +	if (size > HDBSS_MAX_SIZE)
>> +		return -EINVAL;
>> +
>> +	if (!enable && !kvm->arch.enable_hdbss) /* Already Off */
>> +		return 0;
>> +
>> +	if (enable && kvm->arch.enable_hdbss) /* Already On, can't set size */
>> +		return -EINVAL;
>> +
>> +	if (!enable) { /* Turn it off */
>> +		kvm->arch.mmu.vtcr &= ~(VTCR_EL2_HD | VTCR_EL2_HDBSS | VTCR_EL2_HA);
>> +
>> +		kvm_for_each_vcpu(i, vcpu, kvm) {
>> +			/* Kick vcpus to flush hdbss buffer. */
>> +			kvm_vcpu_kick(vcpu);
>> +
>> +			kvm_arm_vcpu_free_hdbss(vcpu);
>> +		}
>> +
>> +		kvm->arch.enable_hdbss = false;
>> +
>> +		return 0;
>> +	}
>> +
>> +	/* Turn it on */
>> +	kvm_for_each_vcpu(i, vcpu, kvm) {
>> +		hdbss_pg = alloc_pages(GFP_KERNEL_ACCOUNT, size);
>> +		if (!hdbss_pg)
>> +			goto error_alloc;
>> +
>> +		vcpu->arch.hdbss = (struct vcpu_hdbss_state) {
>> +			.base_phys = page_to_phys(hdbss_pg),
>> +			.size = size,
>> +			.next_index = 0,
>> +		};
>> +	}
>> +
>> +	kvm->arch.enable_hdbss = true;
>> +	kvm->arch.mmu.vtcr |= VTCR_EL2_HD | VTCR_EL2_HDBSS | VTCR_EL2_HA;
>> +
>> +	/*
>> +	 * We should kick vcpus out of guest mode here to load new
>> +	 * vtcr value to vtcr_el2 register when re-enter guest mode.
>> +	 */
>> +	kvm_for_each_vcpu(i, vcpu, kvm)
>> +		kvm_vcpu_kick(vcpu);
>> +
>> +	return 0;
>> +
>> +error_alloc:
>> +	kvm_for_each_vcpu(i, vcpu, kvm) {
>> +		if (vcpu->arch.hdbss.base_phys)
>> +			kvm_arm_vcpu_free_hdbss(vcpu);
>> +	}
>> +
>> +	return -ENOMEM;
>> +}
>> +
>>   int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
>>   			    struct kvm_enable_cap *cap)
>>   {
>> @@ -182,6 +263,11 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
>>   		r = 0;
>>   		set_bit(KVM_ARCH_FLAG_EXIT_SEA, &kvm->arch.flags);
>>   		break;
>> +	case KVM_CAP_ARM_HW_DIRTY_STATE_TRACK:
>> +		mutex_lock(&kvm->lock);
>> +		r = kvm_cap_arm_enable_hdbss(kvm, cap);
>> +		mutex_unlock(&kvm->lock);
>> +		break;
>>   	default:
>>   		break;
>>   	}
>> @@ -471,6 +557,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>>   			r = kvm_supports_cacheable_pfnmap();
>>   		break;
>>
>> +	case KVM_CAP_ARM_HW_DIRTY_STATE_TRACK:
>> +		r = system_supports_hdbss();
>> +		break;
>>   	default:
>>   		r = 0;
>>   	}
>> @@ -1120,6 +1209,9 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
>>   		if (kvm_dirty_ring_check_request(vcpu))
>>   			return 0;
>>
>> +		if (kvm_check_request(KVM_REQ_FLUSH_HDBSS, vcpu))
>> +			kvm_flush_hdbss_buffer(vcpu);
>> +
>>   		check_nested_vcpu_requests(vcpu);
>>   	}
>>
>> @@ -1898,7 +1990,17 @@ long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl,
>>
>>   void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
>>   {
>> +	/*
>> +	 * Flush all CPUs' dirty log buffers to the dirty_bitmap.  Called
>> +	 * before reporting dirty_bitmap to userspace. Send a request with
>> +	 * KVM_REQUEST_WAIT to flush buffer synchronously.
>> +	 */
>> +	struct kvm_vcpu *vcpu;
>> +
>> +	if (!kvm->arch.enable_hdbss)
>> +		return;
>>
>> +	kvm_make_all_cpus_request(kvm, KVM_REQ_FLUSH_HDBSS);
>>   }
>>
>>   static int kvm_vm_ioctl_set_device_addr(struct kvm *kvm,
>> diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
>> index 9db3f11a4754..600cbc4f8ae9 100644
>> --- a/arch/arm64/kvm/hyp/vhe/switch.c
>> +++ b/arch/arm64/kvm/hyp/vhe/switch.c
>> @@ -213,6 +213,23 @@ static void __vcpu_put_deactivate_traps(struct kvm_vcpu *vcpu)
>>   	local_irq_restore(flags);
>>   }
>>
>> +static void __load_hdbss(struct kvm_vcpu *vcpu)
>> +{
>> +	struct kvm *kvm = vcpu->kvm;
>> +	u64 br_el2, prod_el2;
>> +
>> +	if (!kvm->arch.enable_hdbss)
>> +		return;
>> +
>> +	br_el2 = HDBSSBR_EL2(vcpu->arch.hdbss.base_phys, vcpu->arch.hdbss.size);
>> +	prod_el2 = vcpu->arch.hdbss.next_index;
>> +
>> +	write_sysreg_s(br_el2, SYS_HDBSSBR_EL2);
>> +	write_sysreg_s(prod_el2, SYS_HDBSSPROD_EL2);
>> +
>> +	isb();
>> +}
>> +
> I see in the code below you trust that the tracking will happen with
> PAGE_SIZE granularity (you track with PAGE_SHIFT).
>
> That may be a problem when we have guest memory backed by hugepages or
> transparent huge pages.
>
> When we are using HDBSS, there is no fault happening, so we have no way of
> doing on-demand block splitting, so we need to make use of eager block
> splitting, _before_ we start to track anything, or else we may have
> different-sized pages in the HDBSS buffer, which is harder to deal with.
>
> Suggestion: do the eager splitting before we enable HDBSS.
>
> For this to happen, we have to enable the EAGER_SPLIT_CHUNK_SIZE
> capability, which can only be enabled when all memslots are empty.
>
> I suggest doing that at kvm_init_stage2_mmu(), and checking if HDBSS is
> in which case we set mmu->split_page_chunk_size to PAGESIZE.
>
> I will send a patch you can put before this one to make sure it works :)
>
> Thanks!
> Leo

Hi Leo,

Thanks for the helpful suggestion. I had previously traced the 
hugepage-splitting path

during live migration and found that when migration starts, enabling 
dirty logging

triggers the splitting path. I also tested HDBSS with traditional 
hugepages and haven't

observed any issues yet.


However, your concern is valid — there may be cases not covered, 
especially when the

VMM uses transparent hugepages. I'll integrate your patch into the next 
version and

run some tests.


For reference, here's the path I traced:

```

- userspace, e.g., QEMU

kvm_log_start
+-> kvm_section_update_flags
     +-> kvm_slot_update_flags
         |
         | // For each memory region, QEMU issues a 
KVM_SET_USER_MEMORY_REGION ioctl.
         | // Before issuing it, flags are updated to include 
KVM_MEM_LOG_DIRTY_PAGES.
         +-> kvm_mem_flags
         +-> kvm_set_user_memory_region   // ioctl that enables dirty 
logging on the memslot

- KVM

KVM_SET_USER_MEMORY_REGION
+-> kvm_vm_ioctl_set_memory_region
     +-> kvm_set_memory_region / __kvm_set_memory_region
         +-> kvm_set_memslot
             +-> kvm_commit_memory_region
                 +-> kvm_arch_commit_memory_region
                     +-> kvm_mmu_split_memory_region
                         // Splits Stage-2 hugepages/contiguous mappings 
into 4KB PTEs.
                         +-> kvm_mmu_split_huge_pages
                             +-> kvm_pgtable_stage2_split

```

Thanks again for the detailed explanation and for sending the patch.

>>   void kvm_vcpu_load_vhe(struct kvm_vcpu *vcpu)
>>   {
>>   	host_data_ptr(host_ctxt)->__hyp_running_vcpu = vcpu;
>> @@ -220,10 +237,12 @@ void kvm_vcpu_load_vhe(struct kvm_vcpu *vcpu)
>>   	__vcpu_load_switch_sysregs(vcpu);
>>   	__vcpu_load_activate_traps(vcpu);
>>   	__load_stage2(vcpu->arch.hw_mmu, vcpu->arch.hw_mmu->arch);
>> +	__load_hdbss(vcpu);
>>   }
>>
>>   void kvm_vcpu_put_vhe(struct kvm_vcpu *vcpu)
>>   {
>> +	kvm_flush_hdbss_buffer(vcpu);
>>   	__vcpu_put_deactivate_traps(vcpu);
>>   	__vcpu_put_switch_sysregs(vcpu);
>>
>> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
>> index 070a01e53fcb..42b0710a16ce 100644
>> --- a/arch/arm64/kvm/mmu.c
>> +++ b/arch/arm64/kvm/mmu.c
>> @@ -1896,6 +1896,9 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
>>   	if (writable)
>>   		prot |= KVM_PGTABLE_PROT_W;
>>
>> +	if (writable && kvm->arch.enable_hdbss && logging_active)
>> +		prot |= KVM_PGTABLE_PROT_DBM;
>> +
>>   	if (exec_fault)
>>   		prot |= KVM_PGTABLE_PROT_X;
>>
>> @@ -2033,6 +2036,70 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
>>   	return 0;
>>   }
>>
>> +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu)
>> +{
>> +	int idx, curr_idx;
>> +	u64 br_el2;
>> +	u64 *hdbss_buf;
>> +	struct kvm *kvm = vcpu->kvm;
>> +
>> +	if (!kvm->arch.enable_hdbss)
>> +		return;
>> +
>> +	curr_idx = HDBSSPROD_IDX(read_sysreg_s(SYS_HDBSSPROD_EL2));
>> +	br_el2 = HDBSSBR_EL2(vcpu->arch.hdbss.base_phys, vcpu->arch.hdbss.size);
>> +
>> +	/* Do nothing if HDBSS buffer is empty or br_el2 is NULL */
>> +	if (curr_idx == 0 || br_el2 == 0)
>> +		return;
>> +
>> +	hdbss_buf = page_address(phys_to_page(vcpu->arch.hdbss.base_phys));
>> +	if (!hdbss_buf)
>> +		return;
>> +
>> +	guard(write_lock_irqsave)(&vcpu->kvm->mmu_lock);
>> +	for (idx = 0; idx < curr_idx; idx++) {
>> +		u64 gpa;
>> +
>> +		gpa = hdbss_buf[idx];
>> +		if (!(gpa & HDBSS_ENTRY_VALID))
>> +			continue;
>> +
>> +		gpa &= HDBSS_ENTRY_IPA;
>> +		kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT);
>> +	}
> Here ^

Thanks!

Tian


>
>> +
>> +	/* reset HDBSS index */
>> +	write_sysreg_s(0, SYS_HDBSSPROD_EL2);
>> +	vcpu->arch.hdbss.next_index = 0;
>> +	isb();
>> +}
>> +
>> +static int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu)
>> +{
>> +	u64 prod;
>> +	u64 fsc;
>> +
>> +	prod = read_sysreg_s(SYS_HDBSSPROD_EL2);
>> +	fsc = FIELD_GET(HDBSSPROD_EL2_FSC_MASK, prod);
>> +
>> +	switch (fsc) {
>> +	case HDBSSPROD_EL2_FSC_OK:
>> +		/* Buffer full, which is reported as permission fault. */
>> +		kvm_flush_hdbss_buffer(vcpu);
>> +		return 1;
>> +	case HDBSSPROD_EL2_FSC_ExternalAbort:
>> +	case HDBSSPROD_EL2_FSC_GPF:
>> +		return -EFAULT;
>> +	default:
>> +		/* Unknown fault. */
>> +		WARN_ONCE(1,
>> +				"Unexpected HDBSS fault type, FSC: 0x%llx (prod=0x%llx, vcpu=%d)\n",
>> +				fsc, prod, vcpu->vcpu_id);
>> +		return -EFAULT;
>> +	}
>> +}
>> +
>>   /**
>>    * kvm_handle_guest_abort - handles all 2nd stage aborts
>>    * @vcpu:	the VCPU pointer
>> @@ -2071,6 +2138,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
>>
>>   	is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
>>
>> +	if (esr_iss2_is_hdbssf(esr))
>> +		return kvm_handle_hdbss_fault(vcpu);
>> +
>>   	if (esr_fsc_is_translation_fault(esr)) {
>>   		/* Beyond sanitised PARange (which is the IPA limit) */
>>   		if (fault_ipa >= BIT_ULL(get_kvm_ipa_limit())) {
>> diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
>> index 959532422d3a..c03a4b310b53 100644
>> --- a/arch/arm64/kvm/reset.c
>> +++ b/arch/arm64/kvm/reset.c
>> @@ -161,6 +161,9 @@ void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu)
>>   	free_page((unsigned long)vcpu->arch.ctxt.vncr_array);
>>   	kfree(vcpu->arch.vncr_tlb);
>>   	kfree(vcpu->arch.ccsidr);
>> +
>> +	if (vcpu->kvm->arch.enable_hdbss)
>> +		kvm_arm_vcpu_free_hdbss(vcpu);
>>   }
>>
>>   static void kvm_vcpu_reset_sve(struct kvm_vcpu *vcpu)
>> --
>> 2.33.0
>>

^ permalink raw reply

* Re: [PATCH v2] Docs: iio: ad7191 Correct clock configuration
From: Jonathan Cameron @ 2026-03-27  7:31 UTC (permalink / raw)
  To: Ammar Mustafa
  Cc: Andy Shevchenko, Alisa-Dariana Roman, David Lechner, Nuno Sá,
	Andy Shevchenko, Jonathan Corbet, Shuah Khan, linux-iio,
	linux-doc, linux-kernel
In-Reply-To: <acWthfEL_HGtVykN@TARS>

On Thu, 26 Mar 2026 18:04:53 -0400
Ammar Mustafa <ammarmustafa34@gmail.com> wrote:

> On Sun, Mar 22, 2026 at 12:13:14PM +0000, Jonathan Cameron wrote:
> > On Sat, 28 Feb 2026 12:50:46 +0200
> > Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> >   
> > > On Fri, Feb 27, 2026 at 02:08:33PM -0500, Ammar Mustafa wrote:  
> > > > Correct the ad7191 documentation to match the datasheet:
> > > > - Fix inverted CLKSEL pin logic: device uses external clock when pin is
> > > >   inactive, and internal CMOS/crystal when high.    
> > > 
> > > high --> active
> > > 
> > > Thanks, this part looks good in the below documentation update.
> > >   
> > > > - Correct CMOS-compatible clock pin from MCLK2 to MCLK1.    
> > > 
> > > I haven't checked driver yet, but is it only for a single component?
> > > Can you double check that _all_ supported by the driver have the same
> > > in their datasheet(s)?
> > > 
> > > ...  
> > 
> > Hi Ammar,
> > 
> > Just a quick note to say I'm going to mark this one in patchwork
> > as needing a new version given Andy's questions have been here a while.
> > 
> > Thanks,
> > 
> > Jonathan
> >   
> > >   
> > > > +- When CLKSEL pin is ACTIVE: Uses internal 4.92MHz clock (no clock property
> > > >    needed)
> > > > -- When CLKSEL pin is tied HIGH: Requires external clock source
> > > > +- When CLKSEL pin is INACTIVE: Requires external clock source
> > > >    - Can be a crystal between MCLK1 and MCLK2 pins
> > > > -  - Or a CMOS-compatible clock driving MCLK2 pin
> > > > +  - Or a CMOS-compatible clock driving MCLK1 pin and MCLK2 left unconnected
> > > >    - Must specify the "clocks" property in device tree when using external clock    
> > >   
> >   
> 
> Hi Jonathon, 
> 
> I replied to Andy's questionm not sure if I can attach it in mutt for you,
> but we found that this driver only supports the AD7191 so no other 
> documentation needs to be updated or check for this issue. 
> Let me know if I need to do anything else to have this patch merged.
> 
Given it's docs, I'll a sneak it in (not so worried if this one gets
build time in linux-next).  Applied.

Thanks,

Jonathan

> Thank you,
> 
> Ammar Mustafa


^ permalink raw reply


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