* [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
* Re: [PATCH v11 3/6] iio: adc: ad4691: add triggered buffer support
From: David Lechner @ 2026-05-16 17:32 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-3-eab27d852ac2@analog.com>
On 5/15/26 8:31 AM, Radu Sabau via B4 Relay 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.
>
> 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>
> ---
> drivers/iio/adc/Kconfig | 2 +
> drivers/iio/adc/ad4691.c | 592 +++++++++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 580 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 5e601a87e5f3..484363458658 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -143,6 +143,8 @@ config AD4691
> tristate "Analog Devices AD4691 Family ADC Driver"
> depends on SPI
> depends on REGULATOR || COMPILE_TEST
> + select IIO_BUFFER
> + select IIO_TRIGGERED_BUFFER
> select REGMAP
> help
> Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index ba77e1bfef16..bf27d5f33a49 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -5,24 +5,35 @@
> */
> #include <linux/array_size.h>
> #include <linux/bitfield.h>
> -#include <linux/bitops.h>
> +#include <linux/bitmap.h>
> #include <linux/cleanup.h>
> #include <linux/delay.h>
> #include <linux/dev_printk.h>
> #include <linux/device/devres.h>
> +#include <linux/dmaengine.h>
Belongs in later patch, I think.
> #include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/kstrtox.h>
> #include <linux/limits.h>
> #include <linux/math.h>
> #include <linux/module.h>
> #include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> #include <linux/reset.h>
> +#include <linux/string.h>
> #include <linux/spi/spi.h>
> #include <linux/units.h>
> #include <linux/unaligned.h>
>
> +#include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
>
> #define AD4691_VREF_uV_MIN 2400000
> #define AD4691_VREF_uV_MAX 5250000
> @@ -31,6 +42,9 @@
> #define AD4691_VREF_3P3_uV_MAX 3750000
> #define AD4691_VREF_4P096_uV_MAX 4500000
>
> +#define AD4691_CNV_DUTY_CYCLE_NS 380
> +#define AD4691_CNV_HIGH_TIME_NS 430
> +
> #define AD4691_SPI_CONFIG_A_REG 0x000
> #define AD4691_SW_RESET (BIT(7) | BIT(0))
>
> @@ -38,6 +52,7 @@
> #define AD4691_CLAMP_STATUS1_REG 0x01A
> #define AD4691_CLAMP_STATUS2_REG 0x01B
> #define AD4691_DEVICE_SETUP 0x020
> +#define AD4691_MANUAL_MODE BIT(2)
> #define AD4691_LDO_EN BIT(4)
> #define AD4691_REF_CTRL 0x021
> #define AD4691_REF_CTRL_MASK GENMASK(4, 2)
> @@ -45,13 +60,18 @@
> #define AD4691_OSC_FREQ_REG 0x023
> #define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
> #define AD4691_STD_SEQ_CONFIG 0x025
> +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
> #define AD4691_SPARE_CONTROL 0x02A
>
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
> +
> #define AD4691_OSC_EN_REG 0x180
> #define AD4691_STATE_RESET_REG 0x181
> #define AD4691_STATE_RESET_ALL 0x01
> #define AD4691_ADC_SETUP 0x182
> #define AD4691_ADC_MODE_MASK GENMASK(1, 0)
> +#define AD4691_CNV_BURST_MODE 0x01
> #define AD4691_AUTONOMOUS_MODE 0x02
> /*
> * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
> @@ -61,6 +81,8 @@
> #define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
> #define AD4691_GPIO_MODE1_REG 0x196
> #define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GP_MODE_MASK GENMASK(3, 0)
> +#define AD4691_GP_MODE_DATA_READY 0x06
> #define AD4691_GPIO_READ 0x1A0
> #define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> #define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> @@ -110,6 +132,7 @@ struct ad4691_chip_info {
> .sign = 'u', \
> .realbits = 16, \
> .storagebits = 16, \
> + .endianness = IIO_BE, \
> }, \
> }
>
> @@ -130,6 +153,7 @@ static const struct iio_chan_spec ad4691_channels[] = {
> AD4691_CHANNEL(13),
> AD4691_CHANNEL(14),
> AD4691_CHANNEL(15),
> + IIO_CHAN_SOFT_TIMESTAMP(16),
> };
>
> static const struct iio_chan_spec ad4693_channels[] = {
> @@ -141,6 +165,17 @@ static const struct iio_chan_spec ad4693_channels[] = {
> AD4691_CHANNEL(5),
> AD4691_CHANNEL(6),
> AD4691_CHANNEL(7),
> + IIO_CHAN_SOFT_TIMESTAMP(8),
> +};
> +
> +static const struct ad4691_channel_info ad4691_sw_info = {
> + .channels = ad4691_channels,
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> +};
> +
> +static const struct ad4691_channel_info ad4693_sw_info = {
> + .channels = ad4693_channels,
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> };
>
> /*
> @@ -167,15 +202,7 @@ static const int ad4691_osc_freqs_Hz[] = {
> [0xF] = 1250,
> };
>
> -static const struct ad4691_channel_info ad4691_sw_info = {
> - .channels = ad4691_channels,
> - .num_channels = ARRAY_SIZE(ad4691_channels),
> -};
> -
> -static const struct ad4691_channel_info ad4693_sw_info = {
> - .channels = ad4693_channels,
> - .num_channels = ARRAY_SIZE(ad4693_channels),
> -};
Would be better to put this in the correct place in the first patch
to avoid noise of moving them.
> +static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
>
> static const struct ad4691_chip_info ad4691_chip_info = {
> .name = "ad4691",
> @@ -204,7 +231,14 @@ static const struct ad4691_chip_info ad4694_chip_info = {
> struct ad4691_state {
> const struct ad4691_chip_info *info;
> struct regmap *regmap;
> + struct spi_device *spi;
> +
> + struct pwm_device *conv_trigger;
> + int irq;
> int vref_uV;
> + u32 cnv_period_ns;
> +
> + bool manual_mode;
> bool refbuf_en;
> bool ldo_en;
> /*
> @@ -212,8 +246,56 @@ struct ad4691_state {
> * atomicity of consecutive SPI operations.
> */
> struct mutex lock;
> + /*
> + * Per-buffer-enable lifetime resources:
> + * Manual Mode - a pre-built SPI message that clocks out N+1
> + * transfers in one go.
> + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> + * transfers in one go.
> + */
> + struct spi_message scan_msg;
> + /*
> + * max 16 + 1 NOOP (manual) or 2*16 + 1 state-reset (CNV burst).
> + */
> + 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.
> + */
> + u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN);
> + /*
> + * CNV burst state-reset: 4-byte write [addr_hi, addr_lo,
> + * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so
> + * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a
> + * deliberate side-write, keeping the oscillator enabled. Shared
> + * with the offload path (mutually exclusive at probe).
> + */
> + u8 scan_tx_reset[4] __aligned(IIO_DMA_MINALIGN);
> + /*
> + * Scan buffer: one BE16 slot per active channel, plus timestamp.
> + * DMA-aligned because scan_xfers point rx_buf directly into vals[].
> + */
> + IIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16);
> };
>
> +/*
> + * Configure the given GP pin (0-3) as DATA_READY output.
> + * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
> + * Even pins occupy bits [3:0], odd pins bits [7:4].
> + */
> +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
> +{
> + unsigned int bit_off = gp_num % 2;
> + unsigned int reg_off = gp_num / 2;
> + unsigned int shift = 4 * bit_off;
> +
> + return regmap_update_bits(st->regmap,
> + AD4691_GPIO_MODE1_REG + reg_off,
> + AD4691_GP_MODE_MASK << shift,
> + AD4691_GP_MODE_DATA_READY << shift);
> +}
> +
> static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> {
> struct spi_device *spi = context;
> @@ -534,13 +616,405 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> return regmap_write(st->regmap, reg, writeval);
> }
>
> -static const struct iio_info ad4691_info = {
> +static int ad4691_set_pwm_freq(struct ad4691_state *st, unsigned int freq)
> +{
> + if (!freq)
> + return -EINVAL;
> +
> + st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
> + return 0;
> +}
> +
> +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
> +{
> + struct pwm_state conv_state = {
> + .period = st->cnv_period_ns,
> + .duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
> + .polarity = PWM_POLARITY_NORMAL,
> + .enabled = enable,
> + };
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
> +}
> +
> +/*
> + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
> + *
> + * Configures the ADC hardware registers for the mode selected at probe
> + * (CNV_BURST or MANUAL). Called from buffer preenable before starting
> + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
> + */
> +static int ad4691_enter_conversion_mode(struct ad4691_state *st)
> +{
> + int ret;
> +
> + if (st->manual_mode)
> + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
> +
> + ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> + AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> +}
> +
> +/*
> + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
> + *
> + * Called from buffer postdisable to restore the chip to the
> + * idle state used by read_raw. Clears the sequencer and resets state.
> + */
> +static int ad4691_exit_conversion_mode(struct ad4691_state *st)
> +{
> + if (st->manual_mode)
> + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_MANUAL_MODE, 0);
> +
> + return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> +}
> +
> +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int k, i;
> + int ret;
> +
> + memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> + memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> + spi_message_init(&st->scan_msg);
> +
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + if (i >= indio_dev->num_channels - 1)
> + break; /* skip soft timestamp */
I don't think timestamp gets set in the scan mask. It is handled separately.
> + /*
> + * Channel-select command occupies the first (high) byte of the
> + * 16-bit DIN frame; the second byte is a don't-care zero pad.
> + * put_unaligned_be16() writes [cmd, 0x00] in memory so the
> + * SPI controller sends the command byte first on the wire.
> + */
> + put_unaligned_be16((u16)(AD4691_ADC_CHAN(i) << 8), &st->scan_tx[k]);
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> + /*
> + * The pipeline means xfer[0] receives the residual from the
> + * previous sequence, not a valid sample. Discard it (rx_buf=NULL)
> + * to avoid aliasing vals[0] across two concurrent DMA mappings.
> + * xfer[1] (or the NOOP when only one channel is active) writes
> + * the real ch[0] result to vals[0]. Subsequent transfers write
> + * into vals[k-1] so each result lands at the next dense slot.
> + */
> + st->scan_xfers[k].rx_buf = (k == 0) ? NULL : &st->vals[k - 1];
> + st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
> + 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;
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> + 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].rx_buf = &st->vals[k - 1];
> + st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> +
> + ret = spi_optimize_message(st->spi, &st->scan_msg);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret) {
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ad4691_exit_conversion_mode(st);
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
> + .preenable = &ad4691_manual_buffer_preenable,
> + .postdisable = &ad4691_manual_buffer_postdisable,
> +};
> +
> +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int acc_mask, std_seq_config;
> + unsigned int k, i;
> + int ret;
> +
> + memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> + memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> + spi_message_init(&st->scan_msg);
> +
> + /*
> + * Each AVG_IN read needs two transfers: a 2-byte address write phase
> + * followed by a 2-byte data read phase. CS toggles between channels
> + * (cs_change=1 on the read phase of all but the last channel).
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + if (i >= indio_dev->num_channels - 1)
> + break; /* skip soft timestamp */
I don't think timestamp gets set in the scan mask. It is handled separately.
> + put_unaligned_be16(0x8000 | AD4691_AVG_IN(i), &st->scan_tx[k]);
> + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[2 * k].len = sizeof(st->scan_tx[k]);
> + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> + st->scan_xfers[2 * k + 1].rx_buf = &st->vals[k];
> + st->scan_xfers[2 * k + 1].len = sizeof(st->scan_tx[k]);
> + st->scan_xfers[2 * k + 1].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
> + k++;
> + }
> +
> + /*
> + * Append a 4-byte state-reset transfer [addr_hi, addr_lo,
> + * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so
> + * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a
> + * deliberate side-write, keeping the oscillator enabled.
> + * STATE_RESET_ALL starts the next burst; the hardware does not
> + * accumulate new conversions until after a STATE_RESET pulse, so
> + * no in-progress data is lost. No cs_change here — CS must
> + * deassert normally at end of message to frame the next command.
> + */
> + 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);
> + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> +
> + ret = spi_optimize_message(st->spi, &st->scan_msg);
> + if (ret)
> + return 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)
> + goto err_unoptimize;
> +
> + acc_mask = ~std_seq_config & GENMASK(15, 0);
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask);
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + goto err_unoptimize;
> +
> + return 0;
> +
> +err_unoptimize:
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> +}
> +
> +static int ad4691_cnv_burst_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + /*
> + * Start the PWM and unmask the IRQ here in postenable, not in
> + * preenable. The IIO core attaches the trigger poll function between
> + * preenable and postenable; enabling sampling or unmasking the IRQ
> + * before that point risks a DATA_READY assertion landing before the
> + * poll function is registered. iio_trigger_poll() would drop the
> + * event, disable_irq_nosync() would fire, and enable_irq() would
> + * never be called, leaving the IRQ permanently masked.
> + */
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + return ret;
> +
> + enable_irq(st->irq);
> + return 0;
> +}
> +
> +static int ad4691_cnv_burst_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + disable_irq(st->irq);
> + return ad4691_sampling_enable(st, false);
> +}
> +
> +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ad4691_exit_conversion_mode(st);
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
> + .preenable = &ad4691_cnv_burst_buffer_preenable,
> + .postenable = &ad4691_cnv_burst_buffer_postenable,
> + .predisable = &ad4691_cnv_burst_buffer_predisable,
> + .postdisable = &ad4691_cnv_burst_buffer_postdisable,
> +};
> +
> +static ssize_t sampling_frequency_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns);
> +}
> +
> +static ssize_t sampling_frequency_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int freq;
> + int ret;
> +
> + ret = kstrtouint(buf, 10, &freq);
> + if (ret)
> + return ret;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + ret = ad4691_set_pwm_freq(st, freq);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0);
> +
> +static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
> + &iio_dev_attr_sampling_frequency,
> + NULL
> +};
> +
> +static irqreturn_t ad4691_irq(int irq, void *private)
> +{
> + struct iio_dev *indio_dev = private;
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + /*
> + * Disable the IRQ before calling iio_trigger_poll(). The IRQ is
> + * re-enabled via the trigger .reenable callback, which the IIO core
> + * calls inside iio_trigger_notify_done() once use_count reaches zero.
> + * Re-enabling here (before notify_done) would race: a DATA_READY
> + * between enable_irq() and notify_done() calls iio_trigger_poll()
> + * while use_count > 0, dropping the event and permanently masking
> + * the IRQ.
> + *
> + * IRQF_ONESHOT masks the hardware line for the duration of this
> + * threaded handler; disable_irq_nosync() keeps the IRQ disabled even
> + * after IRQF_ONESHOT unmasks on return.
> + */
> + disable_irq_nosync(st->irq);
> + iio_trigger_poll(indio_dev->trig);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void ad4691_trigger_reenable(struct iio_trigger *trig)
> +{
> + struct ad4691_state *st = iio_trigger_get_drvdata(trig);
> +
> + enable_irq(st->irq);
> +}
> +
> +static const struct iio_trigger_ops ad4691_trigger_ops = {
> + .reenable = ad4691_trigger_reenable,
> + .validate_device = iio_trigger_validate_own_device,
> +};
> +
> +static int ad4691_read_scan(struct iio_dev *indio_dev, s64 ts)
return value is ignored, so this can be void.
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = spi_sync(st->spi, &st->scan_msg);
> + if (ret)
> + return ret;
> +
> + /*
> + * rx_buf pointers in scan_xfers point directly into scan.vals, so no
> + * copy is needed. The scan_msg already includes a STATE_RESET at the
> + * end (appended in preenable), so no explicit reset is needed here.
> + */
> + iio_push_to_buffers_with_ts(indio_dev, st->vals, sizeof(st->vals), ts);
> + return 0;
> +}
> +
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> +
> + ad4691_read_scan(indio_dev, pf->timestamp);
> + iio_trigger_notify_done(indio_dev->trig);
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * CNV burst mode: only allow our own trigger (driven by DATA_READY IRQ).
> + * Manual mode: external triggers (e.g. iio-trig-hrtimer) must be allowed
> + * because manual mode has no DATA_READY IRQ to fire the internal trigger.
> + * iio_trigger_ops.validate_device = iio_trigger_validate_own_device is
> + * correct in both modes — it prevents other devices from hijacking our
> + * internal trigger; the distinction here is only for iio_info.validate_trigger.
> + */
> +static const struct iio_info ad4691_cnv_burst_info = {
> + .read_raw = &ad4691_read_raw,
> + .write_raw = &ad4691_write_raw,
> + .read_avail = &ad4691_read_avail,
> + .debugfs_reg_access = &ad4691_reg_access,
> + .validate_trigger = iio_validate_own_trigger,
Inconsistent use of & on function pointer. (My personal preference
is to ommit & on all function pointers since it is not required.)
> +};
> +
> +static const struct iio_info ad4691_manual_info = {
> .read_raw = &ad4691_read_raw,
> .write_raw = &ad4691_write_raw,
> .read_avail = &ad4691_read_avail,
> .debugfs_reg_access = &ad4691_reg_access,
> };
>
> +static int ad4691_pwm_setup(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> +
> + st->conv_trigger = devm_pwm_get(dev, "cnv");
> + if (IS_ERR(st->conv_trigger))
> + return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
> + "Failed to get CNV PWM\n");
> +
> + return ad4691_set_pwm_freq(st, st->info->max_rate);
> +}
> +
> static int ad4691_regulator_setup(struct ad4691_state *st)
> {
> struct device *dev = regmap_get_device(st->regmap);
> @@ -623,6 +1097,22 @@ static int ad4691_config(struct ad4691_state *st)
> unsigned int val;
> int ret;
>
> + /*
> + * Determine buffer conversion mode from DT: if a PWM is provided it
> + * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS
> + * and each SPI transfer triggers a conversion (MANUAL_MODE).
> + * Both modes idle in AUTONOMOUS mode so that read_raw can use the
> + * internal oscillator without disturbing the hardware configuration.
> + */
> + if (device_property_present(dev, "pwms")) {
> + st->manual_mode = false;
> + ret = ad4691_pwm_setup(st);
> + if (ret)
> + return ret;
> + } else {
> + st->manual_mode = true;
> + }
> +
> switch (st->vref_uV) {
> case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
> ref_val = AD4691_VREF_2P5;
> @@ -676,6 +1166,79 @@ static int ad4691_config(struct ad4691_state *st)
> return 0;
> }
>
> +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);
> +
> + 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);
> + 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);
> +}
> +
> static int ad4691_probe(struct spi_device *spi)
> {
> struct device *dev = &spi->dev;
> @@ -688,6 +1251,7 @@ static int ad4691_probe(struct spi_device *spi)
> return -ENOMEM;
>
> st = iio_priv(indio_dev);
> + st->spi = spi;
> st->info = spi_get_device_match_data(spi);
> if (!st->info)
> return -ENODEV;
> @@ -714,11 +1278,11 @@ static int ad4691_probe(struct spi_device *spi)
> return ret;
>
> indio_dev->name = st->info->name;
> - indio_dev->info = &ad4691_info;
> indio_dev->modes = INDIO_DIRECT_MODE;
>
> - indio_dev->channels = st->info->sw_info->channels;
> - indio_dev->num_channels = st->info->sw_info->num_channels;
> + ret = ad4691_setup_triggered_buffer(indio_dev, st);
> + if (ret)
> + return ret;
>
> return devm_iio_device_register(dev, indio_dev);
> }
>
^ permalink raw reply
* Re: [PATCH] docs: fix typo in lenovo-wmi-other.rst
From: Randy Dunlap @ 2026-05-16 17:14 UTC (permalink / raw)
To: Cheesecake, Mark Pearson, Derek J. Clark, Armin Wolf,
Jonathan Corbet, Shuah Khan
Cc: platform-driver-x86, linux-doc, linux-kernel
In-Reply-To: <20260516075020.16745-1-cheesecake2960@icloud.com>
On 5/16/26 12:50 AM, Cheesecake wrote:
> Replace "Minumum" with "Minimum".
>
> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>
> ---
> Documentation/wmi/devices/lenovo-wmi-other.rst | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> index 01d471156..1d0410500 100644
> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> @@ -144,5 +144,5 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
> [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
> [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
> - [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
> + [WmiDataId(4), read, Description("Minimum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
> };
This is a well-known misspelling but apparently it's what the hardware/device
reports (or contains), so it should remain as is.
https://lore.kernel.org/platform-driver-x86/BAA4F3A7-E892-4904-95A6-64B177CDA7AD@gmail.com/
and
https://lore.kernel.org/platform-driver-x86/cfd7977e-d612-4e08-a68a-65fed8e164b6@gmx.de/
Looks like we should add a NOTE: there.
--
~Randy
^ permalink raw reply
* Re: [PATCH v11 2/6] iio: adc: ad4691: add initial driver for AD4691 family
From: David Lechner @ 2026-05-16 17:11 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-2-eab27d852ac2@analog.com>
On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> The chip idles in Autonomous Mode so that single-shot read_raw can use
> the internal oscillator without disturbing the hardware configuration.
>
> Three voltage supply domains are managed: avdd (required), vio, and a
> reference supply on either the REF pin (ref-supply, external buffer)
> or the REFIN pin (refin-supply, uses the on-chip reference buffer;
> REFBUF_EN is set accordingly). Hardware reset is performed via
> the reset controller framework; a software reset through SPI_CONFIG_A
> is used as fallback when no hardware reset is available.
>
> Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
> an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
> 16-bit transfer.
>
> IIO_CHAN_INFO_SAMP_FREQ is exposed as info_mask_separate. The oscillator
> is shared hardware — writing any channel's sampling_frequency attribute
> sets it for all others — but per-channel attributes are used throughout
> the series to avoid an ABI change when per-channel oversampling ratios
> are introduced in a later commit, at which point the effective output
> rate (osc_freq / osr[N]) becomes genuinely per-channel.
>
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 12 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ad4691.c | 756 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 770 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 438ca850fa1c..24e4502b8292 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
> S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> +F: drivers/iio/adc/ad4691.c
>
> ANALOG DEVICES INC AD4695 DRIVER
> M: Michael Hennerich <michael.hennerich@analog.com>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 60038ae8dfc4..5e601a87e5f3 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -139,6 +139,18 @@ config AD4170_4
> To compile this driver as a module, choose M here: the module will be
> called ad4170-4.
>
> +config AD4691
> + tristate "Analog Devices AD4691 Family ADC Driver"
> + depends on SPI
> + depends on REGULATOR || COMPILE_TEST
> + select REGMAP
> + help
> + Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> + SPI analog to digital converters (ADC).
> +
> + To compile this driver as a module, choose M here: the module will be
> + called ad4691.
> +
> config AD4695
> tristate "Analog Device AD4695 ADC Driver"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index c76550415ff1..4ac1ea09d773 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
> obj-$(CONFIG_AD4130) += ad4130.o
> obj-$(CONFIG_AD4134) += ad4134.o
> obj-$(CONFIG_AD4170_4) += ad4170-4.o
> +obj-$(CONFIG_AD4691) += ad4691.o
> obj-$(CONFIG_AD4695) += ad4695.o
> obj-$(CONFIG_AD4851) += ad4851.o
> obj-$(CONFIG_AD7091R) += ad7091r-base.o
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> new file mode 100644
> index 000000000000..ba77e1bfef16
> --- /dev/null
> +++ b/drivers/iio/adc/ad4691.c
> @@ -0,0 +1,756 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2024-2026 Analog Devices, Inc.
> + * Author: Radu Sabau <radu.sabau@analog.com>
> + */
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/dev_printk.h>
> +#include <linux/device/devres.h>
> +#include <linux/err.h>
> +#include <linux/limits.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/spi/spi.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#define AD4691_VREF_uV_MIN 2400000
> +#define AD4691_VREF_uV_MAX 5250000
> +#define AD4691_VREF_2P5_uV_MAX 2750000
> +#define AD4691_VREF_3P0_uV_MAX 3250000
> +#define AD4691_VREF_3P3_uV_MAX 3750000
> +#define AD4691_VREF_4P096_uV_MAX 4500000
> +
> +#define AD4691_SPI_CONFIG_A_REG 0x000
> +#define AD4691_SW_RESET (BIT(7) | BIT(0))
> +
> +#define AD4691_STATUS_REG 0x014
> +#define AD4691_CLAMP_STATUS1_REG 0x01A
> +#define AD4691_CLAMP_STATUS2_REG 0x01B
> +#define AD4691_DEVICE_SETUP 0x020
> +#define AD4691_LDO_EN BIT(4)
> +#define AD4691_REF_CTRL 0x021
> +#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
> +#define AD4691_REFBUF_EN BIT(0)
> +#define AD4691_OSC_FREQ_REG 0x023
> +#define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
> +#define AD4691_STD_SEQ_CONFIG 0x025
> +#define AD4691_SPARE_CONTROL 0x02A
> +
> +#define AD4691_OSC_EN_REG 0x180
> +#define AD4691_STATE_RESET_REG 0x181
> +#define AD4691_STATE_RESET_ALL 0x01
> +#define AD4691_ADC_SETUP 0x182
> +#define AD4691_ADC_MODE_MASK GENMASK(1, 0)
> +#define AD4691_AUTONOMOUS_MODE 0x02
> +/*
> + * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
> + * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte.
> + */
> +#define AD4691_ACC_MASK_REG 0x185
> +#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
> +#define AD4691_GPIO_MODE1_REG 0x196
> +#define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GPIO_READ 0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
> +#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
> +
> +static const char * const ad4691_supplies[] = { "avdd", "vio" };
> +
> +enum ad4691_ref_ctrl {
> + AD4691_VREF_2P5 = 0,
> + AD4691_VREF_3P0 = 1,
> + AD4691_VREF_3P3 = 2,
> + AD4691_VREF_4P096 = 3,
> + AD4691_VREF_5P0 = 4,
nit: when I see explicit values, I expect a gap or something odd.
When don't find any, I wasted my time reading what are the default
implicit values anyway.
> +};
> +
> +struct ad4691_channel_info {
> + const struct iio_chan_spec *channels __counted_by_ptr(num_channels);
> + unsigned int num_channels;
> +};
> +
> +struct ad4691_chip_info {
> + const char *name;
> + unsigned int max_rate;
> + const struct ad4691_channel_info *sw_info;
> +};
> +
> +#define AD4691_CHANNEL(ch) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
> + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_separate_available = \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = ch, \
> + .scan_index = ch, \
> + .scan_type = { \
> + .sign = 'u', \
FYI, this has a new name now, .format.
> + .realbits = 16, \
> + .storagebits = 16, \
> + }, \
> + }
> +
> +static const struct iio_chan_spec ad4691_channels[] = {
> + AD4691_CHANNEL(0),
> + AD4691_CHANNEL(1),
> + AD4691_CHANNEL(2),
> + AD4691_CHANNEL(3),
> + AD4691_CHANNEL(4),
> + AD4691_CHANNEL(5),
> + AD4691_CHANNEL(6),
> + AD4691_CHANNEL(7),
> + AD4691_CHANNEL(8),
> + AD4691_CHANNEL(9),
> + AD4691_CHANNEL(10),
> + AD4691_CHANNEL(11),
> + AD4691_CHANNEL(12),
> + AD4691_CHANNEL(13),
> + AD4691_CHANNEL(14),
> + AD4691_CHANNEL(15),
> +};
> +
> +static const struct iio_chan_spec ad4693_channels[] = {
> + AD4691_CHANNEL(0),
> + AD4691_CHANNEL(1),
> + AD4691_CHANNEL(2),
> + AD4691_CHANNEL(3),
> + AD4691_CHANNEL(4),
> + AD4691_CHANNEL(5),
> + AD4691_CHANNEL(6),
> + AD4691_CHANNEL(7),
> +};
> +
> +/*
> + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
> + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
> + * up to 500 kHz and use index 1 as their highest valid rate.
> + */
> +static const int ad4691_osc_freqs_Hz[] = {
> + [0x0] = 1000000,
> + [0x1] = 500000,
> + [0x2] = 400000,
> + [0x3] = 250000,
> + [0x4] = 200000,
> + [0x5] = 167000,
> + [0x6] = 133000,
> + [0x7] = 125000,
> + [0x8] = 100000,
> + [0x9] = 50000,
> + [0xA] = 25000,
> + [0xB] = 12500,
> + [0xC] = 10000,
> + [0xD] = 5000,
> + [0xE] = 2500,
> + [0xF] = 1250,
> +};
> +
> +static const struct ad4691_channel_info ad4691_sw_info = {
> + .channels = ad4691_channels,
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> +};
> +
> +static const struct ad4691_channel_info ad4693_sw_info = {
> + .channels = ad4693_channels,
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> +};
> +
> +static const struct ad4691_chip_info ad4691_chip_info = {
> + .name = "ad4691",
> + .max_rate = 500 * HZ_PER_KHZ,
> + .sw_info = &ad4691_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4692_chip_info = {
> + .name = "ad4692",
> + .max_rate = 1 * HZ_PER_MHZ,
> + .sw_info = &ad4691_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4693_chip_info = {
> + .name = "ad4693",
> + .max_rate = 500 * HZ_PER_KHZ,
> + .sw_info = &ad4693_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4694_chip_info = {
> + .name = "ad4694",
> + .max_rate = 1 * HZ_PER_MHZ,
> + .sw_info = &ad4693_sw_info,
> +};
> +
> +struct ad4691_state {
> + const struct ad4691_chip_info *info;
> + struct regmap *regmap;
> + int vref_uV;
> + bool refbuf_en;
> + bool ldo_en;
> + /*
> + * Synchronize access to members of the driver state, and ensure
> + * atomicity of consecutive SPI operations.
> + */
> + struct mutex lock;
> +};
> +
> +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + struct spi_device *spi = context;
> + u8 tx[2], rx[4];
> + int ret;
> +
> + /* Set bit 15 to mark the operation as READ. */
Can't we just set read_flag_mask in the regmap config?
> + put_unaligned_be16(0x8000 | reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
> + case AD4691_ACC_MASK_REG + 1 ... AD4691_ACC_SAT_OVR_REG(15):
> + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 1);
> + if (ret)
> + return ret;
> + *val = rx[0];
> + return 0;
> + case AD4691_ACC_MASK_REG:
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 2);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be16(rx);
> + return 0;
> + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 3);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be24(rx);
> + return 0;
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 4);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be32(rx);
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct spi_device *spi = context;
> + u8 tx[4];
> +
> + put_unaligned_be16(reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
> + case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
> + if (val > U8_MAX)
> + return -EINVAL;
> + tx[2] = val;
> + return spi_write_then_read(spi, tx, 3, NULL, 0);
> + case AD4691_ACC_MASK_REG:
> + case AD4691_STD_SEQ_CONFIG:
> + if (val > U16_MAX)
> + return -EINVAL;
> + put_unaligned_be16(val, &tx[2]);
> + return spi_write_then_read(spi, tx, 4, NULL, 0);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case AD4691_STATUS_REG:
> + case AD4691_CLAMP_STATUS1_REG:
> + case AD4691_CLAMP_STATUS2_REG:
> + case AD4691_GPIO_READ:
> + case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
> + case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
> + return true;
> + default:
> + break;
> + }
> +
> + /*
> + * Multi-byte registers have non-unit strides; only accept base
> + * addresses to prevent debugfs from triggering reads that cross
> + * register boundaries.
> + */
> + if (reg >= AD4691_AVG_IN(0) && reg <= AD4691_AVG_IN(15))
> + return (reg - AD4691_AVG_IN(0)) % 2 == 0;
> + if (reg >= AD4691_AVG_STS_IN(0) && reg <= AD4691_AVG_STS_IN(15))
> + return (reg - AD4691_AVG_STS_IN(0)) % 3 == 0;
> + if (reg >= AD4691_ACC_IN(0) && reg <= AD4691_ACC_IN(15))
> + return (reg - AD4691_ACC_IN(0)) % 3 == 0;
> + if (reg >= AD4691_ACC_STS_DATA(0) && reg <= AD4691_ACC_STS_DATA(15))
> + return (reg - AD4691_ACC_STS_DATA(0)) % 4 == 0;
> +
> + return false;
> +}
> +
> +static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + case AD4691_STD_SEQ_CONFIG:
> + return true;
> + default:
> + break;
> + }
> +
> + /* Multi-byte registers: only accept base addresses (see volatile_reg). */
> + if (reg >= AD4691_AVG_IN(0) && reg <= AD4691_AVG_IN(15))
> + return (reg - AD4691_AVG_IN(0)) % 2 == 0;
> + if (reg >= AD4691_AVG_STS_IN(0) && reg <= AD4691_AVG_STS_IN(15))
> + return (reg - AD4691_AVG_STS_IN(0)) % 3 == 0;
> + if (reg >= AD4691_ACC_IN(0) && reg <= AD4691_ACC_IN(15))
> + return (reg - AD4691_ACC_IN(0)) % 3 == 0;
> + if (reg >= AD4691_ACC_STS_DATA(0) && reg <= AD4691_ACC_STS_DATA(15))
> + return (reg - AD4691_ACC_STS_DATA(0)) % 4 == 0;
> +
> + return false;
> +}
> +
> +static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static const struct regmap_config ad4691_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 32,
> + .reg_read = ad4691_reg_read,
> + .reg_write = ad4691_reg_write,
> + .volatile_reg = ad4691_volatile_reg,
> + .readable_reg = ad4691_readable_reg,
> + .writeable_reg = ad4691_writeable_reg,
> + .max_register = AD4691_ACC_STS_DATA(15),
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +/*
> + * 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
> + * starts at index 1.
> + */
> +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)
> +{
> + unsigned int reg_val;
> + int ret;
> +
No mutex lock here? Maybe without OK since it is a read.
> + /*
> + * 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;
> +
> + *val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
> + return IIO_VAL_INT;
> +}
> +
> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int start = ad4691_samp_freq_start(st->info);
> +
> + 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;
mutex lock?
> + return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> + AD4691_OSC_FREQ_MASK, i);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int ad4691_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type,
> + int *length, long mask)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int start = ad4691_samp_freq_start(st->info);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = &ad4691_osc_freqs_Hz[start];
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
> + return IIO_AVAIL_LIST;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int reg_val, osc_idx, period_us;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + /* Use AUTONOMOUS mode for single-shot reads. */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + BIT(chan->channel));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> + ~BIT(chan->channel) & GENMASK(15, 0));
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> + 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]);
> + fsleep(period_us);
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
> + if (ret)
> + return ret;
> +
> + *val = reg_val;
> +
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int ad4691_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long info)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + switch (info) {
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + return ad4691_single_shot_read(indio_dev, chan, val);
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ad4691_get_sampling_freq(st, val);
> + case IIO_CHAN_INFO_SCALE:
> + *val = st->vref_uV / (MICRO / MILLI);
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
Should we aquire direct mode so that we can't change the rate during
buffered read?
> + return ad4691_set_sampling_freq(indio_dev, val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> + unsigned int writeval, unsigned int *readval)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + if (readval)
> + return regmap_read(st->regmap, reg, readval);
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ad4691_info = {
> + .read_raw = &ad4691_read_raw,
> + .write_raw = &ad4691_write_raw,
> + .read_avail = &ad4691_read_avail,
> + .debugfs_reg_access = &ad4691_reg_access,
> +};
> +
> +static int ad4691_regulator_setup(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + int ret;
> +
> + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies),
> + ad4691_supplies);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable supplies\n");
> +
> + /*
> + * vdd-supply and ldo-in-supply are mutually exclusive:
> + * vdd-supply present → external 1.8V VDD; disable internal LDO.
> + * vdd-supply absent → enable internal LDO fed from ldo-in-supply.
> + * Having both simultaneously is strongly inadvisable per the datasheet.
> + */
> + ret = devm_regulator_get_enable_optional(dev, "vdd");
> + if (ret == -ENODEV) {
After some recent discussions, we've been leaning towards using
if (device_property_present(dev, "vdd-supply") instead of handling
-ENODEV.
I think it reads more nicely in a linear way here:
if (device_property_present(dev, "vdd-supply") {
/* do stuff with vdd supply */
} else if device_property_present(dev, "ldo-in-supply") {
/* do stuff with ldo-in supply */
} else {
return dev_err_probe(dev, -EINVAL, "missing one of vdd-supply, ldo-in-supply\n");
}
> + ret = devm_regulator_get_enable(dev, "ldo-in");
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to get and enable LDO-IN\n");
> + st->ldo_en = true;
> + } else if (ret) {
> + return dev_err_probe(dev, ret, "Failed to get and enable VDD\n");
> + }
> +
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
> + if (st->vref_uV == -ENODEV) {
similar with ref-supply and refin-supply
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
> + st->refbuf_en = true;
> + }
> + if (st->vref_uV < 0)
> + return dev_err_probe(dev, st->vref_uV,
> + "Failed to get reference supply\n");
> +
> + if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
> + return dev_err_probe(dev, -EINVAL,
> + "vref(%d) must be in the range [%u...%u]\n",
> + st->vref_uV, AD4691_VREF_uV_MIN,
> + AD4691_VREF_uV_MAX);
> +
> + return 0;
> +}
> +
> +static int ad4691_reset(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct reset_control *rst;
> +
> + rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(rst))
> + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
> +
> + if (rst) {
> + /*
> + * Assert the reset line before sleeping to guarantee a proper
> + * reset pulse on every probe, including driver reloads where
> + * the line may already be deasserted (reset_control_put() does
> + * not re-assert on release).
> + * devm_reset_control_get_optional_exclusive_deasserted() cannot
> + * be used because it deasserts immediately without delay; the
> + * datasheet (Table 5) requires a ≥300 µs reset pulse width
> + * before deassertion.
> + */
> + reset_control_assert(rst);
> + fsleep(300);
> + return reset_control_deassert(rst);
> + }
> +
> + /* No hardware reset available, fall back to software reset. */
> + return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
> + AD4691_SW_RESET);
> +}
> +
> +static int ad4691_config(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + enum ad4691_ref_ctrl ref_val;
> + unsigned int val;
> + int ret;
> +
> + switch (st->vref_uV) {
> + case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
> + ref_val = AD4691_VREF_2P5;
> + break;
> + case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX:
> + ref_val = AD4691_VREF_3P0;
> + break;
> + case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX:
> + ref_val = AD4691_VREF_3P3;
> + break;
> + case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX:
> + ref_val = AD4691_VREF_4P096;
> + break;
> + case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX:
> + ref_val = AD4691_VREF_5P0;
> + break;
> + default:
> + return dev_err_probe(dev, -EINVAL,
> + "Unsupported vref voltage: %d uV\n",
> + st->vref_uV);
> + }
> +
> + val = FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val);
> + if (st->refbuf_en)
> + val |= AD4691_REFBUF_EN;
> +
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL, val);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> +
> + ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_LDO_EN, st->ldo_en);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n");
> +
> + /*
> + * Set the internal oscillator to the highest rate this chip supports.
> + * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
> + * chips start at index 1 (500 kHz).
> + */
> + ret = regmap_write(st->regmap, AD4691_OSC_FREQ_REG,
> + ad4691_samp_freq_start(st->info));
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
> +
> + ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
> +
> + return 0;
> +}
> +
> +static int ad4691_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad4691_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> + st->info = spi_get_device_match_data(spi);
> + if (!st->info)
> + return -ENODEV;
We've recently standardized on not checking return value
of spi_get_device_match_data().
> +
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
> + st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "Failed to initialize regmap\n");
> +
> + ret = ad4691_regulator_setup(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_reset(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_config(st);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = st->info->name;
> + indio_dev->info = &ad4691_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + indio_dev->channels = st->info->sw_info->channels;
> + indio_dev->num_channels = st->info->sw_info->num_channels;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static const struct of_device_id ad4691_of_match[] = {
> + { .compatible = "adi,ad4691", .data = &ad4691_chip_info },
> + { .compatible = "adi,ad4692", .data = &ad4692_chip_info },
> + { .compatible = "adi,ad4693", .data = &ad4693_chip_info },
> + { .compatible = "adi,ad4694", .data = &ad4694_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, ad4691_of_match);
> +
> +static const struct spi_device_id ad4691_id[] = {
> + { "ad4691", (kernel_ulong_t)&ad4691_chip_info },
> + { "ad4692", (kernel_ulong_t)&ad4692_chip_info },
> + { "ad4693", (kernel_ulong_t)&ad4693_chip_info },
> + { "ad4694", (kernel_ulong_t)&ad4694_chip_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, ad4691_id);
> +
> +static struct spi_driver ad4691_driver = {
> + .driver = {
> + .name = "ad4691",
> + .of_match_table = ad4691_of_match,
> + },
> + .probe = ad4691_probe,
> + .id_table = ad4691_id,
> +};
> +module_spi_driver(ad4691_driver);
> +
> +MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
> +MODULE_LICENSE("GPL");
>
^ permalink raw reply
* Re: [PATCH] docs: fix typos in design.rst
From: SeongJae Park @ 2026-05-16 17:08 UTC (permalink / raw)
To: Cheesecake
Cc: SeongJae Park, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
damon, linux-mm, linux-doc, linux-kernel
In-Reply-To: <20260516093552.8404-1-cheesecake2960@icloud.com>
Hello Cheesecake,
Thank you for this patch!
For the consistency, let's use 'Docs/mm/damon/design:' as the prefix of the
subject. E.g., Docs/mm/damon/design: fix three typos
On Sat, 16 May 2026 18:35:37 +0900 Cheesecake <cheesecake2960@icloud.com> wrote:
> L140: "unsinged" -> "unsigned"
> L371: "sampleing" -> "sampling"
> L387: "multipled" -> "multiplied"
Thank you for finding and fixing these!
>
> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>
Is Cheesecake your real name or known identity? We don't allow anonymous
contributions [1], and mm community prefer to use real names.
[...]
The file changes look good.
Could you please send v2 of this patch with changed subject and the name (if
Cheesecake is not your real name or known identity)?
[1] https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
Thanks,
SJ
^ permalink raw reply
* [PATCH v3] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 17:07 UTC (permalink / raw)
To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
Chen-Shi-Hong
In-Reply-To: <20260516160823.1461-1-eric039eric@gmail.com>
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>
---
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.
--
2.53.0
^ permalink raw reply related
* Re: [RFC PATCH v3] bpf: introduce TAINT_UNSAFE_BPF for mutating helpers
From: Aaron Tomlin @ 2026-05-16 17:01 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Steven Rostedt, Jonathan Corbet, Song Liu, KP Singh,
Matt Bobrowski, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard, Kumar Kartikeya Dwivedi,
Masami Hiramatsu, Shuah Khan, Jiri Olsa, Martin KaFai Lau,
Yonghong Song, Mathieu Desnoyers, Randy Dunlap, neelx, sean,
chjohnst, steve, mproche, nick.lange, open list:DOCUMENTATION,
LKML, bpf, linux-trace-kernel
In-Reply-To: <CAADnVQLw+_NaOVeaKabuf085wNo_-6MAv8w0EDO3fBz3KCQT5g@mail.gmail.com>
On Wed, May 13, 2026 at 09:35:29AM -0700, Alexei Starovoitov wrote:
> On Wed, May 13, 2026 at 8:23 AM Steven Rostedt <rostedt@goodmis.org> wrote:
> >
> > On Wed, 13 May 2026 08:16:07 -0700
> > Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:
> >
> > > It's impossible to track all modifications.
> > > See what sched-ext is doing.
> > > What does it modify? Everything.
> >
> > What about just having a list of what BPF programs are loaded, what they
> > may be attached to, and what kfuncs they are calling?
>
> Ohh. These have been available forever.
> Just bpftool prog, bpftool link, bpftool prog dump xlated
Hi Alexei,
Thank you for sharing.
Kind regards,
--
Aaron Tomlin
^ permalink raw reply
* Re: [PATCH v2] Documentation: hwmon: lm75: document sysfs interface
From: Guenter Roeck @ 2026-05-16 16:48 UTC (permalink / raw)
To: Chen-Shi-Hong; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel
In-Reply-To: <20260516164022.1792-1-eric039eric@gmail.com>
On 5/16/26 09:40, 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>
Change log is missing.
Yes, I do see Sashiko's reply, but that is not an excuse for neglecting
to provide a change log.
Guenter
> ---
> 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
* [PATCH v2] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 16:40 UTC (permalink / raw)
To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
Chen-Shi-Hong
In-Reply-To: <20260516160823.1461-1-eric039eric@gmail.com>
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>
---
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.
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 1/3] Doc: deprecated.rst: add strlcat()
From: David Laight @ 2026-05-16 16:35 UTC (permalink / raw)
To: Heiko Carstens
Cc: Kees Cook, Manuel Ebner, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, Andy Whitcroft, Joe Perches, Dwaipayan Ray,
Lukas Bulwahn, Geert Uytterhoeven, Randy Dunlap, Jani Nikula,
open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
open list
In-Reply-To: <20260516152819.14597A76-hca@linux.ibm.com>
On Sat, 16 May 2026 17:28:19 +0200
Heiko Carstens <hca@linux.ibm.com> wrote:
> On Thu, May 14, 2026 at 09:31:46AM -0700, Kees Cook wrote:
> > On Thu, May 14, 2026 at 06:26:53PM +0200, Manuel Ebner wrote:
> > > add strlcat and alternatives
> > >
> > > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> > > ---
> > > Documentation/process/deprecated.rst | 7 +++++++
> > > 1 file changed, 7 insertions(+)
> > >
> > > diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> > > index fed56864d036..06e802f4bbfd 100644
> > > --- a/Documentation/process/deprecated.rst
> > > +++ b/Documentation/process/deprecated.rst
> > > @@ -153,6 +153,13 @@ used, and the destinations should be marked with the `__nonstring
> > > attribute to avoid future compiler warnings. For cases still needing
> > > NUL-padding, strtomem_pad() can be used.
> > >
> > > +strlcat()
> > > +---------
> > > +strlcat() must re-scan the destination string from the beginning on each
> > > +call (O(n^2) behavior). Alternatives are seq_buf_puts() and seq_buf_printf().
> > > +snprintf(), scnprintf() and sysfs_emit() are possible aswell, but the adoption
> > > +of the arguments needs to be taken care off.
> > > +
> >
> > How about just:
> >
> > strlcat() must re-scan the destination string from the beginning on each
> > call (O(n^2) behavior). Use the seq_buf API or similar instead.
>
> seq_buf API for appending something to e.g. boot_command_line seems to be odd,
> since boot_command_line is usually "just there" (depending on architecture and
> boot loader).
Indeed, but ISTR that code uses strcat() a lot of the time.
The lengths are all known, so memcpy() can be used.
I don't really see why strlcat() should be deprecated.
Clearly there are many cases where there are better ways to do things.
The only problem with strlcat() is that it returns the 'required length'.
So there are some broken uses.
- fs/nfs/flexfilelayout/flexfilelayout.c
- lib/kunit/string-stream.c (although the preceding vsnprintf() looks like the actual bug).
There is also some very strange code in security/selinus/ima.c - but it may be ok.
In reality the return value of strlcat() isn't really much worse that that
of snprintf().
-- David
>
> So if I would remove strlcat() from appending something to boot_command_line I
> would end up open-coding strlcat(), including the chance for the usual
> off-by-one bugs. Looks like this would be true for nearly all architectures.
>
> Is performance really the only reason to deprecate strlcat()? This seems to be
> a bit questionable to me.
^ permalink raw reply
* Re: [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors
From: Andrea Mayer @ 2026-05-16 16:25 UTC (permalink / raw)
To: Yuya Kusakabe
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Justin Iurman, Shuah Khan, Jonathan Corbet,
Shuah Khan, linux-kernel, netdev, linux-kselftest, linux-doc,
stefano.salsano, ahabdels, Andrea Mayer
In-Reply-To: <20260505-seg6-mobile-v2-0-9e8022bdfdb6@gmail.com>
On Tue, 05 May 2026 01:30:10 +0900
Yuya Kusakabe <yuya.kusakabe@gmail.com> wrote:
Hi Yuya,
Thanks for the work. Some comments on the overall design below. I will
reply on the individual patches separately.
> This series adds the in-kernel data path for the SRv6 Mobile User
> Plane (MUP) architecture defined in RFC 9433. SRv6 MUP integrates
> GTP-U mobile traffic into an SRv6 transport domain by mapping the
> 5-tuple (TEID, QFI, R, U, PDU Session ID) into a single SID, allowing
> operators to replace the GTP-U overlay between the gNB and the
> upstream UPF with native SRv6 forwarding while keeping the radio side
> unchanged.
>
> The series implements the six MUP behaviors that an SRv6 MUP gateway
> typically needs:
>
> End.MAP (RFC 9433 Section 6.2) -- swap DA with the next SID
> without consuming the SRH
> End.M.GTP6.D (Section 6.3) -- IPv6/GTP-U to SRv6 headend encap
> End.M.GTP6.D.Di (Section 6.4) -- drop-in mode variant of the above
> (preserves the original outer DA at
> SRH[0] and discards TEID/QFI)
> End.M.GTP6.E (Section 6.5) -- SRv6 to IPv6/GTP-U egress encap
> End.M.GTP4.E (Section 6.6) -- SRv6 to IPv4/GTP-U egress encap
> H.M.GTP4.D (Section 6.7) -- IPv4/GTP-U to SRv6 headend encap
RFC 9433 Section 6 is titled "SRv6 Segment Endpoint Mobility Behaviors",
but Section 6.7 defines H.M.GTP4.D as "SR Policy Headend with tunnel
decapsulation and map to an SRv6 policy". This behavior receives IPv4
packets and is not bound to any SID, so it does not fit the endpoint
model that seg6_local implements. Placing it there required relaxing the
ETH_P_IPV6 guard to accept ETH_P_IP and adding input_family to
seg6_action_desc, for a single behavior that does not share the endpoint
model.
seg6_local is not the natural place for this behavior. The UAPI cannot
be undone once merged, so where it should live needs discussion on the
list before we proceed.
>
> End.Limit (RFC 9433 Section 6.8) is intentionally out of scope.
>
> All behaviors plug into the existing seg6_local lwtunnel framework, so
> they are configurable through the standard "ip route ... encap
> seg6local action ..." interface.
This adds ~2.2k lines to seg6_local.c, bringing it from ~2.7k to ~5k
lines. The behaviors reuse the existing seg6_local infrastructure (SRH
validation, seg6_do_srh_encap, seg6_lookup_nexthop), while the GTP-U
parsing, encapsulation, and PDU Session handling, for example, is new
and self-contained. Given the volume, moving the MUP code into a
separate seg6_mobile.c (say CONFIG_IPV6_SEG6_MUP) would keep seg6_local
focused on the RFC 8986 endpoint framework. There are trade-offs
either way and I think this deserves discussion on the list.
There is significant duplication across the introduced behaviors: for
example the GTP-U parsing, inner protocol dispatch, and NF_HOOK plumbing
are nearly identical each time, and the small differences are already
hiding issues (HMAC validation missing in some, malformed SRH silently
accepted in others, wrong drop reasons). Some of the individual
functions are also well over 100 lines. Splitting them with helpers
would help.
The new code also has some style drift from seg6_local.c (variable
declaration ordering, scope blocks, blank lines) that should be fixed.
I think this patchset should be broken into smaller patchsets, one per
behavior, each with the behavior, its selftest, and any needed helpers as
separate patches. The same approach was used for End.DT4/End.DT6 and
End.DT46. End.M.GTP4.E alone is ~1.2k lines in a single diff.
> No new netlink families are
> introduced -- the new SEG6_LOCAL_MOBILE_* attributes extend
> SEG6_LOCAL_MAX in an add-only way, and the new SEG6_LOCAL_ACTION_*
> values are appended.
The attribute layout and semantics are probably the most sensitive part
of the series and need to be settled before it can go in, since the UAPI
cannot be changed once merged.
The series reuses SEG6_LOCAL_NH6, SEG6_LOCAL_SRH and SEG6_LOCAL_OIF with
semantics that differ from the existing behaviors. NH6 today means
next-hop in End.X/DX6. This series reuses it as DA replacement in
End.MAP and as prefix template in H.M.GTP4.D.
SRH is inserted verbatim in End.B6/B6.Encaps but augmented per-packet in
the mobile behaviors. These attributes have established UAPI semantics
from their existing behaviors. Giving them a different meaning in new
behaviors is a UAPI semantic divergence.
The selftests use OIF on all five GTP behaviors to select a VRF for the
lookup, but that is what TABLE and VRFTABLE are for (End.DT4, End.DT6).
OIF in the existing behaviors means output interface (End.X) or L2
egress device (End.DX2). VRF support is a nice-to-have that can be added
later as a separate optional attribute.
>
> The egress behaviors (End.M.GTP4.E and End.M.GTP6.E) accept an
> optional per-route pdu_type attribute that is the sole control
> for inserting the GTP-U PDU Session Container (3GPP TS 38.415 Section
> 5.5.2). When pdu_type is set (dl/ul/0..15), every emitted GTP-U
> packet carries the container with that PDU Type and the QFI extracted
> from Args.Mob.Session. When pdu_type is unset, the egress emits
> a short GTPv1-U header with no container. pdu_type must be
> configured on egress routes serving 5G N3 traffic; omitting it is
> intended only for LTE-only / S1-U-style deployments where no PDU
> Session Container is exchanged.
>
> The matching iproute2 patch series has been posted to iproute2-next:
> https://lore.kernel.org/netdev/20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com/
The user-facing parameter names and their semantics are defined in the
iproute2 series (where the man page lives), so that is probably the
better place to discuss keyword choices and attribute naming.
> [snip]
> include/net/dropreason-core.h | 40 +
The series introduces six mobile-specific drop reasons. Drop reasons are
visible to userspace via the kfree_skb tracepoint (perf, bpftrace,
dropwatch), so they look like a kind of interface and their names should
match the actual failure.
BAD_SID and BAD_GTPU make sense as mobile-specific reasons. The other
four have issues: NOMEM and MTU_EXCEEDED duplicate existing generic
reasons (NOMEM and PKT_TOO_BIG). INVALID_SRH_SL is used for multiple SRH
validation failures, not just Segments Left (HMAC failure is also
reported as INVALID_SRH_SL). BAD_INNER is used as a catch-all for outer
header pull failures and encapsulation errors, neither of which involves
the inner payload.
We could think about a prep patch introducing SRv6-level drop reasons
(SEG6_INVALID_SRH, SEG6_HMAC, etc.) that both the existing behaviors and
the MUP ones can share.
> [snip]
> .../selftests/net/srv6_end_m_gtp4_e_test.sh | 486 ++++
> .../selftests/net/srv6_end_m_gtp6_d_di_test.sh | 427 ++++
> .../selftests/net/srv6_end_m_gtp6_d_test.sh | 497 ++++
> .../selftests/net/srv6_end_m_gtp6_e_test.sh | 402 +++
> tools/testing/selftests/net/srv6_end_map_test.sh | 103 +
> .../testing/selftests/net/srv6_h_m_gtp4_d_test.sh | 487 ++++
Selftests for the five GTP behaviors heavily depend on python3 and scapy
heredocs embedded in the shell scripts for packet construction and
validation, which adds an external runtime dependency. A statically
compiled C helper would remove it and avoid embedding python heredocs
in shell scripts.
A few cases worth covering: SRH and no-SRH input paths where the
behavior accepts both, missing SRH where the behavior requires
it, malformed SRH, and invalid attribute values.
The five GTP behaviors pass per-packet context to a finish callback through
skb->cb, and recover the lwtstate via skb_dst(skb)->lwtstate.
When nf_hooks_lwtunnel is enabled, an NF_HOOK sits between the input
handler and the finish callback. Netfilter processing at this hook can then
corrupt the cb (IPCB/IP6CB aliases skb->cb) or drop/replace the skb dst, so
the finish callback dereferences a NULL or unrelated dst.
Even if the dst is preserved, the cb may have been modified.
The dst problem is pre-existing from 7a3f5b0de364 ("netfilter: add
netfilter hooks to SRv6 data plane") and affects seg6_iptunnel too. Both
issues need a robust fix before this series can go in. I want to look at
this myself and will Cc you when I do, as the five new behaviors may need
to be adjusted on top.
> [snip]
> Best regards,
> --
> Yuya Kusakabe <yuya.kusakabe@gmail.com>
Ciao,
Andrea
^ permalink raw reply
* [PATCH v3] docs: kernel-parameters: document scope of irqaffinity= parameter
From: Aaron Tomlin @ 2026-05-16 16:28 UTC (permalink / raw)
To: corbet, skhan
Cc: tglx, akpm, bp, rdunlap, dave.hansen, feng.tang,
pawan.kumar.gupta, dapeng1.mi, kees, elver, paulmck, lirongqing,
bhelgaas, bigeasy, bagasdotme, linux-doc, linux-kernel
There is a common misconception that the "irqaffinity=" boot parameter
acts as a global override for all hardware interrupts. In reality, it
only sets the irq_default_affinity mask, which is explicitly ignored
by managed interrupts (e.g., modern multiqueue storage controllers).
This patch updates kernel-parameters.txt to document this limitation,
directs users to "isolcpus=managed_irq" and
Documentation/core-api/irq/managed_irq.rst for further details.
Additionally, it updates managed_irq.rst to provide a debugfs example
demonstrating the IRQD_AFFINITY_MANAGED state flag.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
Changes since v2 [1]:
- Reworded the debugfs explanation to explicitly distinguish between
active and offline managed IRQ states
- Clarified that the shutdown flags (IRQD_IRQ_DISABLED,
IRQD_IRQ_MASKED, etc.) only appear when the CPU is offlined to
resolve the previous contradiction with the example output
- Explicitly labeled the debugfs snippet as an "active" managed
interrupt
- Used double-colon for literal code block (Bagas Sanjaya)
Changes since v1 [2]:
- Provided an example of a managed IRQ using CONFIG_GENERIC_IRQ_DEBUGFS
- Referenced Documentation/core-api/irq/managed_irq.rst
[1]: https://lore.kernel.org/lkml/20260421150911.42404-1-atomlin@atomlin.com/
[2]: https://lore.kernel.org/lkml/20260414200245.1153919-1-atomlin@atomlin.com/
---
.../admin-guide/kernel-parameters.txt | 11 ++++
Documentation/core-api/irq/managed_irq.rst | 60 ++++++++++++++++++-
2 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index cf3807641d89..365c4931700a 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2726,6 +2726,17 @@ Kernel parameters
irqaffinity= [SMP] Set the default irq affinity mask
The argument is a cpu list, as described above.
+ Note: This parameter only sets the default affinity
+ for unmanaged interrupts (e.g., legacy single-queue
+ devices or unmanaged pre/post vectors). It is
+ explicitly ignored by managed interrupts, such as
+ those utilised by modern multiqueue storage
+ controllers. To isolate CPUs from managed
+ interrupts, see "isolcpus=managed_irq".
+
+ For further details see:
+ Documentation/core-api/irq/managed_irq.rst
+
irqchip.gicv2_force_probe=
[ARM,ARM64,EARLY]
Format: <bool>
diff --git a/Documentation/core-api/irq/managed_irq.rst b/Documentation/core-api/irq/managed_irq.rst
index 05e295f3c289..649f7f7fb0ac 100644
--- a/Documentation/core-api/irq/managed_irq.rst
+++ b/Documentation/core-api/irq/managed_irq.rst
@@ -80,9 +80,63 @@ The following examples assume a system with 8 CPUs.
/proc/irq/48/effective_affinity_list:0
/proc/irq/48/smp_affinity_list:7
- This can be verified via the debugfs interface
- (/sys/kernel/debug/irq/irqs/48). The dstate field will include
- IRQD_IRQ_DISABLED, IRQD_IRQ_MASKED and IRQD_MANAGED_SHUTDOWN.
+ If the Linux kernel was built with Kconfig CONFIG_GENERIC_IRQ_DEBUGFS
+ enabled, this can be verified via the debugfs interface (e.g.,
+ /sys/kernel/debug/irq/irqs/48).
+
+ A managed IRQ will always include IRQD_AFFINITY_MANAGED in its dstate.
+ Furthermore, when the associated CPU is offlined, the dstate field will
+ also include IRQD_IRQ_DISABLED, IRQD_IRQ_MASKED, and IRQD_MANAGED_SHUTDOWN,
+ verifying that the interrupt was cleanly shut down rather than migrated.
+
+ For example, an active managed interrupt might look like this::
+
+ # cat /sys/kernel/debug/irq/irqs/87
+ handler: handle_edge_irq
+ device: 0000:41:00.0
+ status: 0x00000000
+ istate: 0x00004000
+ ddepth: 0
+ wdepth: 0
+ dstate: 0x19601200
+ IRQD_ACTIVATED
+ IRQD_IRQ_STARTED
+ IRQD_SINGLE_TARGET
+ IRQD_AFFINITY_SET
+ IRQD_AFFINITY_MANAGED
+ IRQD_AFFINITY_ON_ACTIVATE
+ IRQD_HANDLE_ENFORCE_IRQCTX
+ node: 0
+ affinity: 3
+ effectiv: 3
+ pending:
+ domain: IR-PCI-MSIX-0000:41:00.0-12
+ hwirq: 0x8
+ chip: IR-PCI-MSIX-0000:41:00.0
+ flags: 0x430
+ IRQCHIP_SKIP_SET_WAKE
+ IRQCHIP_ONESHOT_SAFE
+
+ address_hi: 0x00000000
+ address_lo: 0xfee00000
+ msg_data: 0x00000008
+ parent:
+ domain: AMD-IR-3-14
+ hwirq: 0x41000000
+ chip: AMD-IR
+ flags: 0x0
+ parent:
+ domain: VECTOR
+ hwirq: 0x57
+ chip: APIC
+ flags: 0x0
+ Vector: 33
+ Target: 3
+ move_in_progress: 0
+ is_managed: 1
+ can_reserve: 0
+ has_reserved: 0
+ cleanup_pending: 0
- A QEMU instance is booted with "-device virtio-scsi-pci,num_queues=2"
and the kernel command line includes:
--
2.51.0
^ permalink raw reply related
* Re: [PATCH RESEND bpf-next v10 2/8] bpf: clear list node owner and unlink before drop
From: Kaitao Cheng @ 2026-05-16 16:18 UTC (permalink / raw)
To: Eduard Zingerman, Alexei Starovoitov
Cc: bpf, linux-kernel, linux-doc, ast, memxor, corbet, martin.lau,
daniel, andrii, song, yonghong.song, john.fastabend, kpsingh, sdf,
haoluo, jolsa, shuah, chengkaitao, skhan, vmalik, linux-kselftest,
martin.lau, clm, ihor.solodrai, bot+bpf-ci
In-Reply-To: <7fa6794161a8bd4fdbc21dad68e86e9770c873cc.camel@gmail.com>
在 2026/5/16 02:24, Eduard Zingerman 写道:
> On Fri, 2026-05-15 at 12:34 +0800, Kaitao Cheng wrote:
>>
>> 在 2026/5/14 09:50, Alexei Starovoitov 写道:
>>> On Wed May 13, 2026 at 3:53 PM PDT, Eduard Zingerman wrote:
>>>> On Tue, 2026-05-12 at 06:41 +0000, bot+bpf-ci@kernel.org wrote:
>>>>
>>>> [...]
>>>>
>>>>> When a BPF program holds an owning or refcount-acquired reference to
>>>>> one of these nodes (node X), which is structurally supported because
>>>>> __bpf_obj_drop_impl() uses refcount_dec_and_test() and only frees at
>>>>> refcount 0, a concurrent push to a DIFFERENT bpf_list_head becomes a
>>>>> corruption:
>>>>>
>>>>> CPU 0 (bpf_list_head_free, lock released) CPU 1 (BPF prog, refcount X)
>>>>> ----------------------------------------- ----------------------------
>>>>> (owner of X == NULL, X linked in drain)
>>>>> bpf_list_push_back(other, X)
>>>>> __bpf_list_add: spin_lock()
>>>>> cmpxchg(X->owner, NULL,
>>>>> POISON) -> OK
>>>>> list_add_tail(&X->list_head,
>>>>> other_head)
>>>>> -> overwrites X->next,
>>>>> X->prev, corrupts
>>>>> other_head's chain
>>>>> because X is still
>>>>> stitched into drain
>>>>> pos = drain.next; (may be X or neighbor using X's stale next)
>>>>> list_del_init(pos); reads X->next/prev now pointing into other_head,
>>>>> corrupts other_head's list and/or drain
>>>>
>>>>
>>>> Kaitao, this scenario seem plausible, could you please comment on it?
>>>
>>> I think bot is correct.
>>> This patch looks buggy.
>>> It seems to me an optimization that breaks the concurrent logic.
>>> May be just drop this patch and reorder the other one, so that bot
>>> sees nonown suffix logic first.
>>
>> This patch is still necessary because it addresses the problem discussed
>> in this thread:
>> https://lore.kernel.org/all/DH846C0P88QU.16YT12I1LXBZM@etsalapatis.com/
>>
>> The patch does have a bug, however. To fix the issues we are seeing now,
>> I propose the additional changes below and would appreciate feedback.
>>
>> --- a/kernel/bpf/helpers.c
>> +++ b/kernel/bpf/helpers.c
>> @@ -2263,8 +2263,10 @@ void bpf_list_head_free(const struct btf_field *field, void *list_head,
>> if (!head->next || list_empty(head))
>> goto unlock;
>> list_for_each_safe(pos, n, head) {
>> - WRITE_ONCE(container_of(pos,
>> - struct bpf_list_node_kern, list_head)->owner, NULL);
>> + struct bpf_list_node_kern *node;
>> +
>> + node = container_of(pos, struct bpf_list_node_kern, list_head);
>> + WRITE_ONCE(node->owner, BPF_PTR_POISON);
>> list_move_tail(pos, &drain);
>> }
>> unlock:
>> @@ -2272,8 +2274,12 @@ void bpf_list_head_free(const struct btf_field *field, void *list_head,
>> __bpf_spin_unlock_irqrestore(spin_lock);
>>
>> while (!list_empty(&drain)) {
>> + struct bpf_list_node_kern *node;
>> +
>> pos = drain.next;
>> + node = container_of(pos, struct bpf_list_node_kern, list_head);
>> list_del_init(pos);
>> + WRITE_ONCE(node->owner, NULL);
>
> I think this still leaves a short race window open.
> Why does the .owner has field to be NULL?
> Can the logic that implies for it to be NULL be extended to accept
> POISON as well?
Here, before setting owner to NULL, list_del_init() has already been
executed, which means the node no longer belongs to any list. This
should match the semantic meaning of owner == NULL.
Do you mean deleting WRITE_ONCE(node->owner, NULL) and preventing
all subsequent __bpf_list_add() operations on this node?
>
>> /* The contained type can also have resources, including a
>> * bpf_list_head which needs to be freed.
>> */
>> @@ -2481,6 +2487,14 @@ static int __bpf_list_add(struct bpf_list_node_kern *node,
>> if (unlikely(!h->next))
>> INIT_LIST_HEAD(h);
>>
>> + /* bpf_list_head_free() marks nodes being detached with BPF_PTR_POISON
>> + * before list_del_init(). cmpxchg(NULL, POISON) below would fail with
>> + * that old value and fall into the generic error path, which wrongly
>> + * calls __bpf_obj_drop_impl(). Reject POISON up front instead.
>> + */
>> + if (READ_ONCE(node->owner) == BPF_PTR_POISON)
>> + return -EINVAL;
>> +
This code block is not needed; I will remove it.
--
Thanks
Kaitao Cheng
^ permalink raw reply
* [PATCH] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 16:10 UTC (permalink / raw)
To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
Chen-Shi-Hong
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.
Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
describe the supported attributes and clarify that temp1_alarm and the
write permissions of update_interval depend on the chip.
Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>
---
Documentation/hwmon/lm75.rst | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
index 4269da04508e..cbf737948c1d 100644
--- a/Documentation/hwmon/lm75.rst
+++ b/Documentation/hwmon/lm75.rst
@@ -181,3 +181,21 @@ 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 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.
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 1/3] Doc: deprecated.rst: add strlcat()
From: Heiko Carstens @ 2026-05-16 15:28 UTC (permalink / raw)
To: Kees Cook
Cc: Manuel Ebner, Andy Shevchenko, Jonathan Corbet, Shuah Khan,
Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Geert Uytterhoeven, David Laight, Randy Dunlap, Jani Nikula,
open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
open list
In-Reply-To: <202605140931.913048A68B@keescook>
On Thu, May 14, 2026 at 09:31:46AM -0700, Kees Cook wrote:
> On Thu, May 14, 2026 at 06:26:53PM +0200, Manuel Ebner wrote:
> > add strlcat and alternatives
> >
> > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> > ---
> > Documentation/process/deprecated.rst | 7 +++++++
> > 1 file changed, 7 insertions(+)
> >
> > diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> > index fed56864d036..06e802f4bbfd 100644
> > --- a/Documentation/process/deprecated.rst
> > +++ b/Documentation/process/deprecated.rst
> > @@ -153,6 +153,13 @@ used, and the destinations should be marked with the `__nonstring
> > attribute to avoid future compiler warnings. For cases still needing
> > NUL-padding, strtomem_pad() can be used.
> >
> > +strlcat()
> > +---------
> > +strlcat() must re-scan the destination string from the beginning on each
> > +call (O(n^2) behavior). Alternatives are seq_buf_puts() and seq_buf_printf().
> > +snprintf(), scnprintf() and sysfs_emit() are possible aswell, but the adoption
> > +of the arguments needs to be taken care off.
> > +
>
> How about just:
>
> strlcat() must re-scan the destination string from the beginning on each
> call (O(n^2) behavior). Use the seq_buf API or similar instead.
seq_buf API for appending something to e.g. boot_command_line seems to be odd,
since boot_command_line is usually "just there" (depending on architecture and
boot loader).
So if I would remove strlcat() from appending something to boot_command_line I
would end up open-coding strlcat(), including the chance for the usual
off-by-one bugs. Looks like this would be true for nearly all architectures.
Is performance really the only reason to deprecate strlcat()? This seems to be
a bit questionable to me.
^ permalink raw reply
* Re: [PATCH v4 2/2] hwmon: (pmbus/d1u74t) Add Murata D1U74T PSU driver
From: Guenter Roeck @ 2026-05-16 15:13 UTC (permalink / raw)
To: Abdurrahman Hussain
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
kernel test robot
In-Reply-To: <20260514-d1u74t-v4-2-1f1ee7b002ec@nexthop.ai>
On Thu, May 14, 2026 at 08:03:26PM -0700, Abdurrahman Hussain wrote:
> Add PMBUS driver for Murata D1U74T power supplies.
>
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202605122253.zInzmUeX-lkp@intel.com/
> Signed-off-by: Abdurrahman Hussain <abdurrahman@nexthop.ai>
Applied, after dropping the above Reported-by: and Closes: tags.
Those are inappropriate for new drivers.
I also added the missing include files.
Guenter
> ---
> Documentation/hwmon/d1u74t.rst | 81 +++++++++++++++++++++++++++++++++++++++
> Documentation/hwmon/index.rst | 1 +
> MAINTAINERS | 7 ++++
> drivers/hwmon/pmbus/Kconfig | 9 +++++
> drivers/hwmon/pmbus/Makefile | 1 +
> drivers/hwmon/pmbus/d1u74t.c | 86 ++++++++++++++++++++++++++++++++++++++++++
> 6 files changed, 185 insertions(+)
>
> diff --git a/Documentation/hwmon/d1u74t.rst b/Documentation/hwmon/d1u74t.rst
> new file mode 100644
> index 000000000000..3a9eedbda483
> --- /dev/null
> +++ b/Documentation/hwmon/d1u74t.rst
> @@ -0,0 +1,81 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver d1u74t
> +====================
> +
> +Supported chips:
> +
> + * Murata D1U74T
> +
> + Prefix: 'd1u74t'
> +
> + Addresses scanned: -
> +
> + Datasheet: Publicly available at the Murata website
> +
> +Authors:
> + Abdurrahman Hussain <abdurrahman@nexthop.ai>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for Murata D1U74T Power Supply with
> +PMBus support.
> +
> +The driver is a client driver to the core PMBus driver.
> +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not auto-detect devices. You will have to instantiate the
> +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
> +details.
> +
> +
> +Sysfs entries
> +-------------
> +
> +======================= ======================================================
> +curr1_label "iin"
> +curr1_input Measured input current
> +curr1_alarm Input current alarm
> +curr1_rated_max Maximum rated input current
> +
> +curr2_label "iout1"
> +curr2_input Measured output current
> +curr2_max Maximum output current
> +curr2_max_alarm Output current high alarm
> +curr2_crit Critical high output current
> +curr2_crit_alarm Output current critical high alarm
> +curr2_rated_max Maximum rated output current
> +
> +in1_label "vin"
> +in1_input Measured input voltage
> +in1_alarm Input voltage alarm
> +in1_rated_min Minimum rated input voltage
> +in1_rated_max Maximum rated input voltage
> +
> +in2_label "vout1"
> +in2_input Measured output voltage
> +in2_alarm Output voltage alarm
> +in2_rated_min Minimum rated output voltage
> +in2_rated_max Maximum rated output voltage
> +
> +power1_label "pin"
> +power1_input Measured input power
> +power1_alarm Input power alarm
> +power1_rated_max Maximum rated input power
> +
> +temp[1-3]_input Measured temperature
> +temp[1-3]_max Maximum temperature
> +temp[1-3]_max_alarm Maximum temperature alarm
> +temp[1-3]_rated_max Maximum rated temperature
> +
> +fan1_alarm Fan 1 warning
> +fan1_fault Fan 1 fault
> +fan1_input Fan 1 speed in RPM
> +fan1_target Fan 1 target
> +======================= ======================================================
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8b655e5d6b68..97b1ef65b1c1 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -60,6 +60,7 @@ Hardware Monitoring Kernel Drivers
> corsair-psu
> cros_ec_hwmon
> crps
> + d1u74t
> da9052
> da9055
> dell-smm-hwmon
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b2040011a386..3106cf725dfc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18249,6 +18249,13 @@ F: drivers/mux/
> F: include/dt-bindings/mux/
> F: include/linux/mux/
>
> +MURATA D1U74T PSU DRIVER
> +M: Abdurrahman Hussain <abdurrahman@nexthop.ai>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: Documentation/hwmon/d1u74t.rst
> +F: drivers/hwmon/pmbus/d1u74t.c
> +
> MUSB MULTIPOINT HIGH SPEED DUAL-ROLE CONTROLLER
> M: Bin Liu <b-liu@ti.com>
> L: linux-usb@vger.kernel.org
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index 8f4bff375ecb..ee93b22d2887 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -113,6 +113,15 @@ config SENSORS_CRPS
> This driver can also be built as a module. If so, the module will
> be called crps.
>
> +config SENSORS_D1U74T
> + tristate "Murata D1U74T Power Supply"
> + help
> + If you say yes here you get hardware monitoring support for the Murata
> + D1U74T Power Supply.
> +
> + This driver can also be built as a module. If so, the module will
> + be called d1u74t.
> +
> config SENSORS_DELTA_AHE50DC_FAN
> tristate "Delta AHE-50DC fan control module"
> help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index 7129b62bc00f..8cf7d3075371 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -76,3 +76,4 @@ obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o
> obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
> obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o
> obj-$(CONFIG_SENSORS_CRPS) += crps.o
> +obj-$(CONFIG_SENSORS_D1U74T) += d1u74t.o
> diff --git a/drivers/hwmon/pmbus/d1u74t.c b/drivers/hwmon/pmbus/d1u74t.c
> new file mode 100644
> index 000000000000..286ba492e336
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/d1u74t.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright 2026 Nexthop Systems.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/of.h>
> +#include <linux/pmbus.h>
> +
> +#include "pmbus.h"
> +
> +static const struct i2c_device_id d1u74t_id[] = {
> + { "d1u74t" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(i2c, d1u74t_id);
> +
> +static struct pmbus_driver_info d1u74t_info = {
> + .pages = 1,
> + /* PSU uses default linear data format. */
> + .func[0] = PMBUS_HAVE_PIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
> + PMBUS_HAVE_IIN | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
> + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP |
> + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
> + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_FAN12 |
> + PMBUS_HAVE_STATUS_FAN12,
> +};
> +
> +static int d1u74t_probe(struct i2c_client *client)
> +{
> + char buf[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
> + struct device *dev = &client->dev;
> + int rc;
> +
> + rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
> + if (rc < 0)
> + return dev_err_probe(dev, rc, "Failed to read PMBUS_MFR_ID\n");
> +
> + if (rc != 9 || strncmp(buf, "Murata-PS", 9)) {
> + buf[rc] = '\0';
> + return dev_err_probe(dev, -ENODEV,
> + "Unsupported Manufacturer ID '%s'\n",
> + buf);
> + }
> +
> + rc = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
> + if (rc < 0)
> + return dev_err_probe(dev, rc,
> + "Failed to read PMBUS_MFR_MODEL\n");
> +
> + if (rc < 8 || strncmp(buf, "D1U74T-W", 8)) {
> + buf[rc] = '\0';
> + return dev_err_probe(dev, -ENODEV, "Model '%s' not supported\n",
> + buf);
> + }
> +
> + rc = pmbus_do_probe(client, &d1u74t_info);
> + if (rc)
> + return dev_err_probe(dev, rc, "Failed to probe\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id d1u74t_of_match[] = {
> + {
> + .compatible = "murata,d1u74t",
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, d1u74t_of_match);
> +
> +static struct i2c_driver d1u74t_driver = {
> + .driver = {
> + .name = "d1u74t",
> + .of_match_table = d1u74t_of_match,
> + },
> + .probe = d1u74t_probe,
> + .id_table = d1u74t_id,
> +};
> +
> +module_i2c_driver(d1u74t_driver);
> +
> +MODULE_AUTHOR("Abdurrahman Hussain");
> +MODULE_DESCRIPTION("PMBus driver for Murata D1U74T-W power supplies");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("PMBUS");
^ permalink raw reply
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