* [PATCH] docs: gpu: drm-uapi: fix spelling of "unprivileged"
From: Godswill Onwusilike @ 2026-05-16 20:30 UTC (permalink / raw)
To: David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Jonathan Corbet
Cc: Shuah Khan, dri-devel, linux-doc, linux-kernel,
Godswill Onwusilike
Correct the spelling of "unpriviledged" to "unprivileged" in DRM uAPI documentation.
Signed-off-by: Godswill Onwusilike <onwusilikegodswill@gmail.com>
---
Documentation/gpu/drm-uapi.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst
index 579e87cb9ff7..8717744f0fec 100644
--- a/Documentation/gpu/drm-uapi.rst
+++ b/Documentation/gpu/drm-uapi.rst
@@ -568,7 +568,7 @@ ENOSPC:
EPERM/EACCES:
Returned for an operation that is valid, but needs more privileges.
E.g. root-only or much more common, DRM master-only operations return
- this when called by unpriviledged clients. There's no clear
+ this when called by unprivileged clients. There's no clear
difference between EACCES and EPERM.
ENODEV:
--
2.53.0
^ permalink raw reply related
* Re: [PATCH RESEND] Documentation: fix typo in heading for max31730
From: Krzysztof Kozlowski @ 2026-05-16 19:37 UTC (permalink / raw)
To: Guenter Roeck, Hassan Maazu
Cc: skhan@linuxfoundation.org, corbet@lwn.net,
linux-doc@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <ce11a8ba-8ebc-4c09-b6d0-7e98febeae6b@roeck-us.net>
On 16/05/2026 14:29, Guenter Roeck wrote:
> On Sat, May 16, 2026 at 06:00:01AM +0000, Hassan Maazu wrote:
>> Wrong device name used in heading.
>>
>
> That is not a proper commit description.
But luckily that one-liner was reviewed by Sashiko. :/
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 2/3] iio: dac: Add AD5529R DAC driver support
From: David Lechner @ 2026-05-16 19:35 UTC (permalink / raw)
To: Janani Sunil, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jonathan Corbet,
Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-doc, Janani Sunil
In-Reply-To: <20260508-ad5529r-driver-v2-2-e315441685d7@analog.com>
On 5/8/26 6:55 AM, Janani Sunil wrote:
> Add support for AD5529R 16-channel, 12/16 bit Digital to Analog Converter
>
...
> +#define AD5529R_DAC_CHANNEL(chan, bits) { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .output = 1, \
> + .channel = (chan), \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE), \
> + .scan_type = { \
> + .sign = 'u', \
This field has a new name `.format`.
> + .realbits = (bits), \
> + .storagebits = 16, \
> + }, \
> +}
> +static struct regmap *ad5529r_get_regmap(struct ad5529r_state *st, unsigned int reg)
> +{
> + if (reg <= AD5529R_8BIT_REG_MAX)
> + return st->regmap_8bit;
> +
> + return st->regmap_16bit;
> +}
Another way we have done this is make custom read/write functions for the
regmap itself so that we don't have to have two regmaps.
> +
> +static int ad5529r_debugfs_reg_read(struct ad5529r_state *st, unsigned int reg,
> + unsigned int *val)
> +{
> + return regmap_read(ad5529r_get_regmap(st, reg), reg, val);
> +}
> +
> +static int ad5529r_debugfs_reg_write(struct ad5529r_state *st, unsigned int reg,
> + unsigned int val)
> +{
> + return regmap_write(ad5529r_get_regmap(st, reg), reg, val);
> +}
Would be more logical to move these closer to the struct that
references them.
(I snipped a bunch of functions here)
> +
> +static int ad5529r_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg,
> + unsigned int writeval,
> + unsigned int *readval)
> +{
> + struct ad5529r_state *st = iio_priv(indio_dev);
> +
> + if (!readval)
Might as well swap these and avoid the !.
> + return ad5529r_debugfs_reg_write(st, reg, writeval);
> +
> + return ad5529r_debugfs_reg_read(st, reg, readval);
> +}
> +
^ permalink raw reply
* Re: [PATCH v2 1/3] dt-bindings: iio: dac: Add AD5529R
From: David Lechner @ 2026-05-16 19:25 UTC (permalink / raw)
To: Jonathan Cameron, Janani Sunil
Cc: Lars-Peter Clausen, Michael Hennerich, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, linux-iio, devicetree,
linux-kernel, linux-doc, Janani Sunil, rodrigo.alencar
In-Reply-To: <20260508134843.7646c4f5@jic23-huawei>
On 5/8/26 7:48 AM, Jonathan Cameron wrote:
> On Fri, 8 May 2026 13:55:47 +0200
> Janani Sunil <janani.sunil@analog.com> wrote:
>
>> Devicetree bindings for AD5529R 16 channel 12/16 bit high voltage,
>> buffered voltage output digital-to-analog converter (DAC) with an
>> integrated precision reference.
>>
>> Signed-off-by: Janani Sunil <janani.sunil@analog.com>
>> ---
...
>> + * Multiplexer for output voltage, load current sense and die temperature
>> +
>> + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad5529r.pdf
>> +
>> +properties:
>> + compatible:
>> + const: adi,ad5529r
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + spi-max-frequency:
>> + maximum: 50000000
>> +
>> + reset-gpios:
>> + maxItems: 1
>> + description:
>> + GPIO connected to the RESET pin. Active low. When asserted low,
>> + performs a power-on reset and initializes the device to its default state.
>> +
>> + vdd-supply:
>> + description: Digital power supply (typically 3.3V)
>> +
>> + avdd-supply:
>> + description: Analog power supply (typically 5V)
>> +
>> + hvdd-supply:
>> + description: High voltage positive supply (up to 40V for output range)
>> +
>> + hvss-supply:
>> + description: High voltage negative supply (ground or negative voltage)
>
> I don't mind doing it this way but in some similar cases where 0 is something that
> can be considered the 'default' we've made the supply optional. What was
> your reasoning for requiring it in this case?
>
> dt-bindings should be as complete as we can make them - with that in mind...
>
> There are some more interesting corners on this device the binding doesn't
> currently cover such as mux_out pin. We'd normally do that by making the
> driver potentially a client of an ADC
>
> Easier though is !alarm which smells like an interrupt.
> !clear probably a gpio. TG0-3 also GPIOs.
also optional vref-supply for external vs internal reference
>
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - vdd-supply
>> + - avdd-supply
>> + - hvdd-supply
>> + - hvss-supply
>
^ permalink raw reply
* Re: [PATCH] landlock: Documentation wording cleanups
From: Alejandro Colomar @ 2026-05-16 19:19 UTC (permalink / raw)
To: Günther Noack
Cc: Mickaël Salaün, linux-doc, linux-security-module
In-Reply-To: <20260516190112.4924-1-gnoack3000@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 2294 bytes --]
Hi Günther,
> Cc: Alejandro Colomar <alx.manpages@gmail.com>
I don't use that address anymore. (The gmail account still exists, but
I'll eventually remove it.)
On 2026-05-16T21:01:12+0200, Günther Noack wrote:
> Documentation cleanups suggested by Alejandro Colomar,
> which we have also applied in the man pages.
>
> Link: https://lore.kernel.org/all/agW4yMK6CinJGqXt@devuan/
> Suggested-by: Alejandro Colomar <alx@kernel.org>
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> include/uapi/linux/landlock.h | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 10a346e55e95..48c12ddf1108 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -255,16 +255,16 @@ struct landlock_net_port_attr {
> * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
> * explicit recipient address.
> *
> - * This access right only applies to connections to UNIX server sockets which
> + * This access right applies only to connections to UNIX server sockets which
Yup. As a reminder, 'only' applies to whatever comes immediately after
it.
> * were created outside of the newly created Landlock domain (e.g. from within
> * a parent domain or from an unrestricted process). Newly created UNIX
> * servers within the same Landlock domain continue to be accessible. In this
> * regard, %LANDLOCK_ACCESS_FS_RESOLVE_UNIX has the same semantics as the
> * ``LANDLOCK_SCOPE_*`` flags.
> *
> - * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
> - * in line with other filesystem access rights (but different to denials for
> - * abstract UNIX domain sockets).
> + * If a resolution attempt is denied, the operation returns an ``EACCES``
> + * error, in line with other filesystem access rights (but different to
> + * denials for abstract UNIX domain sockets).
I.e.: s/resolve/resolution/
I miss semantic newlines! :)
Have a lovely day!
Alex
> *
> * This access right is available since the ninth version of the Landlock ABI.
> *
> --
> 2.54.0
>
--
<https://www.alejandro-colomar.es>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* [PATCH v2 2/2] hwmon: raspberrypi: Add voltage input support
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516191555.17978-1-chakrabortyshubham66@gmail.com>
Extend the raspberrypi-hwmon driver to expose firmware-provided
voltage measurements through the hwmon subsystem.
The driver now exports the following voltage inputs:
- in0_input (core)
- in1_input (sdram_c)
- in2_input (sdram_i)
- in3_input (sdram_p)
Voltage values returned by firmware are converted from microvolts
to millivolts as expected by the hwmon subsystem.
Update the documentation related to it.
The existing undervoltage sticky alarm handling is preserved and
associated with the first voltage channel.
Tested in -
- Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 134 +++++++++++++++++++++-
2 files changed, 144 insertions(+), 5 deletions(-)
diff --git a/Documentation/hwmon/raspberrypi-hwmon.rst b/Documentation/hwmon/raspberrypi-hwmon.rst
index 8038ade36490..db315184b861 100644
--- a/Documentation/hwmon/raspberrypi-hwmon.rst
+++ b/Documentation/hwmon/raspberrypi-hwmon.rst
@@ -20,6 +20,17 @@ undervoltage conditions.
Sysfs entries
-------------
-======================= ==================
+======================= ======================================================
+in0_input Core voltage in millivolts
+in1_input SDRAM controller voltage in millivolts
+in2_input SDRAM I/O voltage in millivolts
+in3_input SDRAM PHY voltage in millivolts
+in0_label "core"
+in1_label "sdram_c"
+in2_label "sdram_i"
+in3_label "sdram_p"
in0_lcrit_alarm Undervoltage alarm
-======================= ==================
+======================= ======================================================
+
+The voltage inputs and labels are only exposed if the firmware reports support
+for the corresponding voltage ID.
diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
index a2938881ccd2..4f96f37116f3 100644
--- a/drivers/hwmon/raspberrypi-hwmon.c
+++ b/drivers/hwmon/raspberrypi-hwmon.c
@@ -5,6 +5,7 @@
* Based on firmware/raspberrypi.c by Noralf Trønnes
*
* Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
+ * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
*/
#include <linux/device.h>
#include <linux/devm-helpers.h>
@@ -18,13 +19,26 @@
#define UNDERVOLTAGE_STICKY_BIT BIT(16)
+struct rpi_firmware_get_value {
+ __le32 id;
+ __le32 val;
+} __packed;
+
struct rpi_hwmon_data {
struct device *hwmon_dev;
struct rpi_firmware *fw;
+ u32 valid_inputs;
u32 last_throttled;
struct delayed_work get_values_poll_work;
};
+static const char * const rpi_hwmon_labels[] = {
+ "core",
+ "sdram_c",
+ "sdram_i",
+ "sdram_p",
+};
+
static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
{
u32 new_uv, old_uv, value;
@@ -56,6 +70,23 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
}
+static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
+ long *val)
+{
+ struct rpi_firmware_get_value packet;
+ int ret;
+
+ packet.id = cpu_to_le32(id);
+ packet.val = 0;
+ ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
+ &packet, sizeof(packet));
+ if (ret)
+ return ret;
+
+ *val = le32_to_cpu(packet.val) / 1000;
+ return 0;
+}
+
static void get_values_poll(struct work_struct *work)
{
struct rpi_hwmon_data *data;
@@ -77,19 +108,94 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct rpi_hwmon_data *data = dev_get_drvdata(dev);
- *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ switch (channel) {
+ case 0:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_CORE,
+ val);
+ case 1:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ val);
+ case 2:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ val);
+ case 3:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0) {
+ *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ return 0;
+ }
+ return -EOPNOTSUPP;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type == hwmon_in && attr == hwmon_in_label) {
+ if (channel >= ARRAY_SIZE(rpi_hwmon_labels))
+ return -EOPNOTSUPP;
+
+ *str = rpi_hwmon_labels[channel];
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct rpi_hwmon_data *data = _data;
+
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_label:
+ if (!(data->valid_inputs & BIT(channel)))
+ return 0;
+ return 0444;
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0)
+ return 0444;
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
return 0;
}
static const struct hwmon_channel_info * const rpi_info[] = {
HWMON_CHANNEL_INFO(in,
- HWMON_I_LCRIT_ALARM),
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
NULL
};
static const struct hwmon_ops rpi_hwmon_ops = {
- .visible = 0444,
+ .is_visible = rpi_is_visible,
.read = rpi_read,
+ .read_string = rpi_read_string,
};
static const struct hwmon_chip_info rpi_chip_info = {
@@ -101,6 +207,7 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rpi_hwmon_data *data;
+ long voltage;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
@@ -110,6 +217,26 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
/* Parent driver assure that firmware is correct */
data->fw = dev_get_drvdata(dev->parent);
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_CORE,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(0);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(1);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(2);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(3);
+
data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
data,
&rpi_chip_info,
@@ -159,6 +286,7 @@ static struct platform_driver rpi_hwmon_driver = {
module_platform_driver(rpi_hwmon_driver);
MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
+MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:raspberrypi-hwmon");
--
2.54.0
^ permalink raw reply related
* [PATCH v2 1/2] soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516191555.17978-1-chakrabortyshubham66@gmail.com>
Add firmware voltage domain identifiers for the Raspberry Pi
mailbox property interface.
These IDs are used by firmware clients to query voltage rails
through the RPI_FIRMWARE_GET_VOLTAGE property.
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
include/soc/bcm2835/raspberrypi-firmware.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h
index e1f87fbfe554..fd2e051ce05b 100644
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -156,6 +156,14 @@ enum rpi_firmware_clk_id {
RPI_FIRMWARE_NUM_CLK_ID,
};
+enum rpi_firmware_volt_id {
+ RPI_FIRMWARE_VOLT_ID_RESERVED = 0,
+ RPI_FIRMWARE_VOLT_ID_CORE = 1,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C = 2,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I = 3,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P = 4,
+};
+
/**
* struct rpi_firmware_clk_rate_request - Firmware Request for a rate
* @id: ID of the clock being queried
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/2] raspberrypi: firmware and hwmon voltage support
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516164407.25255-1-chakrabortyshubham66@gmail.com>
Changes in v2:
- Patch 1/2: no changes
- Patch 2/2:
- hide unsupported voltage sensors using .is_visible()
- replace the label switch with a string array
- update raspberrypi-hwmon documentation
Shubham Chakraborty (2):
soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
hwmon: raspberrypi: Add voltage input support
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 134 ++++++++++++++++++++-
include/soc/bcm2835/raspberrypi-firmware.h | 8 ++
3 files changed, 152 insertions(+), 5 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH] landlock: Documentation wording cleanups
From: Günther Noack @ 2026-05-16 19:01 UTC (permalink / raw)
To: Mickaël Salaün
Cc: linux-doc, linux-security-module, Alejandro Colomar,
Günther Noack, Alejandro Colomar
Documentation cleanups suggested by Alejandro Colomar,
which we have also applied in the man pages.
Link: https://lore.kernel.org/all/agW4yMK6CinJGqXt@devuan/
Suggested-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
include/uapi/linux/landlock.h | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 10a346e55e95..48c12ddf1108 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -255,16 +255,16 @@ struct landlock_net_port_attr {
* :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
* explicit recipient address.
*
- * This access right only applies to connections to UNIX server sockets which
+ * This access right applies only to connections to UNIX server sockets which
* were created outside of the newly created Landlock domain (e.g. from within
* a parent domain or from an unrestricted process). Newly created UNIX
* servers within the same Landlock domain continue to be accessible. In this
* regard, %LANDLOCK_ACCESS_FS_RESOLVE_UNIX has the same semantics as the
* ``LANDLOCK_SCOPE_*`` flags.
*
- * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
- * in line with other filesystem access rights (but different to denials for
- * abstract UNIX domain sockets).
+ * If a resolution attempt is denied, the operation returns an ``EACCES``
+ * error, in line with other filesystem access rights (but different to
+ * denials for abstract UNIX domain sockets).
*
* This access right is available since the ninth version of the Landlock ABI.
*
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v11 5/6] iio: adc: ad4691: add oversampling support
From: Jonathan Cameron @ 2026-05-16 18:55 UTC (permalink / raw)
To: Radu Sabau via B4 Relay
Cc: radu.sabau, Lars-Peter Clausen, Michael Hennerich, 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: <20260515-ad4692-multichannel-sar-adc-driver-v11-5-eab27d852ac2@analog.com>
On Fri, 15 May 2026 16:31:34 +0300
Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org> wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add per-channel oversampling ratio (OSR) support for CNV burst mode.
> The accumulator depth register (ACC_DEPTH_IN) is programmed with the
> selected OSR at buffer enable time and before each single-shot read.
>
> Supported OSR values: 1, 2, 4, 8, 16, 32.
>
> Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do
> not expose the oversampling_ratio attribute since OSR is not applicable
> in that mode. A separate manual_channels array is added to
> struct ad4691_channel_info and selected at probe time.
>
> in_voltageN_sampling_frequency represents the effective output rate for
> channel N, defined as osc_freq / osr[N]. The chip has one internal
> oscillator shared by all channels; each channel independently
> accumulates osr[N] oscillator cycles before producing a result.
>
> Writing sampling_frequency computes needed_osc = freq * osr[N] and
> snaps down to the largest oscillator table entry that satisfies both
> osc <= needed_osc and osc % osr[N] == 0, guaranteeing an exact integer
> read-back. The result is stored in target_osc_freq_Hz and written to
> OSC_FREQ_REG at buffer enable and single-shot time, so sampling_frequency
> and oversampling_ratio can be set in any order.
>
> in_voltageN_sampling_frequency_available is computed dynamically from
> the channel's current OSR, listing only oscillator table entries that
> divide evenly by osr[N], expressed as effective rates. The list becomes
> sparser as OSR increases, capping at max_rate / osr[N].
>
> Writing oversampling_ratio stores the new OSR for that channel and snaps
> target_osc_freq_Hz to the largest oscillator table entry that is both
> <= old_effective_rate * new_osr and evenly divisible by new_osr. This
> preserves an integer read-back of in_voltageN_sampling_frequency after
> the OSR change while keeping the oscillator as close as possible to the
> previous effective rate.
>
> OSR defaults to 1 (no accumulation) for all channels.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
Mostly to avoid others looking into it. We do indeed have some issues
in the IIO core with races around read_avail().
They've been there a long time and attempts to fix them haven't yet
made it upstream. Where possible it is better to precompute all the options
and pick a pointer rather than copying on the fly.
I think we can do that here but maybe I'm missing something.
I'm running out of energy tonight and feel like some Eurovision silliness
so I'm not going to do another full review today at least
> ---
> drivers/iio/adc/ad4691.c | 381 ++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 343 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index 25f7a6939b0f..39244e0e4a2d 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
>
> static int ad4691_read_avail(struct iio_dev *indio_dev,
> @@ -634,10 +802,46 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
> unsigned int start = ad4691_samp_freq_start(st->info);
>
> switch (mask) {
> - case IIO_CHAN_INFO_SAMP_FREQ:
> - *vals = &ad4691_osc_freqs_Hz[start];
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + unsigned int osr;
> + int n = 0;
> +
> + /*
> + * Hold the lock while reading osr[chan] and populating the
> + * scratch buffer: a concurrent oversampling_ratio write modifies
> + * both target_osc_freq_Hz and osr[] under the lock, so we must
> + * read osr atomically with respect to that write. The scratch
> + * buffer is per-channel, so concurrent reads on different
> + * channels do not race; concurrent reads on the same channel
> + * would compute identical values, but holding the lock avoids
> + * the formal data race.
The further issue that sashiko points out is we might rip whilst the
core is formatting this. It's actually worse than a small race as the
consumer interface might hold the pointer indefinitely. There are only
a few osr values, can we precompute the lot and make this a pick?
> + */
> + scoped_guard(mutex, &st->lock) {
> + osr = st->osr[chan->channel];
> +
> + /*
> + * Only oscillator frequencies evenly divisible by the
> + * channel's OSR yield an integer effective rate; expose
> + * those as effective rates (osc / osr) so the user works
> + * entirely in output-sample space.
> + */
> + for (unsigned int i = start;
> + i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> + if (ad4691_osc_freqs_Hz[i] % osr)
> + continue;
> + st->samp_freq_avail[chan->channel][n++] =
> + ad4691_osc_freqs_Hz[i] / osr;
> + }
> + }
> + *vals = st->samp_freq_avail[chan->channel];
> *type = IIO_VAL_INT;
> - *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
> + *length = n;
> + 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;
^ permalink raw reply
* Re: [RFC PATCH v3 00/28] mm/damon: introduce data attributes monitoring
From: SeongJae Park @ 2026-05-16 18:50 UTC (permalink / raw)
To: SeongJae Park
Cc: Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Masami Hiramatsu,
Mathieu Desnoyers, Michal Hocko, Mike Rapoport, Shuah Khan,
Shuah Khan, Steven Rostedt, Suren Baghdasaryan, Vlastimil Babka,
damon, linux-doc, linux-kernel, linux-kselftest, linux-mm,
linux-trace-kernel
In-Reply-To: <20260516183712.81393-1-sj@kernel.org>
On Sat, 16 May 2026 11:36:41 -0700 SeongJae Park <sj@kernel.org> wrote:
> TL; DR
> ======
>
> Extend DAMON for monitoring general data attributes other than accesses.
> The short term motivation is lightweight page type (e.g., belonging
> cgroup) aware monitoring. In long term, this will help extending DAMON
> for multiple access events capture primitives (e.g., page faults and
> PMU) and eventually pivotting DAMON to a "Data Attributes Monitoring and
> Operations eNgine" in long term.
[...]
> Changes from RFC v2.1
> - rfc v2.1: https://lore.kernel.org/20260514140904.119781-1-sj@kernel.org
> - Rebase to mm-stable (7.1-rc3) to avoid Sashiko patch apply failure.
Still this seires is based on mm-stable (7.1-rc3) for the same reason. The
patches that based on mm-new is available at damon/next tree [1].
[1] https://origin.kernel.org/doc/html/latest/mm/damon/maintainer-profile.html#scm-trees
Thanks,
SJ
[...]
^ permalink raw reply
* Re: [PATCH v11 3/6] iio: adc: ad4691: add triggered buffer support
From: Jonathan Cameron @ 2026-05-16 18:48 UTC (permalink / raw)
To: Radu Sabau via B4 Relay
Cc: radu.sabau, Lars-Peter Clausen, Michael Hennerich, 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: <20260515-ad4692-multichannel-sar-adc-driver-v11-3-eab27d852ac2@analog.com>
On Fri, 15 May 2026 16:31:32 +0300
Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org> wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add buffered capture support using the IIO triggered buffer framework.
>
> 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.
See below. Sashiko noticed an issue.
Note that I think the vast majority of what it came up with for this
version is garbage. Not this one though.
For manual mode you still register a trigger and attach it.
That trigger never fires...
I suspect it appears to all work because you just set the trigger
for manual mode before enabling it. However makes more sense to not
register the pointless trigger. Just a bit of code reorg probably
to fix this.
One other thing inline from the other other sashiko bit that was 'nearly'
right.
>
> Both modes share the same trigger handler and push a complete scan —
> one big-endian 16-bit (__be16) slot per active channel, densely packed
> in scan_index order, followed by a timestamp.
>
> 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>
>
> +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;
> +
> + indio_dev->channels = st->info->sw_info->channels;
> + indio_dev->num_channels = st->info->sw_info->num_channels;
> + indio_dev->info = st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_burst_info;
> +
> + 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);
One place here where I think sashiko may have a point... You register a trigger
for manual mode. What actually makes it fire as the only code that calls
iio_trigger_poll() is in the irq that isn't registered in this path.
> +
> + if (st->manual_mode)
> + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad4691_trigger_handler,
> + &ad4691_manual_buffer_setup_ops);
> +
> + /*
> + * 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 = -ENXIO;
> + 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 || irq == -EPROBE_DEFER)
> + break;
> + }
> + if (irq < 0)
> + return dev_err_probe(dev, irq, "failed to get GP interrupt\n");
> +
> + st->irq = irq;
> +
> + ret = ad4691_gpio_setup(st, i);
> + if (ret)
> + return ret;
> +
> + /*
> + * 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);
Sashiko was moaning about something different but it made me look at this.
The irq handler her calls iio_trigger_poll but in a thread.
Either that should be a top half (so dev_request_irq() with flag to force
no threading) or should be iio_trigger_poll_nested()
I'm not sure why you weren't seeing a warning on this.
> + 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);
> +}
^ permalink raw reply
* [RFC PATCH v3 28/28] Docs/admin-guide/mm/damon/usage: update for memcg damon filter
From: SeongJae Park @ 2026-05-16 18:37 UTC (permalink / raw)
Cc: SeongJae Park, Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
linux-kernel, linux-mm
In-Reply-To: <20260516183712.81393-1-sj@kernel.org>
Update DAMON usage document for the newly added belonging memory cgroup
attribute monitoring feature.
Signed-off-by: SeongJae Park <sj@kernel.org>
---
Documentation/admin-guide/mm/damon/usage.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/mm/damon/usage.rst b/Documentation/admin-guide/mm/damon/usage.rst
index 7b6074a1666f3..b4584c0e4cd72 100644
--- a/Documentation/admin-guide/mm/damon/usage.rst
+++ b/Documentation/admin-guide/mm/damon/usage.rst
@@ -73,7 +73,7 @@ comma (",").
│ │ │ │ │ │ nr_regions/min,max
│ │ │ │ │ │ :ref:`probes <damon_usage_sysfs_probes>`/nr_probes
│ │ │ │ │ │ │ 0/filters/nr_filters
- │ │ │ │ │ │ │ │ 0/type,matching,allow
+ │ │ │ │ │ │ │ │ 0/type,matching,allow,path
│ │ │ │ │ │ │ │ ...
│ │ │ │ │ │ │ ...
│ │ │ │ │ :ref:`targets <sysfs_targets>`/nr_targets
@@ -283,7 +283,9 @@ the data attribute for the probe.
In the beginning, ``filters`` directory has only one file, ``nr_filters``.
Writing a number (``N``) to the file creates the number of child directories
named ``0`` to ``N-1``. Each directory represents each filter and works in a
-way similar to that for :ref:`DAMOS filter <sysfs_filters>`.
+way similar to that for :ref:`DAMOS filter <sysfs_filters>`. When the filter
+``type`` is ``memcg``, ``path`` file works the role of ``memcg_path`` for
+:ref:`DAMOS filter <sysfs_filters>`.
.. _sysfs_targets:
--
2.47.3
^ permalink raw reply related
* [RFC PATCH v3 27/28] Docs/mm/damon/design: update for memcg damon filter
From: SeongJae Park @ 2026-05-16 18:37 UTC (permalink / raw)
Cc: SeongJae Park, Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
linux-kernel, linux-mm
In-Reply-To: <20260516183712.81393-1-sj@kernel.org>
Update DAMON design document for the newly added belonging memory cgroup
attribute monitoring feature.
Signed-off-by: SeongJae Park <sj@kernel.org>
---
Documentation/mm/damon/design.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
index 85d668e929194..8a2d68cbcefca 100644
--- a/Documentation/mm/damon/design.rst
+++ b/Documentation/mm/damon/design.rst
@@ -286,8 +286,8 @@ registration is made by specifying a probe per attribute. Each of the probe
specifies a rule to determine if a given memory region has the related
attribute. The rule is constructed with multiple filters. The filters work
same to :ref:`DAMOS filters <damon_design_damos_filters>` except the supported
-filter types. Currently only ``anon`` filter type is supported for data
-attributes monitoring.
+filter types. Currently only ``anon`` and ``memcg`` filter types are supported
+for data attributes monitoring.
If such probes are registered, DAMON executes the probes for each region's
sampling memory when it does the access :ref:`sampling
--
2.47.3
^ permalink raw reply related
* [RFC PATCH v3 21/28] Docs/admin-guide/mm/damon/usage: document data attributes monitoring
From: SeongJae Park @ 2026-05-16 18:37 UTC (permalink / raw)
Cc: SeongJae Park, Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
linux-kernel, linux-mm
In-Reply-To: <20260516183712.81393-1-sj@kernel.org>
Update DAMON usage document for the newly added data attributes
monitoring feature.
Signed-off-by: SeongJae Park <sj@kernel.org>
---
Documentation/admin-guide/mm/damon/usage.rst | 44 ++++++++++++++++++--
Documentation/mm/damon/design.rst | 2 +
2 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/mm/damon/usage.rst b/Documentation/admin-guide/mm/damon/usage.rst
index 534e1199cf091..7b6074a1666f3 100644
--- a/Documentation/admin-guide/mm/damon/usage.rst
+++ b/Documentation/admin-guide/mm/damon/usage.rst
@@ -71,6 +71,11 @@ comma (",").
│ │ │ │ │ │ intervals/sample_us,aggr_us,update_us
│ │ │ │ │ │ │ intervals_goal/access_bp,aggrs,min_sample_us,max_sample_us
│ │ │ │ │ │ nr_regions/min,max
+ │ │ │ │ │ │ :ref:`probes <damon_usage_sysfs_probes>`/nr_probes
+ │ │ │ │ │ │ │ 0/filters/nr_filters
+ │ │ │ │ │ │ │ │ 0/type,matching,allow
+ │ │ │ │ │ │ │ │ ...
+ │ │ │ │ │ │ │ ...
│ │ │ │ │ :ref:`targets <sysfs_targets>`/nr_targets
│ │ │ │ │ │ :ref:`0 <sysfs_target>`/pid_target,obsolete_target
│ │ │ │ │ │ │ :ref:`regions <sysfs_regions>`/nr_regions
@@ -95,6 +100,9 @@ comma (",").
│ │ │ │ │ │ │ :ref:`stats <sysfs_schemes_stats>`/nr_tried,sz_tried,nr_applied,sz_applied,sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots
│ │ │ │ │ │ │ :ref:`tried_regions <sysfs_schemes_tried_regions>`/total_bytes
│ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age,sz_filter_passed
+ │ │ │ │ │ │ │ │ │ probes
+ │ │ │ │ │ │ │ │ │ │ 0/hits
+ │ │ │ │ │ │ │ │ │ │ ...
│ │ │ │ │ │ │ │ ...
│ │ │ │ │ │ ...
│ │ │ │ ...
@@ -221,8 +229,8 @@ contexts/<N>/monitoring_attrs/
Files for specifying attributes of the monitoring including required quality
and efficiency of the monitoring are in ``monitoring_attrs`` directory.
-Specifically, two directories, ``intervals`` and ``nr_regions`` exist in this
-directory.
+Specifically, three directories, ``intervals``, ``nr_regions`` and ``probes``
+exist in this directory.
Under ``intervals`` directory, three files for DAMON's sampling interval
(``sample_us``), aggregation interval (``aggr_us``), and update interval
@@ -256,6 +264,27 @@ tuning-applied current values of the two intervals can be read from the
``sample_us`` and ``aggr_us`` files after writing ``update_tuned_intervals`` to
the ``state`` file.
+.. _damon_usage_sysfs_probes:
+
+contexts/<N>/monitoring_attrs/probes/
+-------------------------------------
+
+A directory for registering :ref:`data attributes monitoring
+<damon_design_data_attrs_monitoring>` probes.
+
+In the beginning, this directory has only one file, ``nr_probes``. Writing a
+number (``N``) to the file creates the number of child directories named ``0``
+to ``N-1``. Each directory represents each monitoring probe.
+
+In each probe directory, one directory, ``filters`` exists. The directory
+contains files for installing filters for the probe, that is used to determine
+the data attribute for the probe.
+
+In the beginning, ``filters`` directory has only one file, ``nr_filters``.
+Writing a number (``N``) to the file creates the number of child directories
+named ``0`` to ``N-1``. Each directory represents each filter and works in a
+way similar to that for :ref:`DAMOS filter <sysfs_filters>`.
+
.. _sysfs_targets:
contexts/<N>/targets/
@@ -601,10 +630,19 @@ tried_regions/<N>/
------------------
In each region directory, you will find five files (``start``, ``end``,
-``nr_accesses``, ``age``, and ``sz_filter_passed``). Reading the files will
+``nr_accesses``, ``age`` and ``sz_filter_passed``). Reading the files will
show the properties of the region that corresponding DAMON-based operation
scheme ``action`` has tried to be applied.
+tried_regions/<N>/probes/
+-------------------------
+
+In each region directory, one directory (``probes``) also exists. In the
+directory, subdirectories named ``0`` to ``N-1`` exists. ``N`` is the number
+of installed probes. In each number-named directory, a file (``hits``) exist.
+Reading the file shows the number of data attributes monitoring probe-hit
+positive samples of the region.
+
Example
~~~~~~~
diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
index aa08c899a3e5b..85d668e929194 100644
--- a/Documentation/mm/damon/design.rst
+++ b/Documentation/mm/damon/design.rst
@@ -269,6 +269,8 @@ interval``, DAMON checks if the region's size and access frequency
(``nr_accesses``) has significantly changed. If so, the counter is reset to
zero. Otherwise, the counter is increased.
+.. _damon_design_data_attrs_monitoring:
+
Data Attributes Monitoring
~~~~~~~~~~~~~~~~~~~~~~~~~~
--
2.47.3
^ permalink raw reply related
* [RFC PATCH v3 20/28] Docs/mm/damon/design: document data attributes monitoring
From: SeongJae Park @ 2026-05-16 18:37 UTC (permalink / raw)
Cc: SeongJae Park, Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
linux-kernel, linux-mm
In-Reply-To: <20260516183712.81393-1-sj@kernel.org>
Update DAMON design document for newly added data attributes monitoring
feature.
Signed-off-by: SeongJae Park <sj@kernel.org>
---
Documentation/mm/damon/design.rst | 37 +++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
index afc7d52bda2f7..aa08c899a3e5b 100644
--- a/Documentation/mm/damon/design.rst
+++ b/Documentation/mm/damon/design.rst
@@ -269,6 +269,43 @@ interval``, DAMON checks if the region's size and access frequency
(``nr_accesses``) has significantly changed. If so, the counter is reset to
zero. Otherwise, the counter is increased.
+Data Attributes Monitoring
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Data access pattern is only one type of data attributes. In some use cases,
+users need to know more data attributes information. For example, users may
+need to know how much of a given hot or cold memory region is backed by
+anonymous pages, or belong to a specific cgroup. For such use case, data
+attributes monitoring feature is provided.
+
+Using the feature, users can register data attributes of their interest to the
+DAMON :ref:`context <damon_design_execution_model_and_data_structures>`. The
+registration is made by specifying a probe per attribute. Each of the probe
+specifies a rule to determine if a given memory region has the related
+attribute. The rule is constructed with multiple filters. The filters work
+same to :ref:`DAMOS filters <damon_design_damos_filters>` except the supported
+filter types. Currently only ``anon`` filter type is supported for data
+attributes monitoring.
+
+If such probes are registered, DAMON executes the probes for each region's
+sampling memory when it does the access :ref:`sampling
+<damon_design_region_based_sampling>`. The number of samples that identified
+as having the data attribute (hitting the probe) per :ref:`aggregation interval
+<damon_design_monitoring>` is accounted in a per-region per-probe counter.
+Users can therefore know how much of a given DAMON region has a specific data
+attribute by reading the per-region per-probe probe hits counter after each
+aggregation interval.
+
+This is a sampling based mechanism. Hence, it is lightweight but the output
+may include some measurement errors. The output should be used with good
+understanding of statistics.
+
+Another way to do this for higher accuracy is using :ref:`DAMOS filter
+<damon_design_damos_filters>` with ``stat`` :ref:`action
+<damon_design_damos_action>` and ``sz_ops_filter_passed`` :ref:`stat
+<damon_design_damos_stat>`. This approach provides the data attributes
+information in page level. But, because it is operated in page level, the
+overhead is proportional to the size of the memory.
Dynamic Target Space Updates Handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
2.47.3
^ permalink raw reply related
* [RFC PATCH v3 00/28] mm/damon: introduce data attributes monitoring
From: SeongJae Park @ 2026-05-16 18:36 UTC (permalink / raw)
Cc: SeongJae Park, Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Masami Hiramatsu,
Mathieu Desnoyers, Michal Hocko, Mike Rapoport, Shuah Khan,
Shuah Khan, Steven Rostedt, Suren Baghdasaryan, Vlastimil Babka,
damon, linux-doc, linux-kernel, linux-kselftest, linux-mm,
linux-trace-kernel
TL; DR
======
Extend DAMON for monitoring general data attributes other than accesses.
The short term motivation is lightweight page type (e.g., belonging
cgroup) aware monitoring. In long term, this will help extending DAMON
for multiple access events capture primitives (e.g., page faults and
PMU) and eventually pivotting DAMON to a "Data Attributes Monitoring and
Operations eNgine" in long term.
Background: High Cost of Page Level Properties Monitoring
=========================================================
DAMON is initially introduced as a Data Access MONitor. It has been
extended for not only access monitoring but also data access-aware
system operations (DAMOS). But still the monitoring part is only for
data accesses.
Data access patterns is good information, but some users need more
holistic views. Particularly, users want to show the access pattern
information together with the types of the memory. For example, users
who work for making huge pages efficiently want to know how much of
DAMON-found hot/cold regions are backed by huge pages. Users who run
multiple workloads with different cgroups want to know how much of
DAMON-found hot/cold regions belong to specific cgroups.
For the user demand, we developed a DAMOS extension for page level
properties based monitoring [1], which has landed on 6.14. Using the
feature, users can inform the page level data properties that they are
interested in, in a flexible format that uses DAMOS filters. Then,
DAMON applies the filters to each folio of the entire DAMON region and
lets users know how many bytes of memory in each DAMON region passed the
given filters.
This gives page level detailed and deterministic information to users.
But, because the operation is done at page level, the overhead is
proportional to the memory size. It was useful for test or debugging
purposes on a small number of machines. But it was obviously too heavy
to be enabled always on all machines running the real user workloads.
For real world workloads, it was recommended to use the feature with
user-space controlled sampling approaches. For example, users could do
the page level monitoring only once per hour, on randomly selected one
percent of machines of their fleet. If the runtime and the size of the
fleet is long and big enough, it should provide statistically meaningful
data.
But users are too busy to implement such controls on their own.
Data Attributes Monitoring
==========================
Extend DAMON to monitor not only data accesses, but also general data
attributes. Do the extension while keeping the main promise of DAMON,
the bounded and best-effort minimum overhead.
Allow users to specify what data attributes in addition to the data
access they want to monitor. Users can install one 'data probe' per
data attribute of their interest for this purpose. The 'data probe'
should be able to be applied to any memory, and determine if the given
memory has the appropriate data attribute. E.g., if memory of physical
address 42 belongs to cgroup A. Each 'data probe' is configured with
filters that are very similar to the DAMOS filters.
When DAMON checks if each sampling address memory of each region is
accessed since the last check, it applies data probes if registered.
Same to the number of access check-positive samples accounting
(nr_accesses), it accounts the number of each data probe-positive
samples in another per-region counters array, namely 'probe_hits'. When
DAMON resets nr_accesses every aggregation interval, it resets
'probe_hits' together.
Users can read 'probe_hits' just before the values are reset. In this
way, users can know how many hot/cold memory regions have data
attributes of their interest. E.g., 30 percent of this system's hot
memory is belonging to cgroup A, and 80 percent of the cgroup
A-belonging hot memory is backed by huge pages.
Patches Sequence
================
First eight patches implement the core feature, interface and the
working support. Patch 1 introduces data probe data structure, namely
damon_probe. Patch 2 extends damon_ctx for installing data probes.
Patch 3 introduces another data structure for filters of each data
probe, namely damon_filter. Patch 4 updates damon_ctx commit function
to handle the probes. Patch 5 extends damon_region for the per-region
per-probe positive samples counter, namely probe_hits. Patch 6 extends
damon_operations for applying probes on the underlying DAMON operations
implementation. Patch 7 updates kdamond_fn() to invoke the probes
applying callback. Patch 8 finally implements the probes support on
paddr ops.
Ten changes for user interface (patches 9-18) come next. Patches 9-13
implements sysfs directories and files for setting data probes, namely
probes directory, probe directory, filters directory, filter directory
and filter directory internal files, respectively. Patch 14 connects
the user inputs that are made via the sysfs files to DAMON core.
Following three patches (patches 15-17) implement sysfs directories and
files for showing the probe_hits to users, namely probes directory,
probe directory and hits files, respectively. Patch 18 introduces a new
tracepoint for showing the probe_hits via tracefs.
Patch 19 adds a selftest for the sysfs files.
Patches 20 and 21 documents the design and usage of the new feature,
respectively.
Seven additional patches (patches 22-28) for monitoring belonging memory
cgroup follow. Depending on the feedback, this part might be separated
to another series in future. Patch 22 defines the DAMON filter type for
the new attribute, namely DAMON_FILTER_TYPE_MEMCG. Patch 23 add the
support on paddr ops. Patch 24 updates the sysfs interface for setup of
the target memcg. Patch 25 move code for easy reuse of the filter
target memcg setup. Patch 26 connects the user input to the core layer.
Finally, patches 27 and 28 update the design and usage documents for the
memcg attribute monitoring support.
Discussions
===========
This allows the page properties monitoring with overhead that is low
enough to be enabled always on real world workloads. Because the
sampling time for access check is reused for data attributes check, the
upper-bounded and best-effort minimum overhead of DAMON is kept.
Because the sampling memory for access check is reused for data
attributes check, additional overhead is minimum.
Still DAMOS-based page level properties monitoring should be useful,
because it provides a deterministic page level information. When in
doubt of the sampling based information, running DAMOS-based one
together and comparing the results would be useful, for debugging and
tuning.
Plan for Dropping RFC tag
=========================
Making changes for feedback from myself, humans and Sashiko should be
the major remaining work.
I'm currently hoping to drop the RFC tag by 7.2-rc1.
Future Works: Mid Term
========================
This version of implementation is limiting the maximum number of data
probes to four. I will try to find a way to remove the limit in future.
I personally think it should be enough for common use cases, though, and
therefore not giving high priority at the moment.
Future Works: Long Term
=======================
There are user requests for extending DAMON with detailed access
information, for example, per-CPUs/threads/read/writes monitoring. For
that, I was working [2] on extending DAMON to use page fault events as
another access check primitives, and making the infrastructure flexible
for future use of yet another access check primitive. Actually there is
another ongoing work [3] for extending DAMON with PMU events. The
motivation of the work is reducing the overhead, though.
In my work [2], I was introducing a new interface for access sampling
primitives control. Now I think this data probe interface can be used
for that, too. That is, data access becomes just one type of data
attribute. Also, pg_idle-confirmed access, page fault-confirmed access,
and PMU event-confirmed access will be different types of data
attributes.
The regions adjustment mechanism is currently working based on the
access information. That's because DAMON is designed for data access
monitoring. That is, data access information is the primary interest,
and therefore DAMON adjusts regions in a way that can best-present the
information.
Once data access becomes just one of data attributes, there is no reason
to think data access that special. There might be some users not
interested in access at all but want to know the location of memory of
specific type. Data probes interface will allow doing that. Further,
we could extend the interface to let users set any data attribute as the
'primary' attribute. Then, DAMON will split and merge regions in a way
that can best-present the 'primary' attributes.
DAMOS will also be extended, to specify targets based on not only the
data access pattern, but all user-registered data attributes. From this
stage, we may be able to call DAMON as a "Data Attributes Monitoring and
Operations eNgine".
[1] https://lore.kernel.org/20250106193401.109161-1-sj@kernel.org
[2] https://lore.kernel.org/20251208062943.68824-1-sj@kernel.org/
[3] https://lore.kernel.org/20260423004211.7037-1-akinobu.mita@gmail.com
Changes from RFC v2.2
- rfc v2.2: https://lore.kernel.org/20260515004433.128933-1-sj@kernel.org
- Rename damon_aggregated_v2 trace event to damon_region_aggregated.
- Address Sashiko issues.
- Enclose arguments on damon_for_each_{probe,filter}[_safe]() macros.
- Fix typos in comments and documents.
- Update probe_hits for region split and merge.
- Add more documentation for damon_operation->apply_probes() callback.
- Reduce unnecessary folio_{get,put}() in damon_pa_apply_probes().
- Define damon_sysfs_probe_attrs as static.
- Link scheme tried region sysfs dir and increase the count only after
all internal dir population success.
- Commit damon_filter->memcg_id for newly added filters.
Changes from RFC v2.1
- rfc v2.1: https://lore.kernel.org/20260514140904.119781-1-sj@kernel.org
- Rebase to mm-stable (7.1-rc3) to avoid Sashiko patch apply failure.
Changes from RFC v2
- rfc v2: https://lore.kernel.org/20260512143645.113201-1-sj@kernel.org
- Optimize nr_probes calculation for probe_hits tracepoint.
- Use TRACE_EVENT_CONDITION() for probe_hits tracepoint.
- Rebase to latest mm-new.
Changes from RFC
- rfc: https://lore.kernel.org/all/20260426205222.93895-1-sj@kernel.org/
- Support memcg DAMON filter.
- Use per-probe probe_hits sysfs file.
- Use dynamic_array for probe_hits tracing.
- Fix filter matching field.
- Fix folio leaking in damon_pa_filter_pass().
- Move nr_regions of damon_aggregated_v2 tracepoint after end.
- Rename DAMON_TEST_TYPE_ANON to DAMON_FILTER_TYPE_ANON.
SeongJae Park (28):
mm/damon/core: introduce struct damon_probe
mm/damon/core: embed damon_probe objects in damon_ctx
mm/damon/core: introduce damon_filter
mm/damon/core: commit probes
mm/damon/core: introduce damon_region->probe_hits
mm/damon/core: introduce damon_ops->apply_probes
mm/damon/core: do data attributes monitoring
mm/damon/paddr: support data attributes monitoring
mm/damon/sysfs: implement probes dir
mm/damon/sysfs: implement probe dir
mm/damon/sysfs: implement filters directory
mm/damon/sysfs: implement filter dir
mm/damon/sysfs: implement filter dir files
mm/damon/sysfs: setup probes on DAMON core API parameters
mm/damon/sysfs-schemes: implement tried_regions/<r>/probes/
mm/damon/sysfs-schemes: implement probe dir
mm/damon/sysfs-schemes: implement probe/hits file
mm/damon: trace probe_hits
selftests/damon/sysfs.sh: test probes dir
Docs/mm/damon/design: document data attributes monitoring
Docs/admin-guide/mm/damon/usage: document data attributes monitoring
mm/damon/core: introduce DAMON_FILTER_TYPE_MEMCG
mm/damon/paddr: support DAMON_FILTER_TYPE_MEMCG
mm/damon/sysfs: add filters/<F>/path file
mm/damon/sysfs-schemes: move memcg_path_to_id() to sysfs-common
mm/damon/sysfs: setup damon_filter->memcg_id from path
Docs/mm/damon/design: update for memcg damon filter
Docs/admin-guide/mm/damon/usage: update for memcg damon filter
Documentation/admin-guide/mm/damon/usage.rst | 46 +-
Documentation/mm/damon/design.rst | 39 ++
include/linux/damon.h | 69 +++
include/trace/events/damon.h | 38 ++
mm/damon/core.c | 211 +++++++
mm/damon/paddr.c | 76 +++
mm/damon/sysfs-common.c | 41 ++
mm/damon/sysfs-common.h | 2 +
mm/damon/sysfs-schemes.c | 226 ++++++--
mm/damon/sysfs.c | 557 +++++++++++++++++++
tools/testing/selftests/damon/sysfs.sh | 48 ++
11 files changed, 1305 insertions(+), 48 deletions(-)
base-commit: 5d6919055dec134de3c40167a490f33c74c12581
--
2.47.3
^ permalink raw reply
* Re: [PATCH v11 6/6] docs: iio: adc: ad4691: add driver documentation
From: David Lechner @ 2026-05-16 18:18 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, 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
In-Reply-To: <20260515-ad4692-multichannel-sar-adc-driver-v11-6-eab27d852ac2@analog.com>
On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add RST documentation for the AD4691 family ADC driver covering
> supported devices, IIO channels, operating modes, oversampling,
> reference voltage, LDO supply, reset, GP pins, SPI offload support,
> and buffer data format.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> Documentation/iio/ad4691.rst | 225 +++++++++++++++++++++++++++++++++++++++++++
> Documentation/iio/index.rst | 1 +
> MAINTAINERS | 1 +
> 3 files changed, 227 insertions(+)
>
> diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
> new file mode 100644
> index 000000000000..84492ef7a5d6
> --- /dev/null
> +++ b/Documentation/iio/ad4691.rst
> @@ -0,0 +1,225 @@
> +.. 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`` — per-channel effective output rate,
> + defined as the internal oscillator frequency divided by the channel's
> + oversampling ratio. Writing this attribute selects the nearest achievable
> + rate for the current OSR; the value read back reflects the actual rate after
> + snapping to the closest valid oscillator entry.
> +* ``in_voltageN_sampling_frequency_available`` — list of achievable effective
> + rates for the channel's current oversampling ratio. The list updates
> + dynamically when the oversampling ratio changes.
> +
> +The following attributes are only available in CNV Burst Mode:
> +
> +* ``in_voltageN_oversampling_ratio`` — per-channel hardware oversampling depth;
> + see `Oversampling`_ below.
> +* ``in_voltageN_oversampling_ratio_available`` — valid ratios: 1, 2, 4, 8, 16,
> + 32.
> +
> +
> +Operating modes
> +===============
> +
> +The driver supports two operating modes, selected automatically from the
> +device tree at probe time.
> +
> +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 triggers a conversion and
> +returns the previous result. A user-defined IIO trigger (e.g. hrtimer trigger)
> +drives the buffer.
> +
> +Oversampling is not supported in Manual Mode.
> +
> +CNV Burst Mode
> +--------------
> +
> +Selected when a ``pwms`` property is present in the device tree. A PWM drives
> +the CNV pin at the configured conversion rate. A GP pin wired to the SoC and
> +declared in the device tree signals DATA_READY at the end of each burst,
> +triggering a readout of all active channel results into the IIO buffer.
> +
> +The buffer output rate is controlled by the ``sampling_frequency`` attribute
> +on the IIO buffer. In practice the PWM rate should be set low enough to allow
> +the SPI readout to complete before the next conversion burst begins.
> +
> +Autonomous Mode (idle / single-shot)
> +-------------------------------------
> +
> +When the IIO buffer is disabled, ``in_voltageN_raw`` reads perform a single
> +conversion on the requested channel using the internal oscillator. The
> +oscillator is started and stopped around each read to save power.
> +
> +
> +Oversampling
> +============
> +
> +In CNV Burst Mode each channel has an independent hardware accumulator that
> +averages a configurable number of successive conversions. The result is always
> +returned as a 16-bit mean, so ``realbits`` and ``storagebits`` are unaffected
realbits and storagebits are driver implementation details. I would write this
in terms of userpace, which would be the buffer0/*_type attribute.
> +by the oversampling ratio. Valid ratios are 1, 2, 4, 8, 16 and 32; the default
> +is 1 (no averaging). Oversampling is not supported in Manual Mode.
> +
> +.. code-block:: bash
> +
> + # Set oversampling ratio to 16 on channel 0
> + echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ratio
> +
> + # Read the resulting effective sampling frequency
> + cat /sys/bus/iio/devices/iio:device0/in_voltage0_sampling_frequency
> +
> +Writing ``oversampling_ratio`` stores the new depth for that channel and
> +snaps the internal oscillator to the largest valid table entry that is both
> +less than or equal to ``old_effective_rate × new_osr`` and evenly divisible
> +by ``new_osr``. This preserves an integer read-back of
> +``in_voltageN_sampling_frequency`` after the change and keeps the oscillator
> +as close as possible to the previous effective rate.
> +
> +All channels share one internal oscillator. Writing ``sampling_frequency`` for
> +any channel updates the oscillator and therefore affects the effective rate
> +read back from all other channels.
> +
> +
> +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.
> +* **Buffered internal reference** (``refin-supply``): an internal reference
> + buffer is enabled by the driver.
> +
> +Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the
> +device tree. The reference voltage determines the full-scale range reported
> +via ``in_voltage_scale``.
> +
> +
> +LDO supply
> +==========
> +
> +The chip contains an internal LDO that powers part of the analog front-end.
> +The supply configuration is mutually exclusive:
> +
> +* **External VDD** (``vdd-supply``): an external 1.8 V supply is used directly;
> + the internal LDO is disabled.
> +* **Internal LDO** (``ldo-in-supply``): the internal LDO is enabled and fed
> + from the ``ldo-in`` regulator. Use this when no external 1.8 V VDD is present.
> +
> +Exactly one of ``vdd-supply`` or ``ldo-in-supply`` must be provided.
> +
> +
> +Reset
> +=====
> +
> +The driver supports two reset mechanisms:
> +
> +* **Hardware reset** (``reset-gpios`` in device tree): asserted at probe by
> + the reset controller framework.
> +* **Software reset** (fallback when ``reset-gpios`` is absent): written
> + automatically at probe.
> +
> +
> +GP pins and interrupts
> +======================
> +
> +The chip exposes up to four general-purpose (GP) pins. 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"``).
> +
> +Example device tree fragment::
> +
> + adc@0 {
> + compatible = "adi,ad4692";
> + ...
> + interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-parent = <&gpio0>;
Would be more logical to put interrupt-parent before interrupts.
> + 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; if no offload hardware is
> +available the driver falls back to the software triggered-buffer path.
> +
> +Two SPI offload sub-modes exist:
> +
> +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 offload engine reads all
> +active channel results and streams them directly to the IIO DMA buffer with no
> +CPU involvement. The GP pin used as DATA_READY trigger is supplied by the
> +trigger-source consumer at buffer enable time; no ``interrupt-names`` entry is
> +required.
> +
> +Manual offload
> +--------------
> +
> +Used when no ``pwms`` property is present and SPI offload is available. A
> +periodic SPI offload trigger controls the conversion rate and the offload engine
> +streams results directly to the IIO DMA buffer.
> +
> +The ``sampling_frequency`` attribute on the IIO buffer controls the trigger
> +rate (in Hz). The initial rate is 100 kHz.
> +
> +Oversampling is not supported in Manual Mode.
> +
> +
> +Buffer data format
> +==================
> +
> +The sample format in the IIO buffer depends on whether SPI offload is in use.
> +
> +Software triggered-buffer path (no SPI offload)
> +------------------------------------------------
> +
> +Each active channel occupies one 16-bit big-endian slot (``storagebits=16``,
> +``endianness=be``). Active channels are packed densely in scan-index order,
> +followed by a 64-bit software timestamp appended by the IIO core.
> +
> +SPI offload path
> +----------------
> +
> +Each active channel occupies one 16-bit CPU-native slot (``storagebits=16``,
> +``endianness=cpu``). The SPI offload engine streams 16-bit words directly from
> +the SPI Engine into the DMA buffer; no software timestamp is appended.
> 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
>
^ permalink raw reply
* Re: [PATCH v11 5/6] iio: adc: ad4691: add oversampling support
From: David Lechner @ 2026-05-16 18:10 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, 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
In-Reply-To: <20260515-ad4692-multichannel-sar-adc-driver-v11-5-eab27d852ac2@analog.com>
On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add per-channel oversampling ratio (OSR) support for CNV burst mode.
> The accumulator depth register (ACC_DEPTH_IN) is programmed with the
> selected OSR at buffer enable time and before each single-shot read.
>
> Supported OSR values: 1, 2, 4, 8, 16, 32.
>
> Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do
> not expose the oversampling_ratio attribute since OSR is not applicable
> in that mode. A separate manual_channels array is added to
> struct ad4691_channel_info and selected at probe time.
>
> in_voltageN_sampling_frequency represents the effective output rate for
> channel N, defined as osc_freq / osr[N]. The chip has one internal
> oscillator shared by all channels; each channel independently
> accumulates osr[N] oscillator cycles before producing a result.
>
> Writing sampling_frequency computes needed_osc = freq * osr[N] and
> snaps down to the largest oscillator table entry that satisfies both
> osc <= needed_osc and osc % osr[N] == 0, guaranteeing an exact integer
> read-back. The result is stored in target_osc_freq_Hz and written to
> OSC_FREQ_REG at buffer enable and single-shot time, so sampling_frequency
> and oversampling_ratio can be set in any order.
>
> in_voltageN_sampling_frequency_available is computed dynamically from
> the channel's current OSR, listing only oscillator table entries that
> divide evenly by osr[N], expressed as effective rates. The list becomes
> sparser as OSR increases, capping at max_rate / osr[N].
>
> Writing oversampling_ratio stores the new OSR for that channel and snaps
> target_osc_freq_Hz to the largest oscillator table entry that is both
> <= old_effective_rate * new_osr and evenly divisible by new_osr. This
> preserves an integer read-back of in_voltageN_sampling_frequency after
> the OSR change while keeping the oscillator as close as possible to the
> previous effective rate.
>
> OSR defaults to 1 (no accumulation) for all channels.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> drivers/iio/adc/ad4691.c | 381 ++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 343 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index 25f7a6939b0f..39244e0e4a2d 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -25,6 +25,7 @@
> #include <linux/reset.h>
> #include <linux/string.h>
> #include <linux/spi/spi.h>
> +#include <linux/types.h>
Out of order. Also probably belongs in earlier patch.
> #include <linux/spi/offload/consumer.h>
> #include <linux/spi/offload/provider.h>
> #include <linux/units.h>
> @@ -117,6 +118,7 @@ enum ad4691_ref_ctrl {
>
> struct ad4691_channel_info {
> const struct iio_chan_spec *channels __counted_by_ptr(num_channels);
> + const struct iio_chan_spec *manual_channels __counted_by_ptr(num_channels);
> unsigned int num_channels;
> };
>
> @@ -127,12 +129,39 @@ struct ad4691_chip_info {
> const struct ad4691_channel_info *offload_info;
> };
>
> +/* CNV burst mode channel — exposes oversampling ratio. */
> #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 = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_separate_available = \
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
> + 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', \
This field has new name: .format.
> + .realbits = 16, \
> + .storagebits = 16, \
> + .endianness = IIO_BE, \
> + }, \
> + }
> +
> +/*
> + * Manual mode channel — no oversampling ratio attribute. OSR is not
> + * supported in manual mode; ACC_DEPTH_IN is not configured during manual
> + * buffer enable.
> + */
> +#define AD4691_MANUAL_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), \
> @@ -151,8 +180,33 @@ struct ad4691_chip_info {
> * bits into native 16-bit words before DMA, so samples are in
> * CPU-native byte order (IIO_CPU). storagebits=16 matches the 16-bit
> * DMA word size.
> + *
> + * CNV burst offload configures ACC_DEPTH_IN per channel, so the
> + * oversampling_ratio attribute is exposed. Manual offload does not;
> + * use AD4691_OFFLOAD_MANUAL_CHANNEL for that path.
> */
> #define AD4691_OFFLOAD_CHANNEL(ch) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
> + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) \
> + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_separate_available = \
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) \
> + | 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, \
> + }, \
> + }
> +
> +/* Manual offload — same IIO_CPU layout but no oversampling_ratio attribute. */
> +#define AD4691_OFFLOAD_MANUAL_CHANNEL(ch) \
> { \
> .type = IIO_VOLTAGE, \
> .indexed = 1, \
> @@ -236,23 +290,91 @@ static const struct iio_chan_spec ad4693_offload_channels[] = {
> AD4691_OFFLOAD_CHANNEL(7),
> };
>
> +static const struct iio_chan_spec ad4691_manual_channels[] = {
> + AD4691_MANUAL_CHANNEL(0),
> + AD4691_MANUAL_CHANNEL(1),
> + AD4691_MANUAL_CHANNEL(2),
> + AD4691_MANUAL_CHANNEL(3),
> + AD4691_MANUAL_CHANNEL(4),
> + AD4691_MANUAL_CHANNEL(5),
> + AD4691_MANUAL_CHANNEL(6),
> + AD4691_MANUAL_CHANNEL(7),
> + AD4691_MANUAL_CHANNEL(8),
> + AD4691_MANUAL_CHANNEL(9),
> + AD4691_MANUAL_CHANNEL(10),
> + AD4691_MANUAL_CHANNEL(11),
> + AD4691_MANUAL_CHANNEL(12),
> + AD4691_MANUAL_CHANNEL(13),
> + AD4691_MANUAL_CHANNEL(14),
> + AD4691_MANUAL_CHANNEL(15),
> + IIO_CHAN_SOFT_TIMESTAMP(16),
> +};
> +
> +static const struct iio_chan_spec ad4693_manual_channels[] = {
> + AD4691_MANUAL_CHANNEL(0),
> + AD4691_MANUAL_CHANNEL(1),
> + AD4691_MANUAL_CHANNEL(2),
> + AD4691_MANUAL_CHANNEL(3),
> + AD4691_MANUAL_CHANNEL(4),
> + AD4691_MANUAL_CHANNEL(5),
> + AD4691_MANUAL_CHANNEL(6),
> + AD4691_MANUAL_CHANNEL(7),
> + IIO_CHAN_SOFT_TIMESTAMP(8),
> +};
> +
> +static const struct iio_chan_spec ad4691_offload_manual_channels[] = {
> + AD4691_OFFLOAD_MANUAL_CHANNEL(0),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(1),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(2),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(3),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(4),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(5),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(6),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(7),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(8),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(9),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(10),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(11),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(12),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(13),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(14),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(15),
> +};
> +
> +static const struct iio_chan_spec ad4693_offload_manual_channels[] = {
> + AD4691_OFFLOAD_MANUAL_CHANNEL(0),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(1),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(2),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(3),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(4),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(5),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(6),
> + AD4691_OFFLOAD_MANUAL_CHANNEL(7),
> +};
> +
> +static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
> +
> static const struct ad4691_channel_info ad4691_sw_info = {
> .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> .num_channels = ARRAY_SIZE(ad4691_channels),
> };
>
> static const struct ad4691_channel_info ad4693_sw_info = {
> .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> .num_channels = ARRAY_SIZE(ad4693_channels),
> };
>
> static const struct ad4691_channel_info ad4691_offload_info = {
> .channels = ad4691_offload_channels,
> + .manual_channels = ad4691_offload_manual_channels,
> .num_channels = ARRAY_SIZE(ad4691_offload_channels),
> };
>
> static const struct ad4691_channel_info ad4693_offload_info = {
> .channels = ad4693_offload_channels,
> + .manual_channels = ad4693_offload_manual_channels,
> .num_channels = ARRAY_SIZE(ad4693_offload_channels),
> };
>
> @@ -325,6 +447,19 @@ struct ad4691_state {
> int irq;
> int vref_uV;
> u32 cnv_period_ns;
> + /*
> + * Snapped oscillator frequency (Hz) shared by all channels. Set when
> + * sampling_frequency or oversampling_ratio is written; written to
> + * OSC_FREQ_REG at buffer enable and single-shot time so both attributes
> + * can be set in any order. Reading in_voltageN_sampling_frequency
> + * returns target_osc_freq_Hz / osr[N] — the effective rate for that
> + * channel given its oversampling ratio.
> + */
> + u32 target_osc_freq_Hz;
> + /* Per-channel oversampling ratio; always 1 in manual mode. */
> + u8 osr[16];
> + /* Scratch buffer for read_avail SAMP_FREQ; content is OSR-dependent. */
> + int samp_freq_avail[16][ARRAY_SIZE(ad4691_osc_freqs_Hz)];
Is there a *_MAX_CHANNELS macro to tell us what 16 is?
>
> bool manual_mode;
> bool refbuf_en;
> @@ -398,8 +533,7 @@ 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;
> + return type == SPI_OFFLOAD_TRIGGER_DATA_READY && nargs == 1 && args[0] <= 3;
unrelated change?
> }
>
> static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
> @@ -578,6 +712,16 @@ static const struct regmap_config ad4691_regmap_config = {
> .cache_type = REGCACHE_MAPLE,
> };
>
> +/* Write target_osc_freq_Hz to OSC_FREQ_REG. Called at use time. */
> +static int ad4691_write_osc_freq(struct ad4691_state *st)
> +{
> + for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> + if (ad4691_osc_freqs_Hz[i] == st->target_osc_freq_Hz)
> + return regmap_write(st->regmap, AD4691_OSC_FREQ_REG, i);
> + }
> + return -EINVAL;
> +}
> +
> /*
> * Index 0 in ad4691_osc_freqs_Hz is 1 MHz — valid only for AD4692/AD4694
> * (max_rate == 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid range
> @@ -588,41 +732,65 @@ static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info)
> return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
> }
>
> -static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
> +/*
> + * Find the largest oscillator table entry that is both <= needed_osc and
> + * evenly divisible by osr (guaranteeing an integer effective rate on
> + * read-back). Returns 0 if no such entry exists in the chip's valid range.
> + */
> +static unsigned int ad4691_find_osc_freq(struct ad4691_state *st,
> + unsigned int needed_osc,
> + unsigned int osr)
> {
> - unsigned int reg_val;
> - int ret;
> + unsigned int start = ad4691_samp_freq_start(st->info);
>
> - /*
> - * AD4691_OSC_FREQ_REG is non-volatile and written during
> - * ad4691_config(), so regmap returns the cached value here without
> - * touching the SPI bus. No lock is needed.
> - */
> - ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
> - if (ret)
> - return ret;
> + for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> + if ((unsigned int)ad4691_osc_freqs_Hz[i] > needed_osc)
> + continue;
> + if (ad4691_osc_freqs_Hz[i] % osr)
> + continue;
> + return ad4691_osc_freqs_Hz[i];
> + }
> + return 0;
> +}
>
> - *val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
> +static int ad4691_get_sampling_freq(struct ad4691_state *st, u8 osr, int *val)
> +{
> + *val = st->target_osc_freq_Hz / osr;
> return IIO_VAL_INT;
> }
>
> -static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int freq)
> {
> struct ad4691_state *st = iio_priv(indio_dev);
> - unsigned int start = ad4691_samp_freq_start(st->info);
> + unsigned int osr, found;
>
> IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> if (IIO_DEV_ACQUIRE_FAILED(claim))
> return -EBUSY;
>
> - for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> - if (ad4691_osc_freqs_Hz[i] != freq)
> - continue;
> - return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> - AD4691_OSC_FREQ_MASK, i);
> - }
> + /*
> + * Read osr under st->lock: osr[chan] and target_osc_freq_Hz are
> + * modified together under the lock; reading after acquiring it ensures
> + * we see a consistent snapshot with no concurrent write racing us.
> + */
> + guard(mutex)(&st->lock);
> + osr = st->osr[chan->channel];
>
> - return -EINVAL;
> + if (freq <= 0 || (unsigned int)freq > st->info->max_rate / osr)
> + return -EINVAL;
> +
> + found = ad4691_find_osc_freq(st, (unsigned int)freq * osr, osr);
> + if (!found)
> + return -EINVAL;
> +
> + /*
> + * Store the snapped oscillator frequency; OSC_FREQ_REG is written at
> + * buffer enable and single-shot time so that sampling_frequency and
> + * oversampling_ratio can be set in any order.
> + */
> + st->target_osc_freq_Hz = found;
> + return 0;
> }
>
> static int ad4691_read_avail(struct iio_dev *indio_dev,
> @@ -634,10 +802,46 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
> unsigned int start = ad4691_samp_freq_start(st->info);
>
> switch (mask) {
> - case IIO_CHAN_INFO_SAMP_FREQ:
> - *vals = &ad4691_osc_freqs_Hz[start];
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + unsigned int osr;
> + int n = 0;
> +
> + /*
> + * Hold the lock while reading osr[chan] and populating the
> + * scratch buffer: a concurrent oversampling_ratio write modifies
> + * both target_osc_freq_Hz and osr[] under the lock, so we must
> + * read osr atomically with respect to that write. The scratch
> + * buffer is per-channel, so concurrent reads on different
> + * channels do not race; concurrent reads on the same channel
> + * would compute identical values, but holding the lock avoids
> + * the formal data race.
> + */
> + scoped_guard(mutex, &st->lock) {
I'm not a fan of scposed_guard() in case statements because break would
break out of scoped_guard(), not case.
I would write it as:
{
guard(mutex)(&st->lock);
instead or put the critical section in a new function.
Or don't restrict the scope since the few assignments after the
critical section are not going to take a significant amount of
time. It won't hurt if they are done with the mutex held.
> + osr = st->osr[chan->channel];
> +
> + /*
> + * Only oscillator frequencies evenly divisible by the
> + * channel's OSR yield an integer effective rate; expose
> + * those as effective rates (osc / osr) so the user works
> + * entirely in output-sample space.
> + */
> + for (unsigned int i = start;
> + i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> + if (ad4691_osc_freqs_Hz[i] % osr)
> + continue;
> + st->samp_freq_avail[chan->channel][n++] =
> + ad4691_osc_freqs_Hz[i] / osr;
> + }
> + }
> + *vals = st->samp_freq_avail[chan->channel];
> *type = IIO_VAL_INT;
> - *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
> + *length = n;
> + 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;
> @@ -648,7 +852,7 @@ 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, osc_idx, period_us;
> + unsigned int reg_val, period_us;
> int ret;
>
> guard(mutex)(&st->lock);
> @@ -669,7 +873,12 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> if (ret)
> return ret;
>
> - ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
> + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->channel),
> + st->osr[chan->channel]);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_write_osc_freq(st);
> if (ret)
> return ret;
>
> @@ -677,9 +886,12 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> if (ret)
> return ret;
>
> - osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
> - /* Wait 2 oscillator periods for the conversion to complete. */
> - period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
> + /*
> + * Wait osr + 1 oscillator periods: osr for accumulation, +1 for the
> + * pipeline margin (one extra period ensures the final result is ready).
> + */
> + period_us = DIV_ROUND_UP((st->osr[chan->channel] + 1) * USEC_PER_SEC,
> + st->target_osc_freq_Hz);
> fsleep(period_us);
>
> ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> @@ -713,8 +925,21 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
>
> 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_SAMP_FREQ: {
> + /*
> + * Read target_osc_freq_Hz and osr[chan] under st->lock to get a
> + * consistent snapshot: write_raw for SAMP_FREQ or OSR modifies
> + * both fields under the lock, so a concurrent read without the
> + * lock could observe a new oscillator frequency with the old OSR.
> + */
> + guard(mutex)(&st->lock);
> + return ad4691_get_sampling_freq(st, st->osr[chan->channel], val);
> + }
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
> + guard(mutex)(&st->lock);
> + *val = st->osr[chan->channel];
> + return IIO_VAL_INT;
> + }
> case IIO_CHAN_INFO_SCALE:
> *val = st->vref_uV / (MICRO / MILLI);
> *val2 = chan->scan_type.realbits;
> @@ -728,9 +953,48 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int val, int val2, long mask)
> {
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> switch (mask) {
> case IIO_CHAN_INFO_SAMP_FREQ:
> - return ad4691_set_sampling_freq(indio_dev, val);
> + return ad4691_set_sampling_freq(indio_dev, chan, val);
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
> + unsigned int old_effective, found;
> + bool valid = false;
> +
> + for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i++) {
> + if (ad4691_oversampling_ratios[i] == val) {
> + valid = true;
> + break;
> + }
> + }
> + if (!valid)
> + return -EINVAL;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + /*
> + * Hold st->lock while computing the new oscillator frequency
> + * and updating both target_osc_freq_Hz and osr[chan] atomically:
> + * read_raw for SAMP_FREQ reads both fields under the lock and
> + * must see a consistent pair (new osc ↔ new osr).
> + *
> + * Snap target_osc_freq_Hz to the largest table entry that is
> + * both <= old_effective * new_osr and evenly divisible by
> + * new_osr, preserving an integer read-back of
> + * in_voltageN_sampling_frequency after the OSR change.
> + */
> + guard(mutex)(&st->lock);
> + old_effective = st->target_osc_freq_Hz / st->osr[chan->channel];
> + found = ad4691_find_osc_freq(st, old_effective * (unsigned int)val, val);
> + if (!found)
> + return -EINVAL;
> + st->target_osc_freq_Hz = found;
> + st->osr[chan->channel] = val;
> + return 0;
> + }
> default:
> return -EINVAL;
> }
> @@ -785,6 +1049,10 @@ static int ad4691_enter_conversion_mode(struct ad4691_state *st)
> return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
>
> + ret = ad4691_write_osc_freq(st);
> + if (ret)
> + return ret;
> +
> ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
> if (ret)
> @@ -948,6 +1216,14 @@ static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
> if (ret)
> goto err_unoptimize;
>
> + iio_for_each_active_channel(indio_dev, i) {
> + if (i >= indio_dev->num_channels - 1)
> + break; /* skip soft timestamp */
timestamp channel should be handled separately already.
> + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(i), st->osr[i]);
> + if (ret)
> + goto err_unoptimize;
> + }
> +
> ret = ad4691_enter_conversion_mode(st);
> if (ret)
> goto err_unoptimize;
> @@ -1126,6 +1402,14 @@ static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
> if (ret)
> return ret;
>
> + iio_for_each_active_channel(indio_dev, bit) {
> + if (bit >= indio_dev->num_channels)
> + break; /* defensive guard; offload channels have no soft timestamp */
really don't need it in this case.
> + 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;
> @@ -1524,6 +1808,8 @@ static int ad4691_config(struct ad4691_state *st)
> if (ret)
> return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
>
> + st->target_osc_freq_Hz = ad4691_osc_freqs_Hz[ad4691_samp_freq_start(st->info)];
> +
> ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> if (ret)
> @@ -1540,7 +1826,14 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> unsigned int i;
> int irq, ret;
>
> - indio_dev->channels = st->info->sw_info->channels;
> + /*
> + * Manual mode exposes channels without the oversampling_ratio attribute
> + * because ACC_DEPTH_IN is not configured in manual mode.
> + */
> + if (st->manual_mode)
> + indio_dev->channels = st->info->sw_info->manual_channels;
> + else
> + indio_dev->channels = st->info->sw_info->channels;
> indio_dev->num_channels = st->info->sw_info->num_channels;
> indio_dev->info = st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_burst_info;
>
> @@ -1621,7 +1914,18 @@ static int ad4691_setup_offload(struct iio_dev *indio_dev,
> offload->offload = spi_offload;
> st->offload = offload;
>
> - indio_dev->channels = st->info->offload_info->channels;
> + /*
> + * CNV burst offload exposes oversampling_ratio (ACC_DEPTH_IN is
> + * configured per channel at buffer enable). Manual offload does not
> + * configure ACC_DEPTH_IN, so it uses a separate channel array
> + * without the oversampling_ratio attribute. Both paths use IIO_CPU
> + * (no .endianness annotation) because bits_per_word=16 causes the
> + * SPI Engine to produce native 16-bit DMA words.
> + */
> + if (st->manual_mode)
> + indio_dev->channels = st->info->offload_info->manual_channels;
> + else
> + indio_dev->channels = st->info->offload_info->channels;
> indio_dev->num_channels = st->info->offload_info->num_channels;
> /*
> * Offload path uses DMA directly; no IIO trigger is involved, so
> @@ -1695,6 +1999,7 @@ static int ad4691_probe(struct spi_device *spi)
> st->info = spi_get_device_match_data(spi);
> if (!st->info)
> return -ENODEV;
> + memset(st->osr, 1, sizeof(st->osr));
>
> ret = devm_mutex_init(dev, &st->lock);
> if (ret)
>
^ permalink raw reply
* Re: [PATCH v11 4/6] iio: adc: ad4691: add SPI offload support
From: David Lechner @ 2026-05-16 17:53 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, 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
In-Reply-To: <20260515-ad4692-multichannel-sar-adc-driver-v11-4-eab27d852ac2@analog.com>
On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> 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 16-bit 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 16-bit frames (bits_per_word=16, len=2).
> The SPI Engine assembles received bits into native 16-bit words before
> DMA, so offload samples land in CPU-native byte order (IIO_CPU).
> Dedicated channel arrays (AD4691_OFFLOAD_CHANNEL) reflect this: they
> omit IIO_BE and carry no soft timestamp (DMA delivers data directly to
> userspace). The software triggered-buffer path retains its IIO_BE
> channels because bits_per_word=8 causes SPI to deliver bytes MSB-first
> into memory, making the on-disk layout big-endian. Both paths use
> storagebits=16 as transfers are 16 bits wide in both cases.
>
> IIO_BUFFER_DMAENGINE is selected because the offload path uses
> devm_iio_dmaengine_buffer_setup_with_handle() to allocate and
> attach the DMA RX buffer to the IIO device.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> drivers/iio/adc/Kconfig | 2 +
> drivers/iio/adc/ad4691.c | 458 ++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 457 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 484363458658..44c8dbe3ff0d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -144,8 +144,10 @@ config AD4691
> depends on SPI
> depends on REGULATOR || COMPILE_TEST
> select IIO_BUFFER
> + select IIO_BUFFER_DMAENGINE
> select IIO_TRIGGERED_BUFFER
> select REGMAP
> + select SPI_OFFLOAD
> help
> Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> SPI analog to digital converters (ADC).
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index bf27d5f33a49..25f7a6939b0f 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -25,10 +25,14 @@
> #include <linux/reset.h>
> #include <linux/string.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>
> @@ -44,6 +48,11 @@
>
> #define AD4691_CNV_DUTY_CYCLE_NS 380
> #define AD4691_CNV_HIGH_TIME_NS 430
> +/*
> + * Conservative default for the manual offload periodic trigger. Low enough
> + * to work safely out of the box across all OSR and channel count combinations.
> + */
> +#define AD4691_OFFLOAD_INITIAL_TRIGGER_HZ (100 * HZ_PER_KHZ)
>
> #define AD4691_SPI_CONFIG_A_REG 0x000
> #define AD4691_SW_RESET (BIT(7) | BIT(0))
> @@ -115,6 +124,7 @@ struct ad4691_chip_info {
> const char *name;
> unsigned int max_rate;
> const struct ad4691_channel_info *sw_info;
> + const struct ad4691_channel_info *offload_info;
> };
>
> #define AD4691_CHANNEL(ch) \
> @@ -136,6 +146,30 @@ struct ad4691_chip_info {
> }, \
> }
>
> +/*
> + * Offload path (bits_per_word=16): the SPI Engine assembles received
> + * bits into native 16-bit words before DMA, so samples are in
> + * CPU-native byte order (IIO_CPU). storagebits=16 matches the 16-bit
> + * DMA word size.
> + */
> +#define AD4691_OFFLOAD_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),
> @@ -168,6 +202,40 @@ static const struct iio_chan_spec ad4693_channels[] = {
> IIO_CHAN_SOFT_TIMESTAMP(8),
> };
>
> +/*
> + * Offload channel arrays: no IIO_CHAN_SOFT_TIMESTAMP because DMA delivers
> + * data directly to userspace without a software timestamp.
> + */
> +static const struct iio_chan_spec ad4691_offload_channels[] = {
> + AD4691_OFFLOAD_CHANNEL(0),
> + AD4691_OFFLOAD_CHANNEL(1),
> + AD4691_OFFLOAD_CHANNEL(2),
> + AD4691_OFFLOAD_CHANNEL(3),
> + AD4691_OFFLOAD_CHANNEL(4),
> + AD4691_OFFLOAD_CHANNEL(5),
> + AD4691_OFFLOAD_CHANNEL(6),
> + AD4691_OFFLOAD_CHANNEL(7),
> + AD4691_OFFLOAD_CHANNEL(8),
> + AD4691_OFFLOAD_CHANNEL(9),
> + AD4691_OFFLOAD_CHANNEL(10),
> + AD4691_OFFLOAD_CHANNEL(11),
> + AD4691_OFFLOAD_CHANNEL(12),
> + AD4691_OFFLOAD_CHANNEL(13),
> + AD4691_OFFLOAD_CHANNEL(14),
> + AD4691_OFFLOAD_CHANNEL(15),
> +};
> +
> +static const struct iio_chan_spec ad4693_offload_channels[] = {
> + AD4691_OFFLOAD_CHANNEL(0),
> + AD4691_OFFLOAD_CHANNEL(1),
> + AD4691_OFFLOAD_CHANNEL(2),
> + AD4691_OFFLOAD_CHANNEL(3),
> + AD4691_OFFLOAD_CHANNEL(4),
> + AD4691_OFFLOAD_CHANNEL(5),
> + AD4691_OFFLOAD_CHANNEL(6),
> + AD4691_OFFLOAD_CHANNEL(7),
> +};
> +
> static const struct ad4691_channel_info ad4691_sw_info = {
> .channels = ad4691_channels,
> .num_channels = ARRAY_SIZE(ad4691_channels),
> @@ -178,6 +246,16 @@ static const struct ad4691_channel_info ad4693_sw_info = {
> .num_channels = ARRAY_SIZE(ad4693_channels),
> };
>
> +static const struct ad4691_channel_info ad4691_offload_info = {
> + .channels = ad4691_offload_channels,
> + .num_channels = ARRAY_SIZE(ad4691_offload_channels),
> +};
> +
> +static const struct ad4691_channel_info ad4693_offload_info = {
> + .channels = ad4693_offload_channels,
> + .num_channels = ARRAY_SIZE(ad4693_offload_channels),
> +};
> +
> /*
> * 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
> @@ -208,24 +286,34 @@ static const struct ad4691_chip_info ad4691_chip_info = {
> .name = "ad4691",
> .max_rate = 500 * HZ_PER_KHZ,
> .sw_info = &ad4691_sw_info,
> + .offload_info = &ad4691_offload_info,
> };
>
> static const struct ad4691_chip_info ad4692_chip_info = {
> .name = "ad4692",
> .max_rate = 1 * HZ_PER_MHZ,
> .sw_info = &ad4691_sw_info,
> + .offload_info = &ad4691_offload_info,
> };
>
> static const struct ad4691_chip_info ad4693_chip_info = {
> .name = "ad4693",
> .max_rate = 500 * HZ_PER_KHZ,
> .sw_info = &ad4693_sw_info,
> + .offload_info = &ad4693_offload_info,
> };
>
> static const struct ad4691_chip_info ad4694_chip_info = {
> .name = "ad4694",
> .max_rate = 1 * HZ_PER_MHZ,
> .sw_info = &ad4693_sw_info,
> + .offload_info = &ad4693_offload_info,
> +};
> +
> +struct ad4691_offload_state {
> + struct spi_offload *offload;
> + struct spi_offload_trigger *trigger;
> + u64 trigger_hz;
> };
>
> struct ad4691_state {
> @@ -260,8 +348,11 @@ struct ad4691_state {
> struct spi_transfer scan_xfers[34];
> /*
> * CNV burst: 16 AVG_IN addresses = 16. Manual: 16 channel cmds +
> - * 1 NOOP = 17. Stored as native u16; put_unaligned_be16() fills each
> - * slot so the SPI controller (bits_per_word=8) sends bytes MSB-first.
> + * 1 NOOP = 17. Stored as native u16. The non-offload path fills slots
> + * with put_unaligned_be16() (bits_per_word=8, bytes go out in memory
> + * order). The offload path assigns native values directly
> + * (bits_per_word=bpw, SPI reads each slot as a native 16-bit word and
> + * shifts it out MSB-first).
> */
> u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN);
> /*
> @@ -277,6 +368,8 @@ struct ad4691_state {
> * DMA-aligned because scan_xfers point rx_buf directly into vals[].
> */
> IIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16);
> + /* NULL when no SPI offload hardware is present */
> + struct ad4691_offload_state *offload;
Watch out, this is in DMA area. It needs to be moved before first
DMA buffer.
> };
>
> /*
> @@ -296,6 +389,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)
Should probably also check nargs[0] <= 3 here.
> + return -EINVAL;
> +
> + return ad4691_gpio_setup(st, 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;
> @@ -873,6 +1006,222 @@ 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 bpw = indio_dev->channels[0].scan_type.realbits;
> + unsigned int bit, k;
> + int ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> + memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> + /*
> + * 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]
> + * RX: [data_hi, data_lo] (storagebits=16, shift=0)
> + * Transfer 0 RX is garbage; transfers 1..N carry real data.
> + * scan_tx is reused for TX commands (mutually exclusive with the
> + * non-offload triggered-buffer path).
> + *
> + * bits_per_word=bpw: the SPI controller reads tx_buf as a native
> + * 16-bit word and shifts it out MSB-first. Store the exact 16-bit
> + * value we want on the wire as a plain native u16 — no endianness
> + * macro — so the wire bytes are correct on both LE and BE hosts.
> + * The channel-select command is a single byte; shift it to the MSB
> + * position so SPI sends it first, with a zero pad in the LSB.
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, bit) {
> + st->scan_tx[k] = (u16)(AD4691_ADC_CHAN(bit) << 8);
cast isn't needed
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
odd to have a variable in sizeof(). Also works: sizeof(*st->scan_tx).
> + st->scan_xfers[k].bits_per_word = bpw;
> + st->scan_xfers[k].cs_change = 1;
> + st->scan_xfers[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS;
> + st->scan_xfers[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> + /* First transfer RX is garbage — skip it. */
> + if (k > 0)
> + st->scan_xfers[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + k++;
> + }
> +
> + /* Final NOOP transfer retrieves the last channel's result. */
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */
> + st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
ditto
> + st->scan_xfers[k].bits_per_word = bpw;
> + st->scan_xfers[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + k++;
> +
> + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, k);
> + st->scan_msg.offload = offload->offload;
> +
> + ret = spi_optimize_message(spi, &st->scan_msg);
> + if (ret)
> + goto err_exit_conversion;
> +
> + config.periodic.frequency_hz = offload->trigger_hz;
> + ret = spi_offload_trigger_enable(offload->offload, offload->trigger, &config);
> + if (ret)
> + goto err_unoptimize;
> +
> + return 0;
> +
> +err_unoptimize:
> + spi_unoptimize_message(&st->scan_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->offload, offload->trigger);
> + spi_unoptimize_message(&st->scan_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 bpw = indio_dev->channels[0].scan_type.realbits;
> + unsigned int acc_mask, std_seq_config;
> + unsigned int bit, k;
> + int ret;
> +
> + std_seq_config = bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)) & GENMASK(15, 0);
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config);
> + if (ret)
> + return ret;
> +
> + acc_mask = ~std_seq_config & GENMASK(15, 0);
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> + memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> + /*
> + * Each AVG_IN register read uses two transfers:
> + * TX: [reg_hi | 0x80, reg_lo] (address phase, CS stays asserted)
> + * RX: [data_hi, data_lo] (bpw-wide data phase, storagebits=16)
> + * Both TX and RX use bits_per_word=bpw: the SPI controller reads tx_buf
> + * as a native 16-bit word and shifts it out MSB-first. Store the exact
> + * 16-bit wire value as a plain native u16 — no endianness macro — so the
> + * wire bytes are correct on both LE and BE hosts. The read-address
> + * (0x8000 | reg) is already the 16-bit value we want on the wire.
> + * scan_tx is reused for TX addresses (mutually exclusive with the
> + * non-offload triggered-buffer path).
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, bit) {
> + st->scan_tx[k] = 0x8000 | AD4691_AVG_IN(bit);
> +
> + /* TX: address phase, CS stays asserted into data phase */
> + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[2 * k].len = sizeof(st->scan_tx[k]);
> + st->scan_xfers[2 * k].bits_per_word = bpw;
> +
> + /* RX: data phase, CS toggles after to delimit the next register op */
> + st->scan_xfers[2 * k + 1].len = sizeof(st->scan_tx[k]);
> + st->scan_xfers[2 * k + 1].bits_per_word = bpw;
> + st->scan_xfers[2 * k + 1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + st->scan_xfers[2 * k + 1].cs_change = 1;
> + k++;
> + }
> +
> + /*
> + * State reset: single 4-byte write [addr_hi, addr_lo, STATE_RESET_ALL,
> + * OSC_EN=1]. ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as
> + * a deliberate side-write, keeping the oscillator enabled.
> + * scan_tx_reset is shared with the non-offload path (len=4 here vs
> + * len=3 there) since the two paths are mutually exclusive at probe.
> + */
> + put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset);
> + st->scan_tx_reset[2] = AD4691_STATE_RESET_ALL;
> + st->scan_tx_reset[3] = 1;
> + st->scan_xfers[2 * k].tx_buf = st->scan_tx_reset;
> + st->scan_xfers[2 * k].len = sizeof(st->scan_tx_reset);
> + /*
> + * 4-byte u8 buffer assembled with put_unaligned_be16(); leave
> + * bits_per_word at the default (8) so bytes go out in memory order.
> + */
> +
> + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, 2 * k + 1);
> + st->scan_msg.offload = offload->offload;
> +
> + ret = spi_optimize_message(spi, &st->scan_msg);
> + if (ret)
> + goto err_exit_conversion;
> +
> + ret = spi_offload_trigger_enable(offload->offload, offload->trigger, &config);
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + goto err_disable_trigger;
> +
> + return 0;
> +
> +err_disable_trigger:
> + spi_offload_trigger_disable(offload->offload, offload->trigger);
> +err_unoptimize:
> + spi_unoptimize_message(&st->scan_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;
> +
> + ad4691_sampling_enable(st, false);
> + spi_offload_trigger_disable(offload->offload, offload->trigger);
> + spi_unoptimize_message(&st->scan_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)
> @@ -880,6 +1229,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", READ_ONCE(st->offload->trigger_hz));
Why do we need READ_ONCE?
> +
> return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns);
> }
>
> @@ -900,6 +1252,20 @@ static ssize_t sampling_frequency_store(struct device *dev,
> if (IIO_DEV_ACQUIRE_FAILED(claim))
> return -EBUSY;
>
> + 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;
> +
> + WRITE_ONCE(st->offload->trigger_hz, config.periodic.frequency_hz);
Why do we need WRITE_ONCE?
> + return len;
> + }
> +
> ret = ad4691_set_pwm_freq(st, freq);
> if (ret)
> return ret;
> @@ -1239,9 +1605,83 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> ad4691_buffer_attrs);
> }
>
> +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;
Why allocating this? It seems like it just makes a little extra work
for no reason (except maybe save a few bytes in the state struct when
not used, which doesn't seem worth it).
> +
> + offload->offload = spi_offload;
> + st->offload = offload;
> +
> + indio_dev->channels = st->info->offload_info->channels;
> + indio_dev->num_channels = st->info->offload_info->num_channels;
> + /*
> + * Offload path uses DMA directly; no IIO trigger is involved, so
> + * external triggers are not restricted (no validate_trigger).
> + */
> + indio_dev->info = &ad4691_manual_info;
> +
> + if (st->manual_mode) {
> + offload->trigger =
> + devm_spi_offload_trigger_get(dev, offload->offload,
> + 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 = AD4691_OFFLOAD_INITIAL_TRIGGER_HZ;
> + } 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->offload,
> + 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->offload);
> + 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;
> @@ -1277,10 +1717,20 @@ 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->modes = INDIO_DIRECT_MODE;
>
> - 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;
>
> @@ -1318,3 +1768,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");
>
^ permalink raw reply
* Re: [PATCH v3] Documentation: hwmon: lm75: document sysfs interface
From: Guenter Roeck @ 2026-05-16 17:43 UTC (permalink / raw)
To: Chen-Shi-Hong; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel
In-Reply-To: <20260516170728.2066-1-eric039eric@gmail.com>
On Sun, May 17, 2026 at 01:07:27AM +0800, Chen-Shi-Hong wrote:
> Document the sysfs attributes supported by the lm75 driver.
>
> The driver exposes temp1_input, temp1_max, temp1_max_hyst, and the
> standard update_interval attribute. Some chips also expose temp1_alarm,
> and temp1_label is available if a label is provided for the device.
>
> Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
> describe the supported attributes and clarify that temp1_alarm,
> temp1_label, and the write permissions of update_interval depend on the
> chip.
>
> Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>
When I tried to apply this patch, I noticed that a similar patch is
already queued in hwmon-next. Sorry that I didn't realize this earlier.
Guenter
> ---
> Changes in v2:
> - Document temp1_label as conditionally available when a device label is
> provided.
>
> Changes in v3:
> - Add changelog requested during review.
>
> Documentation/hwmon/lm75.rst | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
> index 4269da04508e..fa8ddcaa0c2b 100644
> --- a/Documentation/hwmon/lm75.rst
> +++ b/Documentation/hwmon/lm75.rst
> @@ -181,3 +181,28 @@ is supported by this driver, other specific enhancements are not.
>
> The LM77 is not supported, contrary to what we pretended for a long time.
> Both chips are simply not compatible, value encoding differs.
> +
> +sysfs-Interface
> +---------------
> +
> +================ ============================================
> +temp1_input temperature input
> +temp1_max maximum temperature
> +temp1_max_hyst maximum temperature hysteresis
> +================ ============================================
> +
> +If a label is provided for the device, the following attribute is also
> +available:
> +
> +================ ============================================
> +temp1_label temperature channel label
> +================ ============================================
> +
> +If supported by the chip, the following attribute is also available:
> +
> +================ ============================================
> +temp1_alarm temperature alarm
> +================ ============================================
> +
> +The standard update_interval attribute is also supported. Its write
> +permissions depend on the chip.
^ permalink raw reply
* Re: [PATCH v3 00/13] Improve process/maintainers output
From: Mauro Carvalho Chehab @ 2026-05-16 17:37 UTC (permalink / raw)
To: Jonathan Corbet
Cc: Mauro Carvalho Chehab, Miguel Ojeda, linux-doc, linux-kernel,
rust-for-linux, Björn Roy Baron, Alice Ryhl,
Andreas Hindborg, Andrew Morton, Benno Lossin, Boqun Feng,
Danilo Krummrich, Gary Guo, Joe Perches, Matteo Croce, Shuah Khan,
Trevor Gross
In-Reply-To: <87zf20rcjk.fsf@trenco.lwn.net>
On Fri, 15 May 2026 08:07:59 -0600
Jonathan Corbet <corbet@lwn.net> wrote:
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> writes:
>
> > Hi Jon,
> >
> > This series improve the output at process/maintainers: instead of a
> > pure enriched text, the maintainer's file content is now converted
> > to a table, and has gained a javascript to allow filtering entries.
>
> OK, I've applied it.
Thanks!
> I've wondered about including the MAINTAINERS
> stuff, but I must admit that the search box is kind of cool...
IMO, with the search box, this is now a lot more usable, and should
likely be helpful to the readers.
Thanks,
Mauro
^ permalink raw reply
* [PATCH 1/2] docs: maintainers_include: restore compatibility with Python 3.6
From: Mauro Carvalho Chehab @ 2026-05-16 17:33 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Shuah Khan
In-Reply-To: <cover.1778952682.git.mchehab+huawei@kernel.org>
glob root_dir parameter requires Python 3.10, which is more than
our current Python minimal requirement.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
Documentation/sphinx/maintainers_include.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/sphinx/maintainers_include.py b/Documentation/sphinx/maintainers_include.py
index be8e566e0363..b8b7282ebe38 100755
--- a/Documentation/sphinx/maintainers_include.py
+++ b/Documentation/sphinx/maintainers_include.py
@@ -143,7 +143,7 @@ class MaintainersParser:
if not m:
return None
- doc_list = glob(m.group(1), root_dir=self.base_dir)
+ doc_list = glob(os.path.join(self.base_dir, m.group(1)))
else:
doc_list = [text]
--
2.54.0
^ permalink raw reply related
* [PATCH 0/2] Two small cleanups to maintainers_include
From: Mauro Carvalho Chehab @ 2026-05-16 17:33 UTC (permalink / raw)
To: Jonathan Corbet, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-doc, linux-kernel, Shuah Khan
Hi Jon,
This series contain two minor cleanups to maintainers_include.py:
- make it backward compatible with Python < 3.10
(according with vermin, it should now be backward-compat up
to 3.6)
- keep "THE REST" at the end.
Mauro Carvalho Chehab (2):
docs: maintainers_include: restore compatibility with Python 3.6
docs: maintainers_include: keep the last entry at the end
Documentation/sphinx/maintainers_include.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH 2/2] docs: maintainers_include: keep the last entry at the end
From: Mauro Carvalho Chehab @ 2026-05-16 17:33 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Shuah Khan
In-Reply-To: <cover.1778952682.git.mchehab+huawei@kernel.org>
The last maintainer's entry ("THE REST") is meant to be at the
end. Ensure that.
While here, use a case-insensitive sort to avoid placing "iSCSI"
near the end.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
Documentation/sphinx/maintainers_include.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Documentation/sphinx/maintainers_include.py b/Documentation/sphinx/maintainers_include.py
index b8b7282ebe38..dc9f9e188ffa 100755
--- a/Documentation/sphinx/maintainers_include.py
+++ b/Documentation/sphinx/maintainers_include.py
@@ -284,7 +284,12 @@ class MaintainersInclude(Include):
self.state.document['maintainers_included'] = True
- for name, fields in sorted(maint_parser.maint_entries.items()):
+ # Keep the last entry ("THE REST") in the end
+ entries = list(maint_parser.maint_entries.keys())
+ entries = sorted(entries[:-1], key=str.casefold) + [entries[-1]]
+
+ for name in entries:
+ fields = maint_parser.maint_entries[name]
output += f" * - {name}\n"
tag = "-"
for field, lines in fields.items():
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox