Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH RFC v4 10/44] KVM: guest_memfd: Add support for KVM_SET_MEMORY_ATTRIBUTES2
From: Ackerley Tng @ 2026-04-03 14:50 UTC (permalink / raw)
  To: Michael Roth
  Cc: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, oupton, pankaj.gupta, qperret,
	rick.p.edgecombe, rientjes, shivankg, steven.price, tabba, willy,
	wyihan, yan.y.zhao, forkloop, pratyush, suzuki.poulose,
	aneesh.kumar, Paolo Bonzini, Sean Christopherson, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Jonathan Corbet, Shuah Khan, Shuah Khan, Vishal Annapurve,
	Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Jason Gunthorpe, Vlastimil Babka, kvm, linux-kernel,
	linux-trace-kernel, linux-doc, linux-kselftest, linux-mm
In-Reply-To: <CAEvNRgGm9icDK8sK5ZfqHEOEqSbvjwtihE4p9d3vpBq-NfVjmw@mail.gmail.com>

Ackerley Tng <ackerleytng@google.com> writes:

>
> [...snip...]
>
> guest_memfd's populate will first check that the memory is shared, then
> also set the memory to private after the populate.
>
> [...snip...]
>
Looking at this again, the above basically means that the entire
conversion process needs to be performed within populate.

In addition to setting the attributes in guest_memfd as private, for
consistency, populate will also have to do all the associated
operations, especially unmapping from the host, checking refcounts,
and the list of work in conversion will only increase in future with
direct map removal/restoration and huge page merging.

The complexity of conversion also means possible errors (EAGAIN for
elevated refcounts and ENOMEM when we're out of memory), and error
information like the offset where the elevated refcount was.

It doesn't look like there's room for this information to be plumbed out
through the platform-specific ioctls, and even if we make space, it
seems odd to have conversion-related error information returned through
the platform-specific call.


I agree with the goal of not having KVM touch private memory contents as
tracked by guest_memfd, so I'd like to propose that we distinguish:

1. private as tracked by KVM (guest_memfd/vm_memory_attributes)
2. private as tracked by trusted entity


Currently, in TDX's populate flow, KVM doesn't do any copying, it only
instructs TDX to do the copying.

"TDH.MEM.PAGE.ADD Operands Information Definition" in the TDX module
ABI spec says the source is accessed as shared, the destination is
accessed as private, and in the case that source and destination are
the same, the "In-Place Add" section says the source will be converted
to private.

In SNP's populate flow, SNP-specific code in KVM does the copying from
shared memory into the destination, then makes the destination address
private in the RMP table before telling the firmware to do the
in-place encryption.


I think we should think of the populate ioctls as being
platform-specific ioctls that, when called, accept

+ destination address: private (as tracked by guest_memfd)
+ source address: shared (as tracked by guest_memfd) or NULL

KVM doesn't touch private memory contents, even in this case, because
it's really a platform-specific ioctl that handles loading, and the
platform does expect the destination is private for both TDX and SNP
at the firmware boundary.

Since SNP (platform-specific) only allows in-place launch update, and
KVM had to provide an interface that allows a different source address
for support before in-place conversion, then SNP has to continue
supporting the to-be-deprecated path by doing the copying within
platform-specific code.

For consistency, guest_memfd can continue to check that it tracks the
destination address as private, and sev_gmem_populate will then hide
the copying code away just to support the legacy case.


The flow before in-place conversion is

1. Load memory (shared or non-guest_memfd memory)
2. KVM_SEV_SNP_LAUNCH_UPDATE or KVM_TDX_INIT_MEM_REGION (destination:
   gfn for separate private memory, source: shared memory)

The proposed flow for in-place conversion is

1. INIT_SHARED or convert to shared
2. Load memory while it is shared
3. Convert to private (PRESERVE, or new flag?)
4. KVM_SEV_SNP_LAUNCH_UPDATE or KVM_TDX_INIT_MEM_REGION (destination:
   gfn for converted private memory, source: NULL)


TLDR:

+ Think of populate ioctls not as KVM touching memory, but platform
  handling population.
+ KVM code (kvm_gmem_populate) still doesn't touch memory contents
+ post_populate is platform-specific code that handles loading into
  private destination memory just to support legacy non-in-place
  conversion.
+ Don't complicate populate ioctls by doing conversion just to support
  legacy use-cases where platform-specific code has to do copying on
  the host.

>>>
>>> [...snip...]
>>>

^ permalink raw reply

* [PATCH v12 1/2] dt-bindings: hwmon: add support for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu
In-Reply-To: <20260403-add-mcp9982-hwmon-v12-0-b3bfb26ff136@microchip.com>

Add devicetree schema for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
 .../bindings/hwmon/microchip,mcp9982.yaml          | 237 +++++++++++++++++++++
 MAINTAINERS                                        |   6 +
 2 files changed, 243 insertions(+)

diff --git a/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
new file mode 100644
index 000000000000..83dd2bf37e27
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
@@ -0,0 +1,237 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,mcp9982.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP998X/33 and MCP998XD/33D Temperature Monitor
+
+maintainers:
+  - Victor Duicu <victor.duicu@microchip.com>
+
+description: |
+  The MCP998X/33 and MCP998XD/33D family is a high-accuracy 2-wire
+  multichannel automotive temperature monitor.
+  The datasheet can be found here:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,mcp9933
+      - microchip,mcp9933d
+      - microchip,mcp9982
+      - microchip,mcp9982d
+      - microchip,mcp9983
+      - microchip,mcp9983d
+      - microchip,mcp9984
+      - microchip,mcp9984d
+      - microchip,mcp9985
+      - microchip,mcp9985d
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 1
+    maxItems: 2
+
+  interrupt-names:
+    description:
+      The chip family has three different interrupt pins divided among them.
+      The chips without "D" have alert-therm and therm-addr.
+      The chips with "D" have alert-therm and sys-shtdwn.
+    minItems: 1
+    items:
+      - enum: [alert-therm, therm-addr, sys-shtdwn]
+      - enum: [therm-addr, sys-shtdwn]
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  microchip,enable-anti-parallel:
+    description:
+      Enable anti-parallel diode mode operation.
+      MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes
+      in anti-parallel connection on the same set of pins.
+    type: boolean
+
+  microchip,parasitic-res-on-channel1-2:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      1 and 2 are affected together.
+    type: boolean
+
+  microchip,parasitic-res-on-channel3-4:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      3 and 4 are affected together.
+    type: boolean
+
+  microchip,power-state:
+    description:
+      The chip can be set in Run state or Standby state. In Run state the ADC
+      is converting on all channels at the programmed conversion rate.
+      In Standby state the host must initiate a conversion cycle by writing
+      to the One-Shot register.
+      True value sets Run state.
+      Chips with "D" in the name can only be set in Run mode.
+    type: boolean
+
+  vdd-supply: true
+
+patternProperties:
+  "^channel@[1-4]$":
+    description:
+      Represents the external temperature channels to which
+      a remote diode is connected.
+    type: object
+
+    properties:
+      reg:
+        items:
+          maxItems: 1
+
+      label:
+        description: Unique name to identify which channel this is.
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9982d
+              - microchip,mcp9983d
+              - microchip,mcp9984d
+              - microchip,mcp9985d
+              - microchip,mcp9933d
+    then:
+      properties:
+        interrupt-names:
+          items:
+            enum:
+              - alert-therm
+              - sys-shtdwn
+      required:
+        - microchip,power-state
+        - microchip,parasitic-res-on-channel1-2
+    else:
+      properties:
+        microchip,power-state: true
+        interrupt-names:
+          items:
+            enum:
+              - alert-therm
+              - therm-addr
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9983d
+              - microchip,mcp9984d
+              - microchip,mcp9985d
+    then:
+      required:
+        - microchip,parasitic-res-on-channel3-4
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9982
+              - microchip,mcp9982d
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+      patternProperties:
+        "^channel@[2-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9983
+              - microchip,mcp9983d
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9933
+              - microchip,mcp9933d
+    then:
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9984
+              - microchip,mcp9984d
+    then:
+      properties:
+        channel@4: false
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        temperature-sensor@4c {
+            compatible = "microchip,mcp9985";
+            reg = <0x4c>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            microchip,enable-anti-parallel;
+            microchip,parasitic-res-on-channel1-2;
+            microchip,parasitic-res-on-channel3-4;
+            vdd-supply = <&vdd>;
+
+            channel@1 {
+                reg = <1>;
+                label = "Room Temperature";
+            };
+
+            channel@2 {
+                reg = <2>;
+                label = "GPU Temperature";
+            };
+        };
+    };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e08767323763..026510a4129c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17160,6 +17160,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/microchip,mcp3911.yaml
 F:	drivers/iio/adc/mcp3911.c
 
+MICROCHIP MCP9982 TEMPERATURE DRIVER
+M:	Victor Duicu <victor.duicu@microchip.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+
 MICROCHIP MMC/SD/SDIO MCI DRIVER
 M:	Aubin Constans <aubin.constans@microchip.com>
 S:	Maintained

-- 
2.51.0


^ permalink raw reply related

* [PATCH v12 2/2] hwmon: add support for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu
In-Reply-To: <20260403-add-mcp9982-hwmon-v12-0-b3bfb26ff136@microchip.com>

Add driver for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
 Documentation/hwmon/index.rst   |   1 +
 Documentation/hwmon/mcp9982.rst | 111 +++++
 MAINTAINERS                     |   2 +
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/mcp9982.c         | 997 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1123 insertions(+)

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 85d7a686883e..b02709fc216e 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -173,6 +173,7 @@ Hardware Monitoring Kernel Drivers
    mc33xs2410_hwmon
    mc34vr500
    mcp3021
+   mcp9982
    menf21bmc
    mlxreg-fan
    mp2856
diff --git a/Documentation/hwmon/mcp9982.rst b/Documentation/hwmon/mcp9982.rst
new file mode 100644
index 000000000000..790ee1697b45
--- /dev/null
+++ b/Documentation/hwmon/mcp9982.rst
@@ -0,0 +1,111 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Kernel driver MCP998X
+=====================
+
+Supported chips:
+
+  * Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D
+
+    Prefix: 'mcp9982'
+
+    Datasheet:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+Authors:
+
+   - Victor Duicu <victor.duicu@microchip.com>
+
+Description
+-----------
+
+This driver implements support for the MCP998X family containing: MCP9982,
+MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D,
+MCP9933 and MCP9933D.
+
+The MCP998X Family is a high accuracy 2-wire multichannel automotive
+temperature monitor.
+
+The chips in the family have different numbers of external channels,
+ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in
+anti-parallel connection is supported by MCP9984/85/33 and
+MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present
+only in MCP998XD and MCP9933D.
+
+Temperatures are read in millidegrees Celsius, ranging from -64 to
+191.875 with 0.125 precision.
+
+Each channel has a minimum, maximum, and critical limit alongside associated alarms.
+The chips also implement a hysteresis mechanism which applies only to the maximum
+and critical limits. The relative difference between a limit and its hysteresis
+is the same for both and the value is kept in a single register.
+
+The chips measure temperatures with a variable conversion rate.
+Update_interval = Conversion/Second, so the available options are:
+- 16000 (ms) = 1 conv/16 sec
+- 8000 (ms) = 1 conv/8 sec
+- 4000 (ms) = 1 conv/4 sec
+- 2000 (ms) = 1 conv/2 sec
+- 1000 (ms) = 1 conv/sec
+- 500 (ms) = 2 conv/sec
+- 250 (ms) = 4 conv/sec
+- 125 (ms) = 8 conv/sec
+- 64 (ms) = 16 conv/sec
+- 32 (ms) = 32 conv/sec
+- 16 (ms) = 64 conv/sec
+
+Usage Notes
+-----------
+
+Parameters that can be configured in devicetree:
+- anti-parallel diode mode operation
+- resistance error correction on channels 1 and 2
+- resistance error correction on channels 3 and 4
+- power state
+
+Chips 82/83 and 82D/83D do not support anti-parallel diode mode.
+For chips with "D" in the name resistance error correction must be on.
+Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+for details.
+
+There are two power states:
+- Active state: in which the chip is converting on all channels at the
+programmed rate.
+
+- Standby state: in which the host must initiate a conversion cycle.
+
+Chips with "D" in the name work in Active state only and those without
+can work in either state.
+
+Chips with "D" in the name can't set update interval slower than 1 second.
+
+Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable
+while the others are read only. Setting tempX_crit_hyst writes the difference
+between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value
+applies automatically  to the other limits. At power up the device starts with
+a 10 degree hysteresis.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. The temperature limits and
+update_interval are read-write. The attribute tempX_crit_hyst is read-write,
+while tempX_max_hyst is read only. All other attributes are read only.
+
+======================= ==================================================
+temp[1-5]_label		User name for channel.
+temp[1-5]_input		Measured temperature for channel.
+
+temp[1-5]_crit		Critical temperature limit.
+temp[1-5]_crit_alarm	Critical temperature limit alarm.
+temp[1-5]_crit_hyst	Critical temperature limit hysteresis.
+
+temp[1-5]_max		High temperature limit.
+temp[1-5]_max_alarm	High temperature limit alarm.
+temp[1-5]_max_hyst	High temperature limit hysteresis.
+
+temp[1-5]_min		Low temperature limit.
+temp[1-5]_min_alarm	Low temperature limit alarm.
+
+update_interval		The interval at which the chip will update readings.
+======================= ==================================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 026510a4129c..5c6662e10b04 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17165,6 +17165,8 @@ M:	Victor Duicu <victor.duicu@microchip.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+F:	Documentation/hwmon/mcp9982.rst
+F:	drivers/hwmon/mcp9982.c
 
 MICROCHIP MMC/SD/SDIO MCI DRIVER
 M:	Aubin Constans <aubin.constans@microchip.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 157678b821fc..c758ab2d5fdf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1388,6 +1388,17 @@ config SENSORS_MCP3021
 	  This driver can also be built as a module. If so, the module
 	  will be called mcp3021.
 
+config SENSORS_MCP9982
+       tristate "Microchip Technology MCP9982 driver"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Say yes here to include support for Microchip Technology's MCP998X/33
+         and MCP998XD/33D Multichannel Automotive Temperature Monitor Family.
+
+         This driver can also be built as a module. If so, the module
+         will be called mcp9982.
+
 config SENSORS_MLXREG_FAN
 	tristate "Mellanox FAN driver"
 	depends on MELLANOX_PLATFORM
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1bde..cec33da29a68 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
 obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
 obj-$(CONFIG_SENSORS_MC34VR500)	+= mc34vr500.o
 obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
+obj-$(CONFIG_SENSORS_MCP9982)	+= mcp9982.o
 obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_TPS23861)	+= tps23861.o
 obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c
new file mode 100644
index 000000000000..f0b836a3f256
--- /dev/null
+++ b/drivers/hwmon/mcp9982.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive
+ * Temperature Monitor Family
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Victor Duicu <victor.duicu@microchip.com>
+ *
+ * Datasheet can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/byteorder/generic.h>
+#include <linux/delay.h>
+#include <linux/device/devres.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/time64.h>
+#include <linux/util_macros.h>
+
+/* MCP9982 Registers */
+#define MCP9982_HIGH_BYTE_ADDR(index)		(2 * (index))
+#define MCP9982_ONE_SHOT_ADDR			0x0A
+#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR	0x0B
+#define MCP9982_INTERNAL_LOW_LIMIT_ADDR		0x0C
+#define MCP9982_EXT_HIGH_LIMIT_ADDR(index)	(4 * ((index) - 1) + 0x0D)
+#define MCP9982_EXT_LOW_LIMIT_ADDR(index)	(4 * ((index) - 1) + 0x0F)
+#define MCP9982_THERM_LIMIT_ADDR(index)		((index) + 0x1D)
+#define MCP9982_CFG_ADDR			0x22
+#define MCP9982_CONV_ADDR			0x24
+#define MCP9982_HYS_ADDR			0x25
+#define MCP9982_CONSEC_ALRT_ADDR		0x26
+#define MCP9982_ALRT_CFG_ADDR			0x27
+#define MCP9982_RUNNING_AVG_ADDR		0x28
+#define MCP9982_HOTTEST_CFG_ADDR		0x29
+#define MCP9982_STATUS_ADDR			0x2A
+#define MCP9982_EXT_FAULT_STATUS_ADDR		0x2B
+#define MCP9982_HIGH_LIMIT_STATUS_ADDR		0x2C
+#define MCP9982_LOW_LIMIT_STATUS_ADDR		0x2D
+#define MCP9982_THERM_LIMIT_STATUS_ADDR		0x2E
+#define MCP9982_HOTTEST_HIGH_BYTE_ADDR		0x2F
+#define MCP9982_HOTTEST_LOW_BYTE_ADDR		0x30
+#define MCP9982_HOTTEST_STATUS_ADDR		0x31
+#define MCP9982_THERM_SHTDWN_CFG_ADDR		0x32
+#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR	0x33
+#define MCP9982_EXT_BETA_CFG_ADDR(index)	((index) + 0x33)
+#define MCP9982_EXT_IDEAL_ADDR(index)		((index) + 0x35)
+
+/* MCP9982 Bits */
+#define MCP9982_CFG_MSKAL			BIT(7)
+#define MCP9982_CFG_RS				BIT(6)
+#define MCP9982_CFG_ATTHM			BIT(5)
+#define MCP9982_CFG_RECD12			BIT(4)
+#define MCP9982_CFG_RECD34			BIT(3)
+#define MCP9982_CFG_RANGE			BIT(2)
+#define MCP9982_CFG_DA_ENA			BIT(1)
+#define MCP9982_CFG_APDD			BIT(0)
+
+#define MCP9982_STATUS_BUSY			BIT(5)
+
+/* Constants and default values */
+#define MCP9982_MAX_NUM_CHANNELS		5
+#define MCP9982_BETA_AUTODETECT			16
+#define MCP9982_IDEALITY_DEFAULT		18
+#define MCP9982_OFFSET				64
+#define MCP9982_DEFAULT_CONSEC_ALRT_VAL		112
+#define MCP9982_DEFAULT_HYS_VAL			10
+#define MCP9982_DEFAULT_CONV_VAL		6
+#define MCP9982_WAKE_UP_TIME_US			125000
+#define MCP9982_WAKE_UP_TIME_MAX_US		130000
+#define MCP9982_HIGH_LIMIT_DEFAULT		85000
+#define MCP9982_LOW_LIMIT_DEFAULT		0
+
+static const struct hwmon_channel_info * const mcp9985_info[] = {
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST),
+	HWMON_CHANNEL_INFO(chip,
+			   HWMON_C_UPDATE_INTERVAL),
+	NULL
+};
+
+/**
+ * struct mcp9982_features - features of a mcp9982 instance
+ * @name:			chip's name
+ * @phys_channels:		number of physical channels supported by the chip
+ * @hw_thermal_shutdown:	presence of hardware thermal shutdown circuitry
+ * @allow_apdd:			whether the chip supports enabling APDD
+ * @has_recd34:			whether the chip has the channels that are affected by recd34
+ */
+struct mcp9982_features {
+	const char	*name;
+	u8		phys_channels;
+	bool		hw_thermal_shutdown;
+	bool		allow_apdd;
+	bool		has_recd34;
+};
+
+static const struct mcp9982_features mcp9933_chip_config = {
+	.name = "mcp9933",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9933d_chip_config = {
+	.name = "mcp9933d",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9982_chip_config = {
+	.name = "mcp9982",
+	.phys_channels = 2,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = false,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9982d_chip_config = {
+	.name = "mcp9982d",
+	.phys_channels = 2,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = false,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9983_chip_config = {
+	.name = "mcp9983",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = false,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9983d_chip_config = {
+	.name = "mcp9983d",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = false,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9984_chip_config = {
+	.name = "mcp9984",
+	.phys_channels = 4,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9984d_chip_config = {
+	.name = "mcp9984d",
+	.phys_channels = 4,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9985_chip_config = {
+	.name = "mcp9985",
+	.phys_channels = 5,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9985d_chip_config = {
+	.name = "mcp9985d",
+	.phys_channels = 5,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const unsigned int mcp9982_update_interval[11] = {
+	16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16
+};
+
+/* MCP9982 regmap configuration */
+static const struct regmap_range mcp9982_regmap_wr_ranges[] = {
+	regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR),
+	regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR),
+	regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR),
+	regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_wr_table = {
+	.yes_ranges = mcp9982_regmap_wr_ranges,
+	.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges),
+};
+
+static const struct regmap_range mcp9982_regmap_rd_ranges[] = {
+	regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR),
+	regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_rd_table = {
+	.yes_ranges = mcp9982_regmap_rd_ranges,
+	.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges),
+};
+
+static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MCP9982_ONE_SHOT_ADDR:
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+	case MCP9982_CFG_ADDR:
+	case MCP9982_CONV_ADDR:
+	case MCP9982_HYS_ADDR:
+	case MCP9982_CONSEC_ALRT_ADDR:
+	case MCP9982_ALRT_CFG_ADDR:
+	case MCP9982_RUNNING_AVG_ADDR:
+	case MCP9982_HOTTEST_CFG_ADDR:
+	case MCP9982_THERM_SHTDWN_CFG_ADDR:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config mcp9982_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.rd_table = &mcp9982_regmap_rd_table,
+	.wr_table = &mcp9982_regmap_wr_table,
+	.volatile_reg = mcp9982_is_volatile_reg,
+	.max_register = MCP9982_EXT_IDEAL_ADDR(4),
+	.cache_type = REGCACHE_MAPLE,
+};
+
+/**
+ * struct mcp9982_priv - information about chip parameters
+ * @regmap:			device register map
+ * @chip:			pointer to structure holding chip features
+ * @labels:			labels of the channels
+ * @interval_idx:		index representing the current update interval
+ * @enabled_channel_mask:	mask containing which channels should be enabled
+ * @num_channels:		number of active physical channels
+ * @recd34_enable:		state of Resistance Error Correction(REC) on channels 3 and 4
+ * @recd12_enable:		state of Resistance Error Correction(REC) on channels 1 and 2
+ * @apdd_enable:		state of anti-parallel diode mode
+ * @run_state:			chip is in Run state, otherwise is in Standby state
+ */
+struct mcp9982_priv {
+	struct regmap *regmap;
+	const struct mcp9982_features *chip;
+	const char *labels[MCP9982_MAX_NUM_CHANNELS];
+	unsigned int interval_idx;
+	unsigned long enabled_channel_mask;
+	u8 num_channels;
+	bool recd34_enable;
+	bool recd12_enable;
+	bool apdd_enable;
+	bool run_state;
+};
+
+static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val)
+{
+	unsigned int limit, reg_high, reg_low;
+	int ret;
+
+	switch (address) {
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+		ret = regmap_read(priv->regmap, address, &limit);
+		if (ret)
+			return ret;
+
+		*val = ((int)limit - MCP9982_OFFSET) * 1000;
+
+		return 0;
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+		/*
+		 * In order to keep consistency with reading temperature memory region we will use
+		 * single byte I2C read.
+		 */
+		ret = regmap_read(priv->regmap, address, &reg_high);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(priv->regmap, address + 1, &reg_low);
+		if (ret)
+			return ret;
+
+		*val = ((reg_high << 8) + reg_low) >> 5;
+		*val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			long *val)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int reg_high, reg_low, hyst, reg_status;
+	int ret;
+	u8 addr;
+
+	/*
+	 * In Standby State the conversion cycle must be initated manually in
+	 * order to read fresh temperature values and the status of the alarms.
+	 */
+	if (!priv->run_state) {
+		switch (type) {
+		case hwmon_temp:
+			switch (attr) {
+			case hwmon_temp_input:
+			case hwmon_temp_max_alarm:
+			case hwmon_temp_min_alarm:
+			case hwmon_temp_crit_alarm:
+				ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
+				if (ret)
+					return ret;
+				/*
+				 * When the device is in Standby mode, 125 ms need
+				 * to pass from writing in One Shot register before
+				 * the conversion cycle begins.
+				 */
+				usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US);
+				ret = regmap_read_poll_timeout
+					       (priv->regmap, MCP9982_STATUS_ADDR,
+					       reg_status, !(reg_status & MCP9982_STATUS_BUSY),
+					       MCP9982_WAKE_UP_TIME_US,
+					       MCP9982_WAKE_UP_TIME_US * 10);
+				break;
+			}
+		default:
+			break;
+		}
+	}
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+			/*
+			 * The only areas of memory that support SMBus block read are 80h->89h
+			 * (temperature memory block) and 90h->97h(status memory block).
+			 * In this context the read operation uses SMBus protocol and the first
+			 * value returned will be the number of addresses that can be read.
+			 * Temperature memory block is 10 bytes long and status memory block is 8
+			 * bytes long.
+			 *
+			 * Depending on the read instruction used, the chip behaves differently:
+			 * - regmap_bulk_read() when applied to the temperature memory block
+			 * (80h->89h), the chip replies with SMBus block read, including count,
+			 * additionally to the high and the low bytes. This function cannot be
+			 * applied on the memory region 00h->09h(memory area which does not support
+			 * block reads, returns wrong data) unless use_single_read is set in
+			 * regmap_config.
+			 *
+			 * - regmap_multi_reg_read() when applied to the 00h->09h area uses I2C
+			 * and returns only the high and low temperature bytes. When applied to
+			 * the temperature memory block (80h->89h) returns the count till the end of
+			 * the temperature memory block(aka SMBus count).
+			 *
+			 * - i2c_smbus_read_block_data() is not supported by all drivers.
+			 *
+			 * In order to keep consistency with reading limit memory region we will
+			 * use single byte I2C read.
+			 *
+			 * Low register is latched when high temperature register is read.
+			 */
+			ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), &reg_high);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
+					  &reg_low);
+			if (ret)
+				return ret;
+
+			*val = ((reg_high << 8) + reg_low) >> 5;
+			*val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+			return 0;
+		case hwmon_temp_max:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+			return mcp9982_read_limit(priv, addr, val);
+		case hwmon_temp_max_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_max_hyst:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+			ret = mcp9982_read_limit(priv, addr, val);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+			if (ret)
+				return ret;
+
+			*val -= hyst * 1000;
+
+			return 0;
+		case hwmon_temp_min:
+			if (channel)
+				addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+			return mcp9982_read_limit(priv, addr, val);
+		case hwmon_temp_min_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_crit:
+			return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+		case hwmon_temp_crit_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_crit_hyst:
+			ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+			if (ret)
+				return ret;
+
+			*val -= hyst * 1000;
+
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			*val = mcp9982_update_interval[priv->interval_idx];
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			      int channel, const char **str)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = priv->labels[channel];
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
+{
+	int ret;
+	unsigned int regh, regl;
+
+	switch (address) {
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+		regh = DIV_ROUND_CLOSEST(val, 1000);
+		regh = clamp_val(regh, 0, 255);
+
+		return regmap_write(priv->regmap, address, regh);
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+		val = DIV_ROUND_CLOSEST(val, 125);
+		regh = (val >> 3) & 0xff;
+		regl = (val & 0x07) << 5;
+		/* Block writing is not supported by the chip. */
+		ret = regmap_write(priv->regmap, address, regh);
+		if (ret)
+			return ret;
+
+		return regmap_write(priv->regmap, address + 1, regl);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val)
+{
+	int hyst, ret;
+	int limit;
+
+	val = DIV_ROUND_CLOSEST(val, 1000);
+	val = clamp_val(val, 0, 255);
+
+	/* Therm register is 8 bits and so it keeps only the integer part of the temperature. */
+	ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit);
+	if (ret)
+		return ret;
+
+	hyst = clamp_val(limit - val, 0, 255);
+
+	return regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst);
+}
+
+static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			 long val)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int idx;
+	u8 addr;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+
+			/*
+			 * For MCP998XD and MCP9933D update interval
+			 * can't be longer than 1 second.
+			 */
+			if (priv->chip->hw_thermal_shutdown)
+				val = clamp_val(val, 0, 1000);
+
+			idx = find_closest_descending(val, mcp9982_update_interval,
+						      ARRAY_SIZE(mcp9982_update_interval));
+			priv->interval_idx = idx;
+
+			return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
+		default:
+			return -EINVAL;
+		}
+	case hwmon_temp:
+		val = clamp_val(val, -64000, 191875);
+		val = val + (MCP9982_OFFSET * 1000);
+		switch (attr) {
+		case hwmon_temp_max:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+			return mcp9982_write_limit(priv, addr, val);
+		case hwmon_temp_min:
+			if (channel)
+				addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+			return mcp9982_write_limit(priv, addr, val);
+		case hwmon_temp_crit:
+			return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+		case hwmon_temp_crit_hyst:
+			return mcp9982_write_hyst(priv, channel, val);
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+				  int channel)
+{
+	const struct mcp9982_priv *priv = _data;
+
+	if (!test_bit(channel, &priv->enabled_channel_mask))
+		return 0;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			if (priv->labels[channel])
+				return 0444;
+			else
+				return 0;
+		case hwmon_temp_input:
+		case hwmon_temp_min_alarm:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_crit_alarm:
+			return 0444;
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+			return 0644;
+		default:
+			return 0;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static const struct hwmon_ops mcp9982_hwmon_ops = {
+	.is_visible = mcp9982_is_visible,
+	.read = mcp9982_read,
+	.read_string = mcp9982_read_label,
+	.write = mcp9982_write,
+};
+
+static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
+{
+	long high_limit, low_limit;
+	unsigned int i;
+	int ret;
+	u8 val;
+
+	/* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
+	if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
+
+	/* Chips with "D" work only in Run state. */
+	if (priv->chip->hw_thermal_shutdown && !priv->run_state)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
+
+	/* All chips with "D" in the name must have RECD12 enabled. */
+	if (priv->chip->hw_thermal_shutdown && !priv->recd12_enable)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD12.\n");
+	/* Chips 83D/84D/85D must have RECD34 enabled. */
+	if (priv->chip->hw_thermal_shutdown)
+		if ((priv->chip->has_recd34 && !priv->recd34_enable))
+			return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD34.\n");
+
+	/*
+	 * Set default values in registers.
+	 * APDD, RECD12 and RECD34 are active on 0.
+	 */
+	val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
+	      FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
+	      FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
+	      FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
+	      FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
+	      FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
+	      FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
+
+	ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
+	if (ret)
+		return ret;
+
+	/*
+	 * Read initial value from register.
+	 * The convert register utilises only 4 out of 8 bits.
+	 * Numerical values 0->10 set their respective update intervals,
+	 * while numerical values 11->15 default to 1 second.
+	 */
+	ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx);
+	if (ret)
+		return ret;
+	if (priv->interval_idx >= 11)
+		priv->interval_idx = 4;
+
+	ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	/*
+	 * Only external channels 1 and 2 support beta compensation.
+	 * Set beta auto-detection.
+	 */
+	for (i = 1; i < 3; i++)
+		if (test_bit(i, &priv->enabled_channel_mask)) {
+			ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
+					   MCP9982_BETA_AUTODETECT);
+			if (ret)
+				return ret;
+		}
+
+	high_limit = MCP9982_HIGH_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
+	low_limit = MCP9982_LOW_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
+
+	/* Set default values for internal channel limits. */
+	if (test_bit(0, &priv->enabled_channel_mask)) {
+		ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR, high_limit);
+		if (ret)
+			return ret;
+
+		ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR, low_limit);
+		if (ret)
+			return ret;
+
+		ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0), high_limit);
+		if (ret)
+			return ret;
+	}
+
+	/* Set ideality factor and limits to default for external channels. */
+	for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
+		if (test_bit(i, &priv->enabled_channel_mask)) {
+			ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
+					   MCP9982_IDEALITY_DEFAULT);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i), high_limit);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i), low_limit);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i), high_limit);
+			if (ret)
+				return ret;
+		}
+
+	return 0;
+}
+
+static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int reg_nr;
+	int ret;
+
+	/* Initialise internal channel( which is always present ). */
+	priv->labels[0] = "internal diode";
+	priv->enabled_channel_mask = 1;
+
+	/* Default values to work on systems without devicetree or firmware nodes. */
+	if (!dev_fwnode(dev)) {
+		priv->num_channels = device_nr_channels;
+		priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
+		priv->apdd_enable = false;
+		priv->recd12_enable = true;
+		priv->recd34_enable = true;
+		priv->run_state = true;
+		return 0;
+	}
+
+	priv->apdd_enable =
+		device_property_read_bool(dev, "microchip,enable-anti-parallel");
+
+	priv->recd12_enable =
+		device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
+
+	priv->recd34_enable =
+		device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
+
+	priv->run_state =
+		device_property_read_bool(dev, "microchip,power-state");
+
+	priv->num_channels = device_get_child_node_count(dev) + 1;
+
+	if (priv->num_channels > device_nr_channels)
+		return dev_err_probe(dev, -EINVAL,
+				     "More channels than the chip supports.\n");
+
+	/* Read information about the external channels. */
+	device_for_each_named_child_node_scoped(dev, child, "channel") {
+		reg_nr = 0;
+		ret = fwnode_property_read_u32(child, "reg", &reg_nr);
+		if (ret || !reg_nr || reg_nr >= device_nr_channels)
+			return dev_err_probe(dev, -EINVAL,
+			  "Channel reg is incorrectly set.\n");
+
+		fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
+		set_bit(reg_nr, &priv->enabled_channel_mask);
+	}
+
+	return 0;
+}
+
+static const struct hwmon_chip_info mcp998x_chip_info = {
+	.ops = &mcp9982_hwmon_ops,
+	.info = mcp9985_info,
+};
+
+static int mcp9982_probe(struct i2c_client *client)
+{
+	const struct mcp9982_features *chip;
+	struct device *dev = &client->dev;
+	struct mcp9982_priv *priv;
+	struct device *hwmon_dev;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
+
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->regmap),
+				     "Cannot initialize register map.\n");
+
+	dev_set_drvdata(dev, priv);
+
+	chip = i2c_get_match_data(client);
+	if (!chip)
+		return -EINVAL;
+	priv->chip = chip;
+
+	ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
+	if (ret)
+		return ret;
+
+	ret = mcp9982_init(dev, priv);
+	if (ret)
+		return ret;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
+							 &mcp998x_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id mcp9982_id[] = {
+	{ .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
+	{ .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
+	{ .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
+	{ .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
+	{ .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
+	{ .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
+	{ .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
+	{ .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
+	{ .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
+	{ .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mcp9982_id);
+
+static const struct of_device_id mcp9982_of_match[] = {
+	{
+		.compatible = "microchip,mcp9933",
+		.data = &mcp9933_chip_config,
+	}, {
+		.compatible = "microchip,mcp9933d",
+		.data = &mcp9933d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9982",
+		.data = &mcp9982_chip_config,
+	}, {
+		.compatible = "microchip,mcp9982d",
+		.data = &mcp9982d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9983",
+		.data = &mcp9983_chip_config,
+	}, {
+		.compatible = "microchip,mcp9983d",
+		.data = &mcp9983d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9984",
+		.data = &mcp9984_chip_config,
+	}, {
+		.compatible = "microchip,mcp9984d",
+		.data = &mcp9984d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9985",
+		.data = &mcp9985_chip_config,
+	}, {
+		.compatible = "microchip,mcp9985d",
+		.data = &mcp9985d_chip_config,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mcp9982_of_match);
+
+static struct i2c_driver mcp9982_driver = {
+	.driver	 = {
+		.name = "mcp9982",
+		.of_match_table = mcp9982_of_match,
+	},
+	.probe = mcp9982_probe,
+	.id_table = mcp9982_id,
+};
+module_i2c_driver(mcp9982_driver);
+
+MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>");
+MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


^ permalink raw reply related

* [PATCH v12 0/2] add support in hwmon for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu

Add support in hwmon for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

The chips in the family have different numbers of external channels,
ranging from 1(MCP9982) to 4 channels (MCP9985).
Reading diodes in anti-parallel connection is supported by MCP9984/85/33
and MCP9984D/85D/33D.
Dedicated hardware shutdown circuitry is present only in MCP998XD
and MCP9933D.

The driver supports reading the temperature channels, the temperature
limits, the limit hysteresis and their corresponding alarms. The user can
set the limits, the update interval and the hysteresis value through
the critical limit hysteresis.

Differences related to previous patch:
v12:
- in devicetree list items in enum.
- make microchip,parasitic-res-on-channel3-4
  required only for the "D" chips that are
  affected by it.
- in mcp9982.rst rephrase to "device starts
  with a 10 degree hysteresis".
- in driver check recd34 is enabled only for
  the "D" chips that are affected by it.
- in mcp9982_read() initiate a conversion cycle
  only when reading temperature values and alarms.
  Use poll function to check the BUSY bit.
- define hyst as unsigned int.
- add comment to clarify that in Standby mode,
  after writing in OneShot register 125ms need
  to pass before the conversion cycle begins.
- add comment to clarify the behavior of the chip
  regarding block read.
- rework the way temperature limits and hysteresis
  are written in order to round the result instead
  of truncating.
- return regmap_write() directly where applicable.
- in mcp9982_write() use clamp_val().
- in mcp9982_init(), if update interval value is 
  outside mcp9982_update_interval[] set default
  value of 1 second.
- use device_for_each_named_child_node_scoped().
- rephrase some comments.
- link to v11: https://lore.kernel.org/r/20260305-add-mcp9982-hwmon-v11-0-6e914ba63239@microchip.com

v11:
- in devicetree set minItems to interrupts.
- in interrupt-names define both items and remove maxItems.
- in conditionals list the items that must be present.
- in driver in mcp9982_write_limit() calculate regh and regl
  at the start of the function.
- link to v10: https://lore.kernel.org/all/20260217-add-mcp9982-hwmon-v10-0-5e0aaae6f289@microchip.com/

v10:
- in devicetree rework interrupt-names.
- rework conditionals to disable the channels that are not used.
- in patternProperties remove minItems from reg and change maxItems to 1.
- in mcp9982.rst fix indentation errors and clarify sysfs entries
  regarding limit hysteresis.
- add power state in the list of devicetree parameters.
- in driver change default high and critical temperature limit
  value to 85000.
- rework driver to not force wait until the next conversion is done.
  Only in Standby state wait the wake up time.
- fix typo in comment about mcp9982_priv.
- remove bitwise operations with mask FF.
- remove clamp_val() from limit hysteresis calculation.
- edit comments regarding block read and write.
- in mcp9982_write_limit() replace bulk write with two writes.
- in mcp9982_write() remove pointless assignment to ret.
- in mcp9982_init() do not override update interval.
- define mcp998x_chip_info as static const struct.
- link to v9: https://lore.kernel.org/all/20260127151823.9728-1-victor.duicu@microchip.com/

v9:
- update copyright year.
- add tempX_max_hyst and tempX_crit_hyst attributes and document
  them in mcp9982.rst.
- in include list add byteorder/generic.h and remove unaligned.h.
- remove definitions for temperature memory block
  and status memory block.
- remove individual definitions for register addresses 1Dh->21h.
- add constants MCP9982_WAKE_UP_TIME_MAX_US and
  MCP9982_TIMER_BUFFER_US.
- add checks to ensure that values read from registers are on 8 bits.
- in mcp9982_read_limit() simplify calculation, replace bulk read
  with individual operations and add comment.
- in mcp9982_read_limit() add explicit case branches for limits
  that are on 16 bits.
- in mcp9982_read() replace mdelay() with usleep_range().
- in mcp9982_read() replace block reading for temperature values with
  individual operations, add comment and remove unnecessary
  mask variable.
- in regmap_read_poll_timeout() add final timeout.
- in mcp9982_read_label() remove label check.
- in mcp9982_write_limit() replace put_unaligned_be16() with cpu_to_be16().
- in mcp9982_write_limit() add explicit case branches for limits
  that are on 16 bits.
- in mcp9982_init() write default value for diode alert mask register.
- in mcp9982_parse_fw_config() replace E2BIG with EINVAL.
- link to v8: https://lore.kernel.org/all/20251120071248.3767-1-victor.duicu@microchip.com/

v8:
- in Kconfig add select REGMAP_I2C.
- in yaml add power state attribute. For chips with "D" in the name
  check that Run mode is set in yaml and driver.
- in the include list: remove cleanup.h, add math.h, minmax.h and
  util_macros.h.
- add min, max and crit limits for all channels. These attributes can
  be read and written. In mcp9982_init() set default values for limits.
- add alarms for limits.
- edit regmap ranges to add the limit registers.
- when writing update interval, don't force the user to set exact value.
  Search for closest valid value.
- in mcp9982_parse_fw_config() check value from fwnode_property_read_u32().
- edit coding style and comments.
- remove constant MCP9982_SCALE.
- rename variable sampl_idx from mcp9982_priv to interval_idx.
- in mcp9982_write() rename variable use_previous_freq
  to use_previous_interval.
- link to v7: https://lore.kernel.org/all/20251031155831.42763-1-victor.duicu@microchip.com/

v7:
- send driver to hwmon subsystem.
- include index.rst and mcp9982.rst.
- in microchip,mcp9982.yaml set microchip,parasitic-res-on-channel1-2
  and 3-4 to required.
- in mcp9982.c rework read raw, read label and write raw functions.
- remove avail parameters.
- rework sampling frequency to update interval.
- lock running average filter to off.
- rework definition of channels.
- add cache type Maple to mcp9982_regmap_config().
- define constants for the numerical values used.
- in include list add: bitops.h, cleanup.h, device.h, hwmon.h,
  time64.h, unaligned.h. Remove iio.h, math64.h, string.h, units.h.
- add explicit definitions for beta and ideality registers.
- add definitions for status memory block registers.
- add mcp9982_is_visible() and set visible only the channels
  that are enabled.
- in mcp9982_parse_fw_config() add branch with default values for
  systems that do not have devicetree or firmware nodes.
- remove mutex.
- link to v6: https://lore.kernel.org/all/20250930133131.13797-1-victor.duicu@microchip.com/

Differences related to the IIO patch:

v6:
- in yaml first condition list part numbers instead
  of regular expression. Add ^ to regular expression.
- edit coding style and comments.
- use hex values in defines.
- remove MCP9982_TEMP_MEM_BLOCK_LOW and
  MCP9982_TEMP_MEM_BLOCK_HIGH.
- in MCP9982_CHAN() place macro parameters in ().
- move all variable definitions at the start of functions.
- in mcp9982_parse_fw_config() initialise iio_idx to 0.
- remove bit flags.
- in MCP9982_CHAN remove outer ().
- remove variable start in mcp9982_write_raw().
- replace constant in .max_register.
- use get_unaligned_be16 in mcp9982_read_raw().
- link to v5: https://lore.kernel.org/all/20250918111937.5150-1-victor.duicu@microchip.com/

v5:
- in yaml edit description of interrupts.
- add min and maxItems to reg.
- remove ideality parameter.
- use pattern recognition in conditionals.
- group conditions based on the chip.
- correct microchip,parasitic-res-on-channel3-4 to true.
- in driver include bitops.h.
- change name of some variables.
- rename mcp9982_parse_of_config() to mcp9982_parse_fw_config().
- implement bulk reading of temp registers.
- lock ideality parameter to default value.
- implement bit flags.
- add compound literal to MCP9982_CHAN.
- remove hysteresis parameter.
- edit comments.
- change values from int to bool in mcp9982_features.
- remove mcp9982_calc_all_3db_values() and hardcode values.
  When filter is OFF the 3db value is equal to frequency.
- add .max_register to regmap_config.
- remove devm_kcalloc().
- in mcp9982_read_avail() add an else branch to hw_thermal_shutdown
  check.
- in mcp9982_read_raw use USEC_PER_MSEC and set regmap_read_poll_timeout
  to never timeout.
  Replace switch with bitmap_weight.
- in mcp9982_read_label() remove unnecessary if.
- in mcp9982_write_raw() remove duplicated code.
- in mcp9982_init add error messages when APDD and RECD are incorrectly
  set.
- in mcp9982_parse_fw_config() add default for reg_nr.
- link to v4: https://lore.kernel.org/all/20250829143447.18893-1-victor.duicu@microchip.com/

v4:
- lock beta parameters to default value of beta-autodetect.
  Remove beta parameters and checks from devicetree.
- lock temperature range to extended.
  This change avoids the issue of the average filter using raw values
  with different scales when changing the range.
- change driver to wait an amount of time before reading a raw value
  to ensure it is valid.
- change driver to stop calculating the physical temp when reading
  in_tempx_raw. Reading from in_tempx_raw will return the raw value.
  The physical temp will be calculated with in_tempx_raw, scale and
  offset parameters.
- add scale parameter to channel definition.
- initialise chips with "D" to work in Run state and those without
  in Standby state.
- when activating the low pass filter for chips without "D",
  set the power state to RUN to ensure fresh values for the average.
- add minimum and maximum to microchip,beta1 and microchip,beta2 in yaml.
- rename microchip,resistance-comp-ch1-2-enable and
  microchip,resistance-comp-ch3-4-enable to
  microchip,parasitic-res-on-channel1-2
  and microchip,parasitic-res-on-channel3-4
  and edit description in yaml.
- add conditional logic to check if the chip supports APDD
  and force default values where necessary in yaml.
- edit comments and coding style.
- replace asm/div64.h with linux/math64.h.
- add delay.h to includes.
- redefine mcp9982_sampl_fr with new structure division.
- in mcp9982_priv remove dev_name,extended_temp_range and beta_values.
  Add run_state, wait_before_read, time_limit and pointer to chip
  structure to remove all instances of matching strings.
  Reorder parameters for memory optimization.
- in mcp9982_features add flags to know if the chip has thermal shutdown
  circuitry and supports APDD.
- in mcp9982_read_avail() rework verification of chip type in sampling
  frequency case.
- in mcp9982_read_raw() rework switch in low pass filter case.
- in mcp9982_parse_of_config() replace generic -EINVAL code
  with -E2BIG and -EOVERFLOW.
- link to v3: https://lore.kernel.org/all/20250613130207.8560-1-victor.duicu@microchip.com/

v3:
- move beta parameters to devicetree.
- change the name of the interrupts and add
  check to match them to the device in yaml.
- remove label for device and remove "0x" from
  channel registers in example in yaml.
- edit comments in yaml and driver.
- add minItems to interrupts in yaml.
- rename microchip,recd12 and microchip,recd34 to
  microchip,resistance-comp-ch1-2-enable
  and microchip,resistance-comp-ch3-4-enable.
- rename microchip,apdd-state to microchip,enable-anti-parallel.
- add static to mcp9982_3db_values_map_tbl to fix
  kernel test robot warning.
- in mcp9982_init() add check to ensure that hardware
  shutdown feature can't be overridden.
- replace div_u64_rem with do_div and add
  asm/div64.h to includes.
- remove unused includes.
- add iio_chan_spec in the macro definition of MCP9982_CHAN.
- remove MCP9982_EXT_BETA_ENBL.
- in mcp9982_init() replace regmap_assign_bits
  with regmap_write when setting beta compensation.
- remove custom attribute enable_extended_temp_range and
  map it to IIO_CHAN_INFO_OFFSET.
- add unsigned to int variables that allow it.
- reorder parameters in mcp9982_priv, change some
  from int to bool, add const to labels and add dev_name.
- add check for chips with "D" in the name to not
  allow sampling frequencies lower than 1 to
  prevent overriding of hardware shutdown.
- remove mcp9982_attributes.
- move mcp9982_calc_all_3db_values() to before
  mcp9982_init().
- use MICRO instead of number constant.
- in mcp9982_write_raw replace ">=" with "==".
- rename index2 to idx in mcp9982_read_raw().
- remove i2c_set_clientdata() in mcp9982_probe().
- since there are no more custom ABI attributes
  the testing file was removed.
- link to v2: https://lore.kernel.org/all/20250529093628.15042-1-victor.duicu@microchip.com/

v2:
- move hysteresis, extended temperature range and beta parameters
  from devicetree into user space.
- edit comments in yaml and driver.
- remove "|" in descpriptions, remove "+" from PatternProperties in yaml.
- add default to microchip,ideality-factor, delete blank lines and wrap to
  80 chars in yaml.
- remove variables with upper case.
- add check for microchip,apdd-state and microchip,recd34 in yaml.
- improve coding style in driver code.
- add includes for all functions used.
- rename MCP9982_INT_HIGH_BYTE_ADDR to MCP9982_INT_VALUE_ADDR and
  MCP9982_INT_LOW_BYTE_ADDR to MCP9982_FRAC_VALUE_ADDR.
- remove custom attribute running_average_window and
  running_average_window_available and map them to a low pass filter.
- update sysfs-bus-iio-temperature-mcp9982 to reflect current
  driver attributes and point to next kernel version (6.17).
- use compound literal to define driver channels.
- replace device_property_read_string() with i2c_get_match_data() to read
  chip name from devicetree.
- remove MCP9982_DEV_ATTR and mcp9982_prep_custom_attributes().
- remove client, chip_name, iio_info from mcp9982_priv.
- replace sprintf() with sysfs_emit().
- remove error messages which are triggered by keyboard input.
- replace devm_kzalloc() with devm_kcalloc(), array mcp9982_chip_config[]
  with individual structures, device_property_present() with
  device_property_read_bool().
- reordered parameters in mcp9982_features and mcp9982_priv to optimize
  memory allocation.
- remove .endianness from channel properties.
- change name of some parameters in mcp9982_priv.
- add check for reg value 0 from devicetree (channel 0 is for internal
  temperature and can't be disabled).
- link to v1: https://lore.kernel.org/all/20250415132623.14913-1-victor.duicu@microchip.com/

v1:
- initial version.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
Victor Duicu (2):
      dt-bindings: hwmon: add support for MCP998X
      hwmon: add support for MCP998X

 .../bindings/hwmon/microchip,mcp9982.yaml          | 237 +++++
 Documentation/hwmon/index.rst                      |   1 +
 Documentation/hwmon/mcp9982.rst                    | 111 +++
 MAINTAINERS                                        |   8 +
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/mcp9982.c                            | 997 +++++++++++++++++++++
 7 files changed, 1366 insertions(+)
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260305-add-mcp9982-hwmon-9ac964ca3191

Best regards,
-- 
Victor Duicu <victor.duicu@microchip.com>


^ permalink raw reply

* [PATCH v2] Documentation: gpio: update the preferred method for using software node lookup
From: Bartosz Golaszewski @ 2026-04-03 13:04 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski, Jonathan Corbet, Shuah Khan,
	Dmitry Torokhov
  Cc: linux-gpio, linux-doc, linux-kernel, Bartosz Golaszewski

In its current version, the manual for converting of board files from
using GPIO lookup tables to software nodes recommends leaving the
software nodes representing GPIO controllers as "free-floating", not
attached objects and relying on the matching of their names against the
GPIO controller's name. This is an abuse of the software node API and
makes it impossible to create fw_devlinks between GPIO suppliers and
consumers in this case. We want to remove this behavior from GPIOLIB and
to this end, work on converting all existing drivers to using "attached"
software nodes.

Except for a few corner-cases where board files define consumers
depending on GPIO controllers described in firmware - where we need to
reference a real firmware node from a software node - which requires a
more complex approach, most board files can easily be converted to using
propert firmware node lookup.

Update the documentation to recommend attaching the GPIO chip's software
nodes to the actual platform devices and show how to do it.

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Changes in v2:
- Use the new .swnode field of struct platform_device_info in examples
- Fix whitespaces
- Link to v1: https://patch.msgid.link/20260331-doc-gpio-swnodes-v1-1-3f84c268999b@oss.qualcomm.com
---
 Documentation/driver-api/gpio/board.rst         | 20 +++++++++++---
 Documentation/driver-api/gpio/legacy-boards.rst | 36 ++++++++++++++++++-------
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst
index 0993cac891fb5e4887a1aee6deae273197c6aae1..b306c21481d7c191201d81d228a290a908cc82ab 100644
--- a/Documentation/driver-api/gpio/board.rst
+++ b/Documentation/driver-api/gpio/board.rst
@@ -108,9 +108,8 @@ macro, which ties a software node representing the GPIO controller with
 consumer device. It allows consumers to use regular gpiolib APIs, such as
 gpiod_get(), gpiod_get_optional().
 
-The software node representing a GPIO controller need not be attached to the
-GPIO controller device. The only requirement is that the node must be
-registered and its name must match the GPIO controller's label.
+The software node representing a GPIO controller must be attached to the
+GPIO controller device - either as the primary or the secondary firmware node.
 
 For example, here is how to describe a single GPIO-connected LED. This is an
 alternative to using platform_data on legacy systems.
@@ -153,6 +152,21 @@ alternative to using platform_data on legacy systems.
 	};
 	software_node_register_node_group(swnodes);
 
+	/*
+	 * 5. Attach the GPIO controller's software node to the device and
+	 *    register it.
+	 */
+	 static void gpio_foo_register(void)
+	 {
+		struct platform_device_info pdev_info = {
+			.name = "gpio-foo",
+			.id = PLATFORM_DEVID_NONE,
+			.swnode = &gpio_controller_node
+		};
+
+		platform_device_register_full(&pdev_info);
+	 }
+
 	// Then register a platform_device for "leds-gpio" and associate
 	// it with &led_device_swnode via .fwnode.
 
diff --git a/Documentation/driver-api/gpio/legacy-boards.rst b/Documentation/driver-api/gpio/legacy-boards.rst
index 46e3a26dba772e5e5117866b5d202e76c8e2adf2..a9d33bcbb176b5df99838bd03e43ec2ebf4d9db6 100644
--- a/Documentation/driver-api/gpio/legacy-boards.rst
+++ b/Documentation/driver-api/gpio/legacy-boards.rst
@@ -36,12 +36,10 @@ Requirements for GPIO Properties
 When using software nodes to describe GPIO connections, the following
 requirements must be met for the GPIO core to correctly resolve the reference:
 
-1.  **The GPIO controller's software node "name" must match the controller's
-    "label".** The gpiolib core uses this name to find the corresponding
-    struct gpio_chip at runtime.
-    This software node has to be registered, but need not be attached to the
-    device representing the GPIO controller that is providing the GPIO in
-    question. It may be left as a "free floating" node.
+1.  **The GPIO controller's software node must be registered and attached to
+    the controller's ``struct device`` either as its primary or secondary
+    firmware node.** The gpiolib core uses the address of the firmware node to
+    find the corresponding ``struct gpio_chip`` at runtime.
 
 2.  **The GPIO property must be a reference.** The ``PROPERTY_ENTRY_GPIO()``
     macro handles this as it is an alias for ``PROPERTY_ENTRY_REF()``.
@@ -121,13 +119,21 @@ A typical legacy board file might look like this:
   /* Device registration */
   static int __init myboard_init(void)
   {
+  	struct platform_device_info pdev_info = {
+  		.name = MYBOARD_GPIO_CONTROLLER,
+  		.id = PLATFORM_DEVID_NONE,
+  		.swnode = &gpio_controller_node
+  	};
+
   	gpiod_add_lookup_table(&myboard_leds_gpios);
   	gpiod_add_lookup_table(&myboard_buttons_gpios);
 
+  	platform_device_register_full(&pdev_info);
   	platform_device_register_data(NULL, "leds-gpio", -1,
   				      &myboard_leds_pdata, sizeof(myboard_leds_pdata));
   	platform_device_register_data(NULL, "gpio-keys", -1,
-  				      &myboard_buttons_pdata, sizeof(myboard_buttons_pdata));
+  				      &myboard_buttons_pdata,
+  				      sizeof(myboard_buttons_pdata));
 
   	return 0;
   }
@@ -141,8 +147,7 @@ Step 1: Define the GPIO Controller Node
 ***************************************
 
 First, define a software node that represents the GPIO controller that the
-LEDs and buttons are connected to. The ``name`` of this node must match the
-name of the driver for the GPIO controller (e.g., "gpio-foo").
+LEDs and buttons are connected to. The ``name`` of this node is optional.
 
 .. code-block:: c
 
@@ -257,6 +262,16 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	if (error)
   		return error;
 
+  	memset(&pdev_info, 0, sizeof(pdev_info));
+  	pdev_info.name = MYBOARD_GPIO_CONTROLLER;
+  	pdev_info.id = PLATFORM_DEVID_NONE;
+  	pdev_info.swnode = &myboard_gpio_controller_node;
+  	gpio_pdev = platform_device_register_full(&pdev_info);
+  	if (IS_ERR(gpio_pdev)) {
+  		error = PTR_ERR(gpio_pdev);
+  		goto err_unregister_nodes;
+  	}
+
   	memset(&pdev_info, 0, sizeof(pdev_info));
   	pdev_info.name = "leds-gpio";
   	pdev_info.id = PLATFORM_DEVID_NONE;
@@ -264,6 +279,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	leds_pdev = platform_device_register_full(&pdev_info);
   	if (IS_ERR(leds_pdev)) {
   		error = PTR_ERR(leds_pdev);
+  		platform_device_unregister(gpio_pdev);
   		goto err_unregister_nodes;
   	}
 
@@ -274,6 +290,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	keys_pdev = platform_device_register_full(&pdev_info);
   	if (IS_ERR(keys_pdev)) {
   		error = PTR_ERR(keys_pdev);
+  		platform_device_unregister(gpio_pdev);
   		platform_device_unregister(leds_pdev);
   		goto err_unregister_nodes;
   	}
@@ -289,6 +306,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   {
   	platform_device_unregister(keys_pdev);
   	platform_device_unregister(leds_pdev);
+  	platform_device_unregister(gpio_pdev);
   	software_node_unregister_node_group(myboard_swnodes);
   }
 

---
base-commit: cc13002a9f984d37906e9476f3e532a8cdd126f5
change-id: 20260331-doc-gpio-swnodes-fc3ddf59b8dc

Best regards,
-- 
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>


^ permalink raw reply related

* [PATCH v9 0/2] Add support for Microchip EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea,
	Conor Dooley

This is the hwmon driver for EMC1812/13/14/15/33 multichannel Low-Voltage
Remote Diode Sensor Family. The chips in the family have one internal
and different numbers of external channels, ranging from 1 (EMC1812) to
4 channels (EMC1815).
Reading diodes in anti-parallel connection is supported by EMC1814, EMC1815
and EMC1833.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
Changes in v9:
- improve the wording in the Documentation/hwmon/emc1812.rst file
- add const to variables in the driver
- initialize the EXT2_BETA_CONFIG only for the pats that support it
- update the writeble regmap table to exclude read-only registers
- Link to v8: https://lore.kernel.org/r/20260310-hw_mon-emc1812-v8-0-bc155727e0d2@microchip.com

Changes in v8:
- remove "address scan" from emc1812.rst documentation
- change the second dimension of emc1812_limit_regs_low[][] to 2
- clamp input value before doing math on it to avoid overflow
- use rounding instead of truncation for 8 bits limit registers
- fix misleading comment when HW ID is not recognized
- Link to v7: https://lore.kernel.org/r/20260223-hw_mon-emc1812-v7-0-51e2676f4e20@microchip.com

Changes in v7:
- driver
  - fix an overflow emc1812_set_hyst
  - remove unused parameter in emc1812_set_temp
- devicetree binding:
  - remove unneeded restrictions not to bloating the binding
- Link to v6: https://lore.kernel.org/r/20260212-hw_mon-emc1812-v6-0-e37e9b38d898@microchip.com

Changes in v6:
- driver
  - fix an overflow when writing more then 191875 to limits stored on 8
    bits register
  - remove "i2c_set_clientdata" from probe
  - fix discrepancy where writing 16ms and reading it back returns 15ms
    at update interval
  - skip setting the ideality factor for channels that are not available
    on the device
- devicetree binding:
  - change the way interrupts are described/used
  - add "microchip,enable-anti-parallel"
  - rewrite "allOf" section to be more clear
- Link to v5: https://lore.kernel.org/r/20260205-hw_mon-emc1812-v5-0-232835aefe8f@microchip.com

Changes in v5:
- fix calculation in emc1812_get_limit_temp 
- use i2c_get_match_data cover the case when the driver is instantiated
  via I2C ID table.
- replace dev_info with dev_warn
- remove some unnecessary truncation on 8 bits
- remove clamping when reading the temerature with hyst
- not change the conversion rate at probe time
- use a generic define to remove duplicate channel_info entries
- Link to v4: https://lore.kernel.org/r/20260127-hw_mon-emc1812-v4-0-6bf636b54847@microchip.com

Changes in v4:
- fix file permissions for read only properties
- fix calculation when the limits are written
- remove the temp_min_hyst because the part doesn't support it
- Link to v3: https://lore.kernel.org/r/20251218-hw_mon-emc1812-v3-0-a123ada7b859@microchip.com

Changes in v3:
- remove mesages that are not helpfull
- fix an issue related to NULL labels
- fix sign/unsign calculation
- replace E2BIG with EINVAL
- use BIT() to create mask
- Link to v2: https://lore.kernel.org/r/20251121-hw_mon-emc1812-v2-0-5b2070f8b778@microchip.com

Changes in v2:
- update the interrupt section from yaml file
- update index.rst
- remove fault condition from internal sensor
- remove unused members from structures
- update the driver to work on systems without device tree or
  firmware nodes
- add missing include files
- make NULL labels to be not visible
- corect sign/unsign calculations
- corect possible underflow for limits
- Link to v1: https://lore.kernel.org/r/20251029-hw_mon-emc1812-v1-0-be4fd8af016a@microchip.com

---
Marius Cristea (2):
      dt-bindings: hwmon: temperature: add support for EMC1812
      hwmon: temperature: add support for EMC1812

 .../bindings/hwmon/microchip,emc1812.yaml          | 184 ++++
 Documentation/hwmon/emc1812.rst                    |  67 ++
 Documentation/hwmon/index.rst                      |   1 +
 MAINTAINERS                                        |   8 +
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/emc1812.c                            | 965 +++++++++++++++++++++
 7 files changed, 1237 insertions(+)
---
base-commit: d2b2fea3503e5e12b2e28784152937e48bcca6ff
change-id: 20251002-hw_mon-emc1812-f1b806487d10

Best regards,
-- 
Marius Cristea <marius.cristea@microchip.com>


^ permalink raw reply

* [PATCH v9 2/2] hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea
In-Reply-To: <20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@microchip.com>

This is the hwmon driver for Microchip EMC1812/13/14/15/33
Multichannel Low-Voltage Remote Diode Sensor Family.

EMC1812 has one external remote temperature monitoring channel.
EMC1813 has two external remote temperature monitoring channels.
EMC1814 has three external remote temperature monitoring channels and
channels 2 and 3 support anti parallel diode.
EMC1815 has four external remote temperature monitoring channels and
channels 1/2  and 3/4 support anti parallel diode.
EMC1833 has two external remote temperature monitoring channels and
channels 1 and 2 support anti parallel diode.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
 Documentation/hwmon/emc1812.rst |  67 +++
 Documentation/hwmon/index.rst   |   1 +
 MAINTAINERS                     |   2 +
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/emc1812.c         | 965 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1047 insertions(+)

diff --git a/Documentation/hwmon/emc1812.rst b/Documentation/hwmon/emc1812.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0b4fbcaaea71a42ef4db1be25182ab363a136a5d
--- /dev/null
+++ b/Documentation/hwmon/emc1812.rst
@@ -0,0 +1,67 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver emc1812
+=====================
+
+Supported chips:
+
+  * Microchip EMC1812, EMC1813, EMC1814, EMC1815, EMC1833
+
+    Prefix: 'emc1812'
+
+    Datasheets:
+
+	- https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+
+Author:
+    Marius Cristea <marius.cristea@microchip.com>
+
+
+Description
+-----------
+
+The Microchip EMC181x/33 chips contain up to 4 remote temperature sensors
+and one internal.
+- The EMC1812 is a single channel remote temperature sensor.
+- The EMC1813 and EMC1833 are dual channel remote temperature sensor. The
+remote channels for this selection of devices can support substrate diodes,
+discrete diode-connected transistors or CPU/GPU thermal diodes.
+- The EMC1814 is a three channel remote temperature sensor that supports
+Anti-Parallel Diode (APD) only on one channel. For the channel that does not
+support APD functionality, substrate diodes, discrete diode-connected
+transistors or CPU/GPU thermal diodes are supported. For the channel that
+supports APD, only discrete diode-connected transistors may be implemented.
+However, if APD is disabled on the EMC1814, then the channel that supports
+APD will be functional with substrate diodes, discrete diode-connected
+transistors and CPU/GPU thermal diodes.
+- The EMC1815 is a four channel remote temperature sensor.
+
+The EMC1815 and EMC1833 support APD on all channels. When APD is enabled,
+the channels support only diode-connected transistors. If APD is disabled,
+then the channels will support substrate transistors, discrete diode-connected
+transistors and CPU/GPU thermal diodes.
+
+Note: Disabling APD functionality to implement substrate diodes on devices
+that support APD eliminates the benefit of APD (two diodes on one channel).
+
+The chips implement three limits for each sensor: low (tempX_min), high
+(tempX_max) and critical (tempX_crit). The chips also implement an
+hysteresis mechanism which applies to all limits. The relative difference
+is stored in a single register on the chip, which means that the relative
+difference between the limit and its hysteresis is always the same for
+all three limits.
+
+This implementation detail implies the following:
+
+* When setting a limit, its hysteresis will automatically follow, the
+  difference staying unchanged. For example, if the old critical limit was
+  80 degrees C, and the hysteresis was 75 degrees C, and you change the
+  critical limit to 90 degrees C, then the hysteresis will automatically
+  change to 85 degrees C.
+* The hysteresis values can't be set independently. We decided to make
+  only tempX_crit_hyst writable, while all other hysteresis attributes
+  are read-only. Setting tempX_crit_hyst writes the difference between
+  tempX_crit_hyst and tempX_crit into the chip, and the same relative
+  hysteresis applies automatically to all other limits.
+* The limits should be set before the hysteresis. At power up the device
+  starts with 10 degree hysteresis.
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 51a5bdf75b08656ee6499c6b5c50a51fc4d7c210..a03e97f9a97f4d3edf7bcd1e8d1b73a21d5f0ab5 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -69,6 +69,7 @@ Hardware Monitoring Kernel Drivers
    ds1621
    ds620
    emc1403
+   emc1812
    emc2103
    emc2305
    emc6w201
diff --git a/MAINTAINERS b/MAINTAINERS
index 85c236df781e47c78deeb7ef4d80bc94bba604c4..fcb712549ea679d49fde8c97840af9528b52d52b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16651,6 +16651,8 @@ M:	Marius Cristea <marius.cristea@microchip.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
+F:	Documentation/hwmon/emc1812.rst
+F:	drivers/hwmon/emc1812.c
 
 MICROCHIP I2C DRIVER
 M:	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2760feb9f83b5d3b990b27acff572e587b373e9d..3b53572fd8bfbd752c2235ca429c4f74b1db3095 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2042,6 +2042,17 @@ config SENSORS_EMC1403
 	  Threshold values can be configured using sysfs.
 	  Data from the different diodes are accessible via sysfs.
 
+config SENSORS_EMC1812
+	tristate "Microchip Technology EMC1812 driver"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  If you say yes here to build support for Microchip Technology's
+	  EMC181X/33  Multichannel Low-Voltage Remote Diode Sensor Family.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called emc1812.
+
 config SENSORS_EMC2103
 	tristate "SMSC EMC2103"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 73b2abdcc6dd9cfae4c84b350febc5d8c191e385..e93e4051e99db698dbaae97ac4841e6d810ee8c4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP)	+= drivetemp.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
+obj-$(CONFIG_SENSORS_EMC1812)	+= emc1812.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC2305)	+= emc2305.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
diff --git a/drivers/hwmon/emc1812.c b/drivers/hwmon/emc1812.c
new file mode 100644
index 0000000000000000000000000000000000000000..1ba8feac93b5f078d4e544c72711e69f0be06238
--- /dev/null
+++ b/drivers/hwmon/emc1812.c
@@ -0,0 +1,965 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for Microchip EMC1812/13/14/15/33 Multichannel high-accuracy
+ * 2-wire low-voltage remote diode temperature monitor family.
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Marius Cristea <marius.cristea@microchip.com>
+ *
+ * Datasheet can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+/* EMC1812 Registers Addresses */
+#define EMC1812_STATUS_ADDR				0x02
+#define EMC1812_CONFIG_LO_ADDR				0x03
+
+#define EMC1812_CFG_ADDR				0x09
+#define EMC1812_CONV_ADDR				0x0A
+#define EMC1812_INT_DIODE_HIGH_LIMIT_ADDR		0x0B
+#define EMC1812_INT_DIODE_LOW_LIMIT_ADDR		0x0C
+#define EMC1812_EXT1_HIGH_LIMIT_HIGH_BYTE_ADDR		0x0D
+#define EMC1812_EXT1_LOW_LIMIT_HIGH_BYTE_ADDR		0x0E
+#define EMC1812_ONE_SHOT_ADDR				0x0F
+
+#define EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR		0x13
+#define EMC1812_EXT1_LOW_LIMIT_LOW_BYTE_ADDR		0x14
+#define EMC1812_EXT2_HIGH_LIMIT_HIGH_BYTE_ADDR		0x15
+#define EMC1812_EXT2_LOW_LIMIT_HIGH_BYTE_ADDR		0x16
+#define EMC1812_EXT2_HIGH_LIMIT_LOW_BYTE_ADDR		0x17
+#define EMC1812_EXT2_LOW_LIMIT_LOW_BYTE_ADDR		0x18
+#define EMC1812_EXT1_THERM_LIMIT_ADDR			0x19
+#define EMC1812_EXT2_THERM_LIMIT_ADDR			0x1A
+#define EMC1812_EXT_DIODE_FAULT_STATUS_ADDR		0x1B
+
+#define EMC1812_DIODE_FAULT_MASK_ADDR			0x1F
+#define EMC1812_INT_DIODE_THERM_LIMIT_ADDR		0x20
+#define EMC1812_THRM_HYS_ADDR				0x21
+#define EMC1812_CONSEC_ALERT_ADDR			0x22
+
+#define EMC1812_EXT1_BETA_CONFIG_ADDR			0x25
+#define EMC1812_EXT2_BETA_CONFIG_ADDR			0x26
+#define EMC1812_EXT1_IDEALITY_FACTOR_ADDR		0x27
+#define EMC1812_EXT2_IDEALITY_FACTOR_ADDR		0x28
+
+#define EMC1812_EXT3_HIGH_LIMIT_HIGH_BYTE_ADDR		0x2C
+#define EMC1812_EXT3_LOW_LIMIT_HIGH_BYTE_ADDR		0x2D
+#define EMC1812_EXT3_HIGH_LIMIT_LOW_BYTE_ADDR		0x2E
+#define EMC1812_EXT3_LOW_LIMIT_LOW_BYTE_ADDR		0x2F
+#define EMC1812_EXT3_THERM_LIMIT_ADDR			0x30
+#define EMC1812_EXT3_IDEALITY_FACTOR_ADDR		0x31
+
+#define EMC1812_EXT4_HIGH_LIMIT_HIGH_BYTE_ADDR		0x34
+#define EMC1812_EXT4_LOW_LIMIT_HIGH_BYTE_ADDR		0x35
+#define EMC1812_EXT4_HIGH_LIMIT_LOW_BYTE_ADDR		0x36
+#define EMC1812_EXT4_LOW_LIMIT_LOW_BYTE_ADDR		0x37
+#define EMC1812_EXT4_THERM_LIMIT_ADDR			0x38
+#define EMC1812_EXT4_IDEALITY_FACTOR_ADDR		0x39
+#define EMC1812_HIGH_LIMIT_STATUS_ADDR			0x3A
+#define EMC1812_LOW_LIMIT_STATUS_ADDR			0x3B
+#define EMC1812_THERM_LIMIT_STATUS_ADDR			0x3C
+#define EMC1812_ROC_GAIN_ADDR				0x3D
+#define EMC1812_ROC_CONFIG_ADDR				0x3E
+#define EMC1812_ROC_STATUS_ADDR				0x3F
+#define EMC1812_R1_RESH_ADDR				0x40
+#define EMC1812_R1_LIMH_ADDR				0x41
+#define EMC1812_R1_LIML_ADDR				0x42
+#define EMC1812_R1_SMPL_ADDR				0x43
+#define EMC1812_R2_RESH_ADDR				0x44
+#define EMC1812_R2_3_RESL_ADDR				0x45
+#define EMC1812_R2_LIMH_ADDR				0x46
+#define EMC1812_R2_LIML_ADDR				0x47
+#define EMC1812_R2_SMPL_ADDR				0x48
+#define EMC1812_PER_MAXTH_1_ADDR			0x49
+#define EMC1812_PER_MAXT1L_ADDR				0x4A
+#define EMC1812_PER_MAXTH_2_ADDR			0x4B
+#define EMC1812_PER_MAXT2_3L_ADDR			0x4C
+#define EMC1812_GBL_MAXT1H_ADDR				0x4D
+#define EMC1812_GBL_MAXT1L_ADDR				0x4E
+#define EMC1812_GBL_MAXT2H_ADDR				0x4F
+#define EMC1812_GBL_MAXT2L_ADDR				0x50
+#define EMC1812_FILTER_SEL_ADDR				0x51
+
+#define EMC1812_INT_HIGH_BYTE_ADDR		0x60
+#define EMC1812_INT_LOW_BYTE_ADDR		0x61
+#define EMC1812_EXT1_HIGH_BYTE_ADDR		0x62
+#define EMC1812_EXT1_LOW_BYTE_ADDR		0x63
+#define EMC1812_EXT2_HIGH_BYTE_ADDR		0x64
+#define EMC1812_EXT2_LOW_BYTE_ADDR		0x65
+#define EMC1812_EXT3_HIGH_BYTE_ADDR		0x66
+#define EMC1812_EXT3_LOW_BYTE_ADDR		0x67
+#define EMC1812_EXT4_HIGH_BYTE_ADDR		0x68
+#define EMC1812_EXT4_LOW_BYTE_ADDR		0x69
+#define EMC1812_HOTTEST_DIODE_HIGH_BYTE_ADDR	0x6A
+#define EMC1812_HOTTEST_DIODE_LOW_BYTE_ADDR	0x6B
+#define EMC1812_HOTTEST_STATUS_ADDR		0x6C
+#define EMC1812_HOTTEST_CFG_ADDR		0x6D
+
+#define EMC1812_PRODUCT_ID_ADDR		0xFD
+#define EMC1812_MANUFACTURER_ID_ADDR	0xFE
+#define EMC1812_REVISION_ADDR		0xFF
+
+/* EMC1812 Config Bits */
+#define EMC1812_CFG_MSKAL		BIT(7)
+#define EMC1812_CFG_RS			BIT(6)
+#define EMC1812_CFG_ATTHM		BIT(5)
+#define EMC1812_CFG_RECD12		BIT(4)
+#define EMC1812_CFG_RECD34		BIT(3)
+#define EMC1812_CFG_RANGE		BIT(2)
+#define EMC1812_CFG_DA_ENA		BIT(1)
+#define EMC1812_CFG_APDD		BIT(0)
+
+/* EMC1812 Status Bits */
+#define EMC1812_STATUS_ROCF		BIT(7)
+#define EMC1812_STATUS_HOTCHG		BIT(6)
+#define EMC1812_STATUS_BUSY		BIT(5)
+#define EMC1812_STATUS_HIGH		BIT(4)
+#define EMC1812_STATUS_LOW		BIT(3)
+#define EMC1812_STATUS_FAULT		BIT(2)
+#define EMC1812_STATUS_ETHRM		BIT(1)
+#define EMC1812_STATUS_ITHRM		BIT(0)
+
+#define EMC1812_BETA_LOCK_VAL		0x0F
+
+#define EMC1812_TEMP_CH_ADDR(index)	(EMC1812_INT_HIGH_BYTE_ADDR + 2 * (index))
+
+#define EMC1812_FILTER_MASK_LEN		2
+
+#define EMC1812_PID			0x81
+#define EMC1813_PID			0x87
+#define EMC1814_PID			0x84
+#define EMC1815_PID			0x85
+#define EMC1833_PID			0x83
+
+/* The maximum number of channels a member of the family can have */
+#define EMC1812_MAX_NUM_CHANNELS		5
+#define EMC1812_TEMP_OFFSET			64
+
+#define EMC1812_DEFAULT_IDEALITY_FACTOR		0x12
+
+#define EMC1812_TEMP_MASK (HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | \
+			   HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | \
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | \
+			   HWMON_T_CRIT_ALARM | HWMON_T_LABEL)
+
+static const struct hwmon_channel_info * const emc1812_info[] = {
+	HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+	HWMON_CHANNEL_INFO(temp,
+			   EMC1812_TEMP_MASK,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT
+			   ),
+	NULL
+};
+
+/**
+ * struct emc1812_features - features of a emc1812 instance
+ * @name:		chip's name
+ * @phys_channels:	number of physical channels supported by the chip
+ * @has_ext2_beta_reg:	the EXT2_BETA register is available on the chip
+ */
+struct emc1812_features {
+	const char	*name;
+	u8		phys_channels;
+	bool		has_ext2_beta_reg;
+};
+
+static const struct emc1812_features emc1833_chip_config = {
+	.name = "emc1833",
+	.phys_channels = 3,
+	.has_ext2_beta_reg = true,
+};
+
+static const struct emc1812_features emc1812_chip_config = {
+	.name = "emc1812",
+	.phys_channels = 2,
+	.has_ext2_beta_reg = false,
+};
+
+static const struct emc1812_features emc1813_chip_config = {
+	.name = "emc1813",
+	.phys_channels = 3,
+	.has_ext2_beta_reg = true,
+};
+
+static const struct emc1812_features emc1814_chip_config = {
+	.name = "emc1814",
+	.phys_channels = 4,
+	.has_ext2_beta_reg = false,
+};
+
+static const struct emc1812_features emc1815_chip_config = {
+	.name = "emc1815",
+	.phys_channels = 5,
+	.has_ext2_beta_reg = false,
+};
+
+enum emc1812_limit_type {temp_min, temp_max};
+
+static const u8 emc1812_temp_map[] = {
+	[hwmon_temp_min] = temp_min,
+	[hwmon_temp_max] = temp_max,
+};
+
+static const u8 emc1812_temp_crit_regs[] = {
+	[0] = EMC1812_INT_DIODE_THERM_LIMIT_ADDR,
+	[1] = EMC1812_EXT1_THERM_LIMIT_ADDR,
+	[2] = EMC1812_EXT2_THERM_LIMIT_ADDR,
+	[3] = EMC1812_EXT3_THERM_LIMIT_ADDR,
+	[4] = EMC1812_EXT4_THERM_LIMIT_ADDR,
+};
+
+static const u8 emc1812_limit_regs[][2] = {
+	[0] = {
+		[temp_min] = EMC1812_INT_DIODE_LOW_LIMIT_ADDR,
+		[temp_max] = EMC1812_INT_DIODE_HIGH_LIMIT_ADDR,
+	},
+	[1] = {
+		[temp_min] = EMC1812_EXT1_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT1_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[2] = {
+		[temp_min] = EMC1812_EXT2_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT2_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[3] = {
+		[temp_min] = EMC1812_EXT3_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT3_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[4] = {
+		[temp_min] = EMC1812_EXT4_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT4_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+};
+
+static const u8 emc1812_limit_regs_low[][2] = {
+	[0] = {
+		[temp_min] = 0xff,
+		[temp_max] = 0xff,
+	},
+	[1] = {
+		[temp_min] = EMC1812_EXT1_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[2] = {
+		[temp_min] = EMC1812_EXT2_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT2_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[3] = {
+		[temp_min] = EMC1812_EXT3_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT3_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[4] = {
+		[temp_min] = EMC1812_EXT4_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT4_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+};
+
+/* Lookup table for temperature conversion times in msec */
+static const u16 emc1812_conv_time[] = {
+	16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62, 31, 16
+};
+
+/**
+ * struct emc1812_data - information about chip parameters
+ * @labels:		labels of the channels
+ * @active_ch_mask:	active channels
+ * @chip:		pointer to structure holding chip features
+ * @regmap:		device register map
+ * @recd34_en:		state of Resistance Error Correction (REC) on channels 3 and 4
+ * @recd12_en:		state of Resistance Error Correction (REC) on channels 1 and 2
+ * @apdd_en:		state of anti-parallel diode mode
+ */
+struct emc1812_data {
+	const char *labels[EMC1812_MAX_NUM_CHANNELS];
+	unsigned long active_ch_mask;
+	const struct emc1812_features *chip;
+	struct regmap *regmap;
+	bool recd34_en;
+	bool recd12_en;
+	bool apdd_en;
+};
+
+/* emc1812 regmap configuration */
+static const struct regmap_range emc1812_regmap_writable_ranges[] = {
+	regmap_reg_range(EMC1812_CFG_ADDR, EMC1812_ONE_SHOT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR, EMC1812_EXT2_THERM_LIMIT_ADDR),
+	regmap_reg_range(EMC1812_DIODE_FAULT_MASK_ADDR, EMC1812_CONSEC_ALERT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_BETA_CONFIG_ADDR, EMC1812_EXT4_IDEALITY_FACTOR_ADDR),
+	regmap_reg_range(EMC1812_ROC_GAIN_ADDR, EMC1812_ROC_CONFIG_ADDR),
+	regmap_reg_range(EMC1812_R1_LIMH_ADDR, EMC1812_R1_SMPL_ADDR),
+	regmap_reg_range(EMC1812_R2_LIMH_ADDR, EMC1812_R2_SMPL_ADDR),
+	regmap_reg_range(EMC1812_FILTER_SEL_ADDR, EMC1812_FILTER_SEL_ADDR),
+	regmap_reg_range(EMC1812_HOTTEST_CFG_ADDR, EMC1812_HOTTEST_CFG_ADDR),
+};
+
+static const struct regmap_access_table emc1812_regmap_wr_table = {
+	.yes_ranges = emc1812_regmap_writable_ranges,
+	.n_yes_ranges = ARRAY_SIZE(emc1812_regmap_writable_ranges),
+};
+
+static const struct regmap_range emc1812_regmap_rd_ranges[] = {
+	regmap_reg_range(EMC1812_STATUS_ADDR, EMC1812_CONFIG_LO_ADDR),
+	regmap_reg_range(EMC1812_CFG_ADDR, EMC1812_ONE_SHOT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR,
+			 EMC1812_EXT_DIODE_FAULT_STATUS_ADDR),
+	regmap_reg_range(EMC1812_DIODE_FAULT_MASK_ADDR, EMC1812_CONSEC_ALERT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_BETA_CONFIG_ADDR, EMC1812_FILTER_SEL_ADDR),
+	regmap_reg_range(EMC1812_INT_HIGH_BYTE_ADDR, EMC1812_HOTTEST_CFG_ADDR),
+	regmap_reg_range(EMC1812_PRODUCT_ID_ADDR, EMC1812_REVISION_ADDR),
+};
+
+static const struct regmap_access_table emc1812_regmap_rd_table = {
+	.yes_ranges = emc1812_regmap_rd_ranges,
+	.n_yes_ranges = ARRAY_SIZE(emc1812_regmap_rd_ranges),
+};
+
+static bool emc1812_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case EMC1812_STATUS_ADDR:
+	case EMC1812_EXT_DIODE_FAULT_STATUS_ADDR:
+	case EMC1812_DIODE_FAULT_MASK_ADDR:
+	case EMC1812_EXT1_BETA_CONFIG_ADDR:
+	case EMC1812_EXT2_BETA_CONFIG_ADDR:
+	case EMC1812_HIGH_LIMIT_STATUS_ADDR:
+	case EMC1812_LOW_LIMIT_STATUS_ADDR:
+	case EMC1812_THERM_LIMIT_STATUS_ADDR:
+	case EMC1812_ROC_STATUS_ADDR:
+	case EMC1812_PER_MAXTH_1_ADDR:
+	case EMC1812_PER_MAXT1L_ADDR:
+	case EMC1812_PER_MAXTH_2_ADDR:
+	case EMC1812_PER_MAXT2_3L_ADDR:
+	case EMC1812_GBL_MAXT1H_ADDR:
+	case EMC1812_GBL_MAXT1L_ADDR:
+	case EMC1812_GBL_MAXT2H_ADDR:
+	case EMC1812_GBL_MAXT2L_ADDR:
+	case EMC1812_INT_HIGH_BYTE_ADDR:
+	case EMC1812_INT_LOW_BYTE_ADDR:
+	case EMC1812_EXT1_HIGH_BYTE_ADDR:
+	case EMC1812_EXT1_LOW_BYTE_ADDR:
+	case EMC1812_EXT2_HIGH_BYTE_ADDR:
+	case EMC1812_EXT2_LOW_BYTE_ADDR:
+	case EMC1812_EXT3_HIGH_BYTE_ADDR:
+	case EMC1812_EXT3_LOW_BYTE_ADDR:
+	case EMC1812_EXT4_HIGH_BYTE_ADDR:
+	case EMC1812_EXT4_LOW_BYTE_ADDR:
+	case EMC1812_HOTTEST_DIODE_HIGH_BYTE_ADDR:
+	case EMC1812_HOTTEST_DIODE_LOW_BYTE_ADDR:
+	case EMC1812_HOTTEST_STATUS_ADDR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config emc1812_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.rd_table = &emc1812_regmap_rd_table,
+	.wr_table = &emc1812_regmap_wr_table,
+	.volatile_reg = emc1812_is_volatile_reg,
+	.max_register = EMC1812_REVISION_ADDR,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static umode_t emc1812_is_visible(const void *_data, enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	const struct emc1812_data *data = _data;
+
+	switch (type) {
+	case hwmon_temp:
+		/* Don't show channels which are not described into the device tree */
+		if (!(data->active_ch_mask & BIT(channel)))
+			return 0;
+
+		/* Don't show channels which are not physically connected */
+		if (channel >= data->chip->phys_channels)
+			return 0;
+
+		switch (attr) {
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+			return 0644;
+		case hwmon_temp_crit_alarm:
+		case hwmon_temp_input:
+		case hwmon_temp_fault:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_min_alarm:
+			return 0444;
+		case hwmon_temp_label:
+			if (data->labels[channel])
+				return 0444;
+			return 0;
+		default:
+			return 0;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+};
+
+static int emc1812_get_temp(struct emc1812_data *data, int channel, long *val)
+{
+	__be16 tmp_be16;
+	int ret;
+
+	ret = regmap_bulk_read(data->regmap, EMC1812_TEMP_CH_ADDR(channel),
+			       &tmp_be16, sizeof(tmp_be16));
+	if (ret)
+		return ret;
+
+	/* Range is always -64 to 191.875°C */
+	*val = ((be16_to_cpu(tmp_be16) >> 5) - (EMC1812_TEMP_OFFSET << 3)) * 125;
+
+	return 0;
+}
+
+static int emc1812_get_crit_limit_temp(struct emc1812_data *data, int channel, long *val)
+{
+	unsigned int tmp;
+	int ret;
+
+	/* Critical register is 8bits long and keeps only integer part of temperature */
+	ret = regmap_read(data->regmap, emc1812_temp_crit_regs[channel], &tmp);
+	if (ret)
+		return ret;
+
+	*val = tmp;
+	/* Range is always -64 to 191°C */
+	*val = (*val - EMC1812_TEMP_OFFSET) * 1000;
+
+	return 0;
+}
+
+static int emc1812_get_limit_temp(struct emc1812_data *data, int ch,
+				  enum emc1812_limit_type type, long *val)
+{
+	unsigned int regvalh;
+	unsigned int regvall = 0;
+	int ret;
+
+	ret = regmap_read(data->regmap, emc1812_limit_regs[ch][type], &regvalh);
+	if (ret < 0)
+		return ret;
+
+	if (ch) {
+		ret = regmap_read(data->regmap, emc1812_limit_regs_low[ch][type], &regvall);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Range is always -64 to 191.875°C */
+	*val = ((regvalh << 3) | (regvall >> 5));
+	*val = (*val - (EMC1812_TEMP_OFFSET << 3)) * 125;
+
+	return 0;
+}
+
+static int emc1812_read_reg(struct device *dev, struct emc1812_data *data, u32 attr,
+			    int channel, long *val)
+{
+	int hyst, ret;
+
+	switch (attr) {
+	case hwmon_temp_min:
+	case hwmon_temp_max:
+		return emc1812_get_limit_temp(data, channel, emc1812_temp_map[attr], val);
+	case hwmon_temp_crit:
+		return emc1812_get_crit_limit_temp(data, channel, val);
+	case hwmon_temp_input:
+		return emc1812_get_temp(data, channel, val);
+	case hwmon_temp_max_hyst:
+		ret = emc1812_get_limit_temp(data, channel, temp_max, val);
+		if (ret < 0)
+			return ret;
+
+		ret = regmap_read(data->regmap, EMC1812_THRM_HYS_ADDR, &hyst);
+		if (ret < 0)
+			return ret;
+
+		*val -= hyst * 1000;
+
+		return 0;
+	case hwmon_temp_crit_hyst:
+		ret = emc1812_get_crit_limit_temp(data, channel, val);
+		if (ret < 0)
+			return ret;
+
+		ret = regmap_read(data->regmap, EMC1812_THRM_HYS_ADDR, &hyst);
+		if (ret < 0)
+			return ret;
+
+		*val -= hyst * 1000;
+
+		return 0;
+	case hwmon_temp_min_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_LOW_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_max_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_HIGH_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_crit_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_THERM_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_fault:
+		*val = regmap_test_bits(data->regmap, EMC1812_EXT_DIODE_FAULT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			int channel, long *val)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+	unsigned int convrate;
+	int ret;
+
+	switch (type) {
+	case hwmon_temp:
+		return emc1812_read_reg(dev, data, attr, channel, val);
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			ret = regmap_read(data->regmap, EMC1812_CONV_ADDR, &convrate);
+			if (ret < 0)
+				return ret;
+
+			if (convrate > 10)
+				convrate = 4;
+
+			*val = DIV_ROUND_CLOSEST(16000, 1 << convrate);
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_read_string(struct device *dev, enum hwmon_sensor_types type,
+			       u32 attr, int channel, const char **str)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+
+	if (channel >= data->chip->phys_channels)
+		return 0;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = data->labels[channel];
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_set_hyst(struct emc1812_data *data, int channel, int val)
+{
+	int hyst, ret;
+	int limit;
+
+	/* Critical register is 8bits long and keeps only integer part of temperature */
+	ret = regmap_read(data->regmap, emc1812_temp_crit_regs[channel], &limit);
+	if (ret)
+		return ret;
+
+	hyst = clamp_val(limit - val, 0, 255);
+
+	ret = regmap_write(data->regmap, EMC1812_THRM_HYS_ADDR, hyst);
+
+	return ret;
+}
+
+static int emc1812_set_temp(struct emc1812_data *data, int channel,
+			    enum emc1812_limit_type map, int val)
+{
+	long valh, vall;
+	u8 regh, regl;
+	int ret;
+
+	regh = emc1812_limit_regs[channel][map];
+	regl = emc1812_limit_regs_low[channel][map];
+
+	if (channel) {
+		val = DIV_ROUND_CLOSEST(val, 125);
+		valh = (val >> 3) & 0xff;
+		vall = (val & 0x07) << 5;
+	} else {
+		/* Temperature limit for internal channel is stored on 8bits */
+		valh = DIV_ROUND_CLOSEST(val, 1000);
+		valh = clamp_val(valh, 0, 255);
+	}
+
+	ret = regmap_write(data->regmap, regh, valh);
+	if (ret < 0)
+		return ret;
+
+	if (channel)
+		ret = regmap_write(data->regmap, regl, vall);
+
+	return ret;
+}
+
+static int emc1812_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			 int channel, long val)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+	unsigned int interval;
+	int convrate;
+
+	switch (type) {
+	case hwmon_temp:
+		/* Range should be -64000 to 191875°C + (EMC1812_TEMP_OFFSET * 1000) */
+		val = clamp_val(val, -64000, 191875);
+		val = val + (EMC1812_TEMP_OFFSET * 1000);
+
+		switch (attr) {
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+			return emc1812_set_temp(data, channel, emc1812_temp_map[attr], val);
+		case hwmon_temp_crit:
+			/* Critical temperature limit is stored on 8bits */
+			val = DIV_ROUND_CLOSEST(val, 1000);
+			val = clamp_val(val, 0, 255);
+			return regmap_write(data->regmap, emc1812_temp_crit_regs[channel], val);
+		case hwmon_temp_crit_hyst:
+			/* Critical temperature hysteresis is stored on 8bits */
+			val = DIV_ROUND_CLOSEST(val, 1000);
+			val = clamp_val(val, 0, 255);
+			return emc1812_set_hyst(data, channel, val);
+		default:
+			return -EOPNOTSUPP;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			interval = clamp_val(val, 0, 16000);
+			convrate = find_closest_descending(interval, emc1812_conv_time,
+							   ARRAY_SIZE(emc1812_conv_time));
+			return regmap_write(data->regmap, EMC1812_CONV_ADDR, convrate);
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_init(struct emc1812_data *priv)
+{
+	int ret;
+	u8 val;
+
+	/*
+	 * Set default values in registers. APDD, RECD12 and RECD34 are active
+	 * on 0. Set ALERT pin to be in comparator mode.
+	 * Set the device to be in Run (Active) state and converting on all
+	 * channels.
+	 * Don't change conversion rate. After reset, default is 4 conversions/seconds.
+	 * The temperature measurement range is -64°C to +191.875°C.
+	 */
+	val = FIELD_PREP(EMC1812_CFG_MSKAL, 0) |
+	      FIELD_PREP(EMC1812_CFG_RS, 0) |
+	      FIELD_PREP(EMC1812_CFG_ATTHM, 1) |
+	      FIELD_PREP(EMC1812_CFG_RECD12, !priv->recd12_en) |
+	      FIELD_PREP(EMC1812_CFG_RECD34, !priv->recd34_en) |
+	      FIELD_PREP(EMC1812_CFG_RANGE, 1) |
+	      FIELD_PREP(EMC1812_CFG_DA_ENA, 0) |
+	      FIELD_PREP(EMC1812_CFG_APDD, !priv->apdd_en);
+
+	ret = regmap_write(priv->regmap, EMC1812_CFG_ADDR, val);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_THRM_HYS_ADDR, 0x0A);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_CONSEC_ALERT_ADDR, 0x70);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_FILTER_SEL_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_HOTTEST_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	/* Enables the beta compensation factor auto-detection function for beta1 and beta2 */
+	ret = regmap_write(priv->regmap, EMC1812_EXT1_BETA_CONFIG_ADDR,
+			   EMC1812_BETA_LOCK_VAL);
+	if (ret)
+		return ret;
+
+	if (priv->chip->has_ext2_beta_reg) {
+		ret = regmap_write(priv->regmap, EMC1812_EXT2_BETA_CONFIG_ADDR,
+				   EMC1812_BETA_LOCK_VAL);
+		if (ret)
+			return ret;
+	}
+
+	/* Set ideality factor for all external channels */
+	ret = regmap_write(priv->regmap, EMC1812_EXT1_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	/* Set the ideality factor only for the channels supported by the chip */
+	if (priv->chip->phys_channels < 3)
+		return 0;
+
+	ret = regmap_write(priv->regmap, EMC1812_EXT2_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	if (priv->chip->phys_channels < 4)
+		return 0;
+
+	ret = regmap_write(priv->regmap, EMC1812_EXT3_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	if (priv->chip->phys_channels < 5)
+		return 0;
+
+	return regmap_write(priv->regmap, EMC1812_EXT4_IDEALITY_FACTOR_ADDR,
+			    EMC1812_DEFAULT_IDEALITY_FACTOR);
+}
+
+static int emc1812_parse_fw_config(struct emc1812_data *data, struct device *dev)
+{
+	unsigned int reg_nr = 0;
+	int num_channels, ret;
+
+	/* To be able to load the driver in case we don't have device tree */
+	if (!dev_fwnode(dev)) {
+		data->active_ch_mask = BIT(data->chip->phys_channels) - 1;
+		return 0;
+	}
+
+	data->apdd_en = device_property_read_bool(dev, "microchip,enable-anti-parallel");
+	data->recd12_en = device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
+	data->recd34_en = device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
+
+	num_channels = device_get_child_node_count(dev) + 1;
+
+	if (num_channels > data->chip->phys_channels)
+		return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n");
+
+	/* Internal temperature channel is always active */
+	data->labels[reg_nr] = "internal_diode";
+	set_bit(reg_nr, &data->active_ch_mask);
+
+	device_for_each_child_node_scoped(dev, child) {
+		ret = fwnode_property_read_u32(child, "reg", &reg_nr);
+		if (ret || reg_nr >= data->chip->phys_channels)
+			return dev_err_probe(dev, -EINVAL,
+				     "The index of the channels does not match the chip\n");
+		/* Mark external channel as active */
+		set_bit(reg_nr, &data->active_ch_mask);
+
+		fwnode_property_read_string(child, "label", &data->labels[reg_nr]);
+	}
+
+	return 0;
+}
+
+static int emc1812_chip_identify(struct emc1812_data *data, struct i2c_client *client)
+{
+	const struct emc1812_features *chip;
+	struct device *dev = &client->dev;
+	int ret, tmp;
+
+	ret = regmap_read(data->regmap, EMC1812_PRODUCT_ID_ADDR, &tmp);
+	if (ret)
+		return ret;
+
+	switch (tmp) {
+	case EMC1812_PID:
+		data->chip = &emc1812_chip_config;
+		break;
+	case EMC1813_PID:
+		data->chip = &emc1813_chip_config;
+		break;
+	case EMC1814_PID:
+		data->chip = &emc1814_chip_config;
+		break;
+	case EMC1815_PID:
+		data->chip = &emc1815_chip_config;
+		break;
+	case EMC1833_PID:
+		data->chip = &emc1833_chip_config;
+		break;
+	default:
+		/*
+		 * If failed to identify the hardware based on internal registers,
+		 * try using fallback compatible in device tree to deal with some
+		 * newer part number.
+		 */
+		chip = i2c_get_match_data(client);
+		if (!chip)
+			return -ENODEV;
+
+		dev_warn(dev, "Unrecognized hardware ID 0x%x, using %s from devicetree data\n",
+			 tmp, chip->name);
+
+		data->chip = chip;
+
+		return 0;
+	}
+
+	return 0;
+}
+
+static const struct hwmon_ops emc1812_ops = {
+	.is_visible = emc1812_is_visible,
+	.read = emc1812_read,
+	.read_string = emc1812_read_string,
+	.write = emc1812_write,
+};
+
+static const struct hwmon_chip_info emc1812_chip_info = {
+	.ops = &emc1812_ops,
+	.info = emc1812_info,
+};
+
+static int emc1812_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct emc1812_data *data;
+	struct device *hwmon_dev;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->regmap = devm_regmap_init_i2c(client, &emc1812_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(dev, PTR_ERR(data->regmap),
+				     "Cannot initialize register map\n");
+
+	ret = emc1812_chip_identify(data, client);
+	if (ret)
+		return dev_err_probe(dev, ret, "Chip identification fails\n");
+
+	ret = emc1812_parse_fw_config(data, dev);
+	if (ret)
+		return ret;
+
+	ret = emc1812_init(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot initialize device\n");
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+							 &emc1812_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id emc1812_id[] = {
+	{ .name = "emc1812", .driver_data = (kernel_ulong_t)&emc1812_chip_config },
+	{ .name = "emc1813", .driver_data = (kernel_ulong_t)&emc1813_chip_config },
+	{ .name = "emc1814", .driver_data = (kernel_ulong_t)&emc1814_chip_config },
+	{ .name = "emc1815", .driver_data = (kernel_ulong_t)&emc1815_chip_config },
+	{ .name = "emc1833", .driver_data = (kernel_ulong_t)&emc1833_chip_config },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, emc1812_id);
+
+static const struct of_device_id emc1812_of_match[] = {
+	{
+		.compatible = "microchip,emc1812",
+		.data = &emc1812_chip_config
+	},
+	{
+		.compatible = "microchip,emc1813",
+		.data = &emc1813_chip_config
+	},
+	{
+		.compatible = "microchip,emc1814",
+		.data = &emc1814_chip_config
+	},
+	{
+		.compatible = "microchip,emc1815",
+		.data = &emc1815_chip_config
+	},
+	{
+		.compatible = "microchip,emc1833",
+		.data = &emc1833_chip_config
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, emc1812_of_match);
+
+static struct i2c_driver emc1812_driver = {
+	.driver	 = {
+		.name = "emc1812",
+		.of_match_table = emc1812_of_match,
+	},
+	.probe = emc1812_probe,
+	.id_table = emc1812_id,
+};
+module_i2c_driver(emc1812_driver);
+
+MODULE_AUTHOR("Marius Cristea <marius.cristea@microchip.com>");
+MODULE_DESCRIPTION("EMC1812/13/14/15/33 high-accuracy remote diode temperature monitor Driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


^ permalink raw reply related

* [PATCH v9 1/2] dt-bindings: hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea,
	Conor Dooley
In-Reply-To: <20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@microchip.com>

This is the devicetree schema for Microchip EMC1812/13/14/15/33
Multichannel Low-Voltage Remote Diode Sensor Family.

EMC1812 has one external remote temperature monitoring channel.
EMC1813 has two external remote temperature monitoring channels.
EMC1814 has three external remote temperature monitoring channels and
channels 2 and 3 support anti parallel diode.
EMC1815 has four external remote temperature monitoring channels and
channels 1/2  and 3/4 support anti parallel diode.
EMC1833 has two external remote temperature monitoring channels and
channels 1 and 2 support anti parallel diode.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
---
 .../bindings/hwmon/microchip,emc1812.yaml          | 184 +++++++++++++++++++++
 MAINTAINERS                                        |   6 +
 2 files changed, 190 insertions(+)

diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5f766a3b6a297cdc9bdfff2215fce6fa41434f0a
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
@@ -0,0 +1,184 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,emc1812.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip EMC1812/13/14/15/33 multichannel temperature sensor
+
+maintainers:
+  - Marius Cristea <marius.cristea@microchip.com>
+
+description: |
+  The Microchip EMC1812/13/14/15/33 is a high-accuracy 2-wire multichannel
+  low-voltage remote diode temperature monitor.
+
+  The datasheet can be found here:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+
+  EMC1812 has one external remote temperature monitoring channel
+  EMC1813 has two external remote temperature monitoring channels
+  EMC1814 has three external remote temperature monitoring channels and
+    channels 2 and 3 support anti parallel diode
+  EMC1815 has four external remote temperature monitoring channels and
+    channels 1/2 and 3/4 support anti parallel diode
+  EMC1833 has two external remote temperature monitoring channels and
+    channels 1 and 2 support anti parallel diode
+
+properties:
+  compatible:
+    enum:
+      - microchip,emc1812
+      - microchip,emc1813
+      - microchip,emc1814
+      - microchip,emc1815
+      - microchip,emc1833
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    items:
+      - description: alert-therm2 asserts when the ALERT limit is exceeded.
+      - description: therm-addr asserts when the THERM limit is exceeded.
+    minItems: 1
+
+  interrupt-names:
+    items:
+      enum: [alert-therm2, therm-addr]
+    minItems: 1
+    maxItems: 2
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  microchip,enable-anti-parallel:
+    description:
+      Enable anti-parallel diode mode operation. EMC1814, EMC1815 and EMC1833
+      support reading two external diodes in anti-parallel connection on the
+      same set of pins. Disabling APD functionality to implement substrate
+      diodes on devices that support APD eliminates the benefit of APD
+      (two diodes on one channel).
+    type: boolean
+
+  microchip,parasitic-res-on-channel1-2:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently
+      far apart that a parasitic resistance is added to the wires, which can
+      affect the measurements. Due to the anti-parallel diode connections,
+      channels 1 and 2 are affected together.
+    type: boolean
+
+  microchip,parasitic-res-on-channel3-4:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      3 and 4 are affected together.
+    type: boolean
+
+  vdd-supply: true
+
+patternProperties:
+  "^channel@[1-4]$":
+    description:
+      Represents the external temperature channels to which
+      a remote diode is connected.
+    type: object
+
+    properties:
+      reg:
+        maxItems: 1
+
+      label:
+        description: Unique name to identify which channel this is.
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+allOf:
+  # EMC1812: 1 Channel, No APD, REC on channel 1
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1812
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[2-4]$": false
+
+  # EMC1813: 2 Channels, No APD, REC on both channel 1 & 2
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1813
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  # EMC1833: 2 Channels, Supports APD, REC on both channel 1 & 2
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1833
+    then:
+      properties:
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  # EMC1814: 3 Channels, Supports APD,
+  # REC on both channel 1 & 2 and channel 3
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1814
+    then:
+      properties:
+        channel@4: false
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        temperature-sensor@4c {
+            compatible = "microchip,emc1813";
+            reg = <0x4c>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            microchip,parasitic-res-on-channel1-2;
+
+            vdd-supply = <&vdd>;
+
+            channel@1 {
+                reg = <1>;
+                label = "External CH1 Temperature";
+            };
+
+            channel@2 {
+                reg = <2>;
+                label = "External CH2 Temperature";
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d7b697bfdba16e4f0ee5f4f0195b9d7da06dae5..85c236df781e47c78deeb7ef4d80bc94bba604c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16646,6 +16646,12 @@ S:	Supported
 F:	Documentation/devicetree/bindings/interrupt-controller/microchip,sama7g5-eic.yaml
 F:	drivers/irqchip/irq-mchp-eic.c
 
+MICROCHIP EMC1812 DRIVER
+M:	Marius Cristea <marius.cristea@microchip.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
+
 MICROCHIP I2C DRIVER
 M:	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
 L:	linux-i2c@vger.kernel.org

-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH v3] doc: Add CPU Isolation documentation
From: Frederic Weisbecker @ 2026-04-03 12:06 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: LKML, Anna-Maria Behnsen, Gabriele Monaco, Ingo Molnar,
	Jonathan Corbet, Marcelo Tosatti, Marco Crivellari, Michal Hocko,
	Paul E . McKenney, Peter Zijlstra, Phil Auld, Steven Rostedt,
	Thomas Gleixner, Valentin Schneider, Vlastimil Babka, Waiman Long,
	linux-doc, Bagas Sanjaya
In-Reply-To: <20260402110122.2gkDqQ7Q@linutronix.de>

Le Thu, Apr 02, 2026 at 01:01:22PM +0200, Sebastian Andrzej Siewior a écrit :
> On 2026-04-02 11:47:49 [+0200], Frederic Weisbecker wrote:
> > nohz_full was introduced in v3.10 in 2013, which means this
> > documentation is overdue for 13 years.
> > 
> > Fortunately Paul wrote a part of the needed documentation a while ago,
> > especially concerning nohz_full in Documentation/timers/no_hz.rst and
> > also about per-CPU kthreads in
> > Documentation/admin-guide/kernel-per-CPU-kthreads.rst
> > 
> > Introduce a new page that gives an overview of CPU isolation in general.
> > 
> > Acked-by: Waiman Long <longman@redhat.com>
> > Reviewed-by: Valentin Schneider <vschneid@redhat.com>
> > Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
> 
> This documents also isolcpus= boot argument. The only thing that this
> argument does and runtime can not do is the managed_irq sub argument.
> This sub argument is a story of its own and it is of quite limited for
> me taste.
> 
> However, isolcpus= is marked as deprecated. I suggest to remove the
> "Deprecated - use cpusets instead" note as the static configuration is
> fine if the system is partitioned once never changed within its
> lifetime.
> Are there any objections and if so why needs this boot argument be
> removed (assuming we have a runtime equivalent knob for managed_irq)?

No objections! Thanks.

> 
> Sebastian

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply

* Re: [PATCH v2 0/3] Documentation: clarify required info in security reports
From: Willy Tarreau @ 2026-04-03 11:51 UTC (permalink / raw)
  To: Greg KH
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel
In-Reply-To: <2026040324-coping-vacation-7d64@gregkh>

On Fri, Apr 03, 2026 at 01:11:47PM +0200, Greg KH wrote:
> On Fri, Apr 03, 2026 at 08:20:15AM +0200, Willy Tarreau wrote:
> > Hi Greg,
> > 
> > I'm sending you the doc clarifications we discussed for the process of
> > reporting security issues. It's cut into the 3 patches I shared this
> > morning on the security list (plus two typos fixed and a paragraph
> > asking for one single issue per report):
> > 
> >   - one patch that reminds our need for a valid e-mail address
> >   - one that explains to reporters how to proceed to find maintainers
> >     addresses, hoping we won't have to do it for 90% of reports anymore
> >   - one that enumerates basic requirements for every report
> > 
> > I think it covers the difficulties we've faced this week. As always,
> > we might possibly find tiny adjustments to add, but my goal would be
> > for such updates to be merged in time to update the public page ASAP
> > so that we can redirect incomplete reports in an attempt to lower the
> > team's current load.
> 
> Looks great, thanks.  I've applied these to one of my trees and will get
> them to Linus in time for 7.0-final.

Thank you!
Willy

^ permalink raw reply

* Re: [PATCH v2 0/3] Documentation: clarify required info in security reports
From: Greg KH @ 2026-04-03 11:11 UTC (permalink / raw)
  To: Willy Tarreau
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel
In-Reply-To: <20260403062018.31080-1-w@1wt.eu>

On Fri, Apr 03, 2026 at 08:20:15AM +0200, Willy Tarreau wrote:
> Hi Greg,
> 
> I'm sending you the doc clarifications we discussed for the process of
> reporting security issues. It's cut into the 3 patches I shared this
> morning on the security list (plus two typos fixed and a paragraph
> asking for one single issue per report):
> 
>   - one patch that reminds our need for a valid e-mail address
>   - one that explains to reporters how to proceed to find maintainers
>     addresses, hoping we won't have to do it for 90% of reports anymore
>   - one that enumerates basic requirements for every report
> 
> I think it covers the difficulties we've faced this week. As always,
> we might possibly find tiny adjustments to add, but my goal would be
> for such updates to be merged in time to update the public page ASAP
> so that we can redirect incomplete reports in an attempt to lower the
> team's current load.

Looks great, thanks.  I've applied these to one of my trees and will get
them to Linus in time for 7.0-final.

greg k-h

^ permalink raw reply

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

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

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

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

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

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

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

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

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

Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.

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

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

-- 
2.43.0



^ permalink raw reply related

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

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

Add buffered capture support using the IIO triggered buffer framework.

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

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

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

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

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

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 3685a03aa8dc..d498f16c0816 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -142,6 +142,8 @@ config AD4170_4
 config AD4691
 	tristate "Analog Devices AD4691 Family ADC Driver"
 	depends on SPI
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	select REGMAP
 	help
 	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index 43bd408c3d11..f2a7273e43b9 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -5,15 +5,19 @@
  */
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
+#include <linux/bitmap.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/interrupt.h>
 #include <linux/math.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
@@ -21,7 +25,12 @@
 #include <linux/units.h>
 #include <linux/unaligned.h>
 
+#include <linux/iio/buffer.h>
 #include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
 
 #define AD4691_VREF_uV_MIN			2400000
 #define AD4691_VREF_uV_MAX			5250000
@@ -30,6 +39,8 @@
 #define AD4691_VREF_3P3_uV_MAX			3750000
 #define AD4691_VREF_4P096_uV_MAX		4500000
 
+#define AD4691_CNV_DUTY_CYCLE_NS		380
+
 #define AD4691_SPI_CONFIG_A_REG			0x000
 #define AD4691_SW_RESET				(BIT(7) | BIT(0))
 
@@ -37,6 +48,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)
@@ -44,13 +56,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
@@ -60,6 +77,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
@@ -95,9 +114,11 @@ struct ad4691_chip_info {
 		.type = IIO_VOLTAGE,					\
 		.indexed = 1,						\
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)		\
-				    | BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+				    | BIT(IIO_CHAN_INFO_SAMP_FREQ)	\
+				    | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),	\
 		.info_mask_separate_available =				\
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ)	\
+				    | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),	\
 		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
 		.channel = ch,						\
 		.scan_index = ch,					\
@@ -125,6 +146,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[] = {
@@ -136,6 +158,7 @@ static const struct iio_chan_spec ad4693_channels[] = {
 	AD4691_CHANNEL(5),
 	AD4691_CHANNEL(6),
 	AD4691_CHANNEL(7),
+	IIO_CHAN_SOFT_TIMESTAMP(8),
 };
 
 /*
@@ -162,6 +185,14 @@ static const int ad4691_osc_freqs_Hz[] = {
 	[0xF] = 1250,
 };
 
+static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
+
+/*
+ * Valid ACC_DEPTH values where the effective divisor equals the count.
+ * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
+ */
+static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
+
 static const struct ad4691_chip_info ad4691_chip_info = {
 	.channels = ad4691_channels,
 	.name = "ad4691",
@@ -193,16 +224,55 @@ static const struct ad4691_chip_info ad4694_chip_info = {
 struct ad4691_state {
 	const struct ad4691_chip_info *info;
 	struct regmap *regmap;
+
+	struct pwm_device *conv_trigger;
+	int irq;
+
+	bool manual_mode;
+
 	int vref_uV;
+	u8 osr[16];
 	bool refbuf_en;
 	bool ldo_en;
+	u32 cnv_period_ns;
 	/*
 	 * Synchronize access to members of the driver state, and ensure
 	 * atomicity of consecutive SPI operations.
 	 */
 	struct mutex lock;
+	/*
+	 * Per-buffer-enable lifetime resources:
+	 * Manual Mode - a pre-built SPI message that clocks out N+1
+	 *		 transfers in one go.
+	 * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
+	 *		    transfers in one go.
+	 */
+	struct spi_message scan_msg;
+	struct spi_transfer *scan_xfers;
+	__be16 *scan_tx;
+	__be16 *scan_rx;
+	/* Scan buffer: one slot per channel plus timestamp */
+	struct {
+		u16 vals[16];
+		aligned_s64 ts;
+	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+/*
+ * Configure the given GP pin (0-3) as DATA_READY output.
+ * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
+ * Even pins occupy bits [3:0], odd pins bits [7:4].
+ */
+static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
+{
+	unsigned int shift = 4 * (gp_num % 2);
+
+	return regmap_update_bits(st->regmap,
+				  AD4691_GPIO_MODE1_REG + gp_num / 2,
+				  AD4691_GP_MODE_MASK << shift,
+				  AD4691_GP_MODE_DATA_READY << shift);
+}
+
 static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
 {
 	struct spi_device *spi = context;
@@ -362,6 +432,24 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
 	return -EINVAL;
 }
 
+static int ad4691_set_oversampling_ratio(struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan,
+					 int osr)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	if (osr < 1 || osr > 32 || !is_power_of_2(osr))
+		return -EINVAL;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	st->osr[chan->scan_index] = osr;
+	return regmap_write(st->regmap,
+			    AD4691_ACC_DEPTH_IN(chan->scan_index), osr);
+}
+
 static int ad4691_read_avail(struct iio_dev *indio_dev,
 			     struct iio_chan_spec const *chan,
 			     const int **vals, int *type,
@@ -376,6 +464,11 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
 		*type = IIO_VAL_INT;
 		*length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
 		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		*vals = ad4691_oversampling_ratios;
+		*type = IIO_VAL_INT;
+		*length = ARRAY_SIZE(ad4691_oversampling_ratios);
+		return IIO_AVAIL_LIST;
 	default:
 		return -EINVAL;
 	}
@@ -406,6 +499,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
 	if (ret)
 		return ret;
 
+	ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->scan_index),
+			   st->osr[chan->scan_index]);
+	if (ret)
+		return ret;
+
 	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
 	if (ret)
 		return ret;
@@ -452,6 +550,9 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
 	}
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		return ad4691_get_sampling_freq(st, val);
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		*val = st->osr[chan->scan_index];
+		return IIO_VAL_INT;
 	case IIO_CHAN_INFO_SCALE:
 		*val = st->vref_uV / (MICRO / MILLI);
 		*val2 = chan->scan_type.realbits;
@@ -468,6 +569,8 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
 	switch (mask) {
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		return ad4691_set_sampling_freq(indio_dev, val);
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		return ad4691_set_oversampling_ratio(indio_dev, chan, val);
 	default:
 		return -EINVAL;
 	}
@@ -486,6 +589,385 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
 	return regmap_write(st->regmap, reg, writeval);
 }
 
+static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq)
+{
+	if (!freq)
+		return -EINVAL;
+
+	st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
+	return 0;
+}
+
+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
+{
+	struct pwm_state conv_state = {
+		.period     = st->cnv_period_ns,
+		.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
+		.polarity   = PWM_POLARITY_NORMAL,
+		.enabled    = enable,
+	};
+
+	return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
+}
+
+/*
+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
+ *
+ * Configures the ADC hardware registers for the mode selected at probe
+ * (CNV_BURST or MANUAL). Called from buffer preenable before starting
+ * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
+ */
+static int ad4691_enter_conversion_mode(struct ad4691_state *st)
+{
+	int ret;
+
+	if (st->manual_mode)
+		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+					  AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
+
+	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				 AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
+	if (ret)
+		return ret;
+
+	return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+			    AD4691_STATE_RESET_ALL);
+}
+
+/*
+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
+ *
+ * Called from buffer postdisable to restore the chip to the
+ * idle state used by read_raw. Clears the sequencer and resets state.
+ */
+static int ad4691_exit_conversion_mode(struct ad4691_state *st)
+{
+	if (st->manual_mode)
+		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+					  AD4691_MANUAL_MODE, 0);
+
+	return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				  AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+}
+
+static void ad4691_free_scan_bufs(struct ad4691_state *st)
+{
+	kfree(st->scan_xfers);
+	kfree(st->scan_tx);
+	kfree(st->scan_rx);
+}
+
+static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+					      iio_get_masklength(indio_dev));
+	unsigned int n_xfers = n_active + 1;
+	unsigned int k, i;
+	int ret;
+
+	st->scan_xfers = kcalloc(n_xfers, sizeof(*st->scan_xfers), GFP_KERNEL);
+	if (!st->scan_xfers)
+		return -ENOMEM;
+
+	st->scan_tx = kcalloc(n_xfers, sizeof(*st->scan_tx), GFP_KERNEL);
+	if (!st->scan_tx) {
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	st->scan_rx = kcalloc(n_xfers, sizeof(*st->scan_rx), GFP_KERNEL);
+	if (!st->scan_rx) {
+		kfree(st->scan_tx);
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&st->scan_msg);
+
+	k = 0;
+	iio_for_each_active_channel(indio_dev, i) {
+		st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
+		st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+		st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+		st->scan_xfers[k].len = sizeof(__be16);
+		st->scan_xfers[k].cs_change = 1;
+		spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+		k++;
+	}
+
+	/* Final NOOP transfer to retrieve last channel's result. */
+	st->scan_tx[k] = cpu_to_be16(AD4691_NOOP);
+	st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+	st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+	st->scan_xfers[k].len = sizeof(__be16);
+	spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+
+	st->scan_msg.spi = spi;
+
+	ret = spi_optimize_message(spi, &st->scan_msg);
+	if (ret) {
+		ad4691_free_scan_bufs(st);
+		return ret;
+	}
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret) {
+		spi_unoptimize_message(&st->scan_msg);
+		ad4691_free_scan_bufs(st);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ad4691_exit_conversion_mode(st);
+	spi_unoptimize_message(&st->scan_msg);
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
+	.preenable = &ad4691_manual_buffer_preenable,
+	.postdisable = &ad4691_manual_buffer_postdisable,
+};
+
+static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->regmap);
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+					      iio_get_masklength(indio_dev));
+	unsigned int bit, k, i;
+	int ret;
+
+	st->scan_xfers = kcalloc(2 * n_active, sizeof(*st->scan_xfers), GFP_KERNEL);
+	if (!st->scan_xfers)
+		return -ENOMEM;
+
+	st->scan_tx = kcalloc(n_active, sizeof(*st->scan_tx), GFP_KERNEL);
+	if (!st->scan_tx) {
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	st->scan_rx = kcalloc(n_active, sizeof(*st->scan_rx), GFP_KERNEL);
+	if (!st->scan_rx) {
+		kfree(st->scan_tx);
+		kfree(st->scan_xfers);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&st->scan_msg);
+
+	/*
+	 * Each AVG_IN read needs two transfers: a 2-byte address write phase
+	 * followed by a 2-byte data read phase. CS toggles between channels
+	 * (cs_change=1 on the read phase of all but the last channel).
+	 */
+	k = 0;
+	iio_for_each_active_channel(indio_dev, i) {
+		st->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));
+		st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
+		st->scan_xfers[2 * k].len = sizeof(__be16);
+		spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
+		st->scan_xfers[2 * k + 1].rx_buf = &st->scan_rx[k];
+		st->scan_xfers[2 * k + 1].len = sizeof(__be16);
+		if (k < n_active - 1)
+			st->scan_xfers[2 * k + 1].cs_change = 1;
+		spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
+		k++;
+	}
+
+	st->scan_msg.spi = spi;
+
+	ret = spi_optimize_message(spi, &st->scan_msg);
+	if (ret)
+		goto err_free_bufs;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   bitmap_read(indio_dev->active_scan_mask, 0,
+				       iio_get_masklength(indio_dev)));
+	if (ret)
+		goto err;
+
+	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+			   ~bitmap_read(indio_dev->active_scan_mask, 0,
+				iio_get_masklength(indio_dev)) & GENMASK(15, 0));
+	if (ret)
+		goto err;
+
+	iio_for_each_active_channel(indio_dev, bit) {
+		ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
+				   st->osr[bit]);
+		if (ret)
+			goto err;
+	}
+
+	ret = ad4691_enter_conversion_mode(st);
+	if (ret)
+		goto err;
+
+	ret = ad4691_sampling_enable(st, true);
+	if (ret)
+		goto err;
+
+	enable_irq(st->irq);
+	return 0;
+err:
+	spi_unoptimize_message(&st->scan_msg);
+err_free_bufs:
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int ret;
+
+	disable_irq(st->irq);
+
+	ret = ad4691_sampling_enable(st, false);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+			   AD4691_SEQ_ALL_CHANNELS_OFF);
+	if (ret)
+		return ret;
+
+	ret = ad4691_exit_conversion_mode(st);
+	spi_unoptimize_message(&st->scan_msg);
+	ad4691_free_scan_bufs(st);
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
+	.preenable = &ad4691_cnv_burst_buffer_preenable,
+	.postdisable = &ad4691_cnv_burst_buffer_postdisable,
+};
+
+static ssize_t sampling_frequency_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ad4691_state *st = iio_priv(indio_dev);
+	int freq, ret;
+
+	ret = kstrtoint(buf, 10, &freq);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&st->lock);
+
+	if (iio_buffer_enabled(indio_dev))
+		return -EBUSY;
+
+	ret = ad4691_set_pwm_freq(st, freq);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static IIO_DEVICE_ATTR(sampling_frequency, 0644,
+		       sampling_frequency_show,
+		       sampling_frequency_store, 0);
+
+static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
+	&iio_dev_attr_sampling_frequency,
+	NULL
+};
+
+static irqreturn_t ad4691_irq(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	/*
+	 * GPx has asserted: stop conversions before reading so the
+	 * accumulator does not continue sampling while the trigger handler
+	 * processes the data. Then fire the IIO trigger to push the sample
+	 * to the buffer.
+	 */
+	ad4691_sampling_enable(st, false);
+	iio_trigger_poll(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops ad4691_trigger_ops = {
+	.validate_device = iio_trigger_validate_own_device,
+};
+
+static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int i, k = 0;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = spi_sync(st->scan_msg.spi, &st->scan_msg);
+	if (ret)
+		return ret;
+
+	if (st->manual_mode) {
+		iio_for_each_active_channel(indio_dev, i) {
+			st->scan.vals[i] = be16_to_cpu(st->scan_rx[k + 1]);
+			k++;
+		}
+	} else {
+		iio_for_each_active_channel(indio_dev, i) {
+			st->scan.vals[i] = be16_to_cpu(st->scan_rx[k]);
+			k++;
+		}
+
+		ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+				   AD4691_STATE_RESET_ALL);
+		if (ret)
+			return ret;
+
+		ret = ad4691_sampling_enable(st, true);
+		if (ret)
+			return ret;
+	}
+
+	iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+				    timestamp);
+	return 0;
+}
+
+static irqreturn_t ad4691_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+
+	ad4691_read_scan(indio_dev, pf->timestamp);
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
 static const struct iio_info ad4691_info = {
 	.read_raw = &ad4691_read_raw,
 	.write_raw = &ad4691_write_raw,
@@ -493,6 +975,18 @@ static const struct iio_info ad4691_info = {
 	.debugfs_reg_access = &ad4691_reg_access,
 };
 
+static int ad4691_pwm_setup(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+
+	st->conv_trigger = devm_pwm_get(dev, "cnv");
+	if (IS_ERR(st->conv_trigger))
+		return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
+				     "Failed to get cnv pwm\n");
+
+	return ad4691_set_pwm_freq(st, st->info->max_rate);
+}
+
 static int ad4691_regulator_setup(struct ad4691_state *st)
 {
 	struct device *dev = regmap_get_device(st->regmap);
@@ -558,6 +1052,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;
@@ -613,6 +1123,78 @@ 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;
+
+	trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+				      indio_dev->name,
+				      iio_device_id(indio_dev));
+	if (!trig)
+		return -ENOMEM;
+
+	trig->ops = &ad4691_trigger_ops;
+	iio_trigger_set_drvdata(trig, st);
+
+	ret = devm_iio_trigger_register(dev, trig);
+	if (ret)
+		return dev_err_probe(dev, ret, "IIO trigger register failed\n");
+
+	indio_dev->trig = iio_trigger_get(trig);
+
+	if (!st->manual_mode) {
+		/*
+		 * The GP pin named in interrupt-names asserts at end-of-conversion.
+		 * The IRQ handler stops conversions and fires the IIO trigger so
+		 * the trigger handler can read and push the sample to the buffer.
+		 * The IRQ is kept disabled until the buffer is enabled.
+		 */
+		irq = -ENODEV;
+		for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
+			irq = fwnode_irq_get_byname(dev_fwnode(dev),
+						    ad4691_gp_names[i]);
+			if (irq > 0)
+				break;
+		}
+		if (irq <= 0)
+			return dev_err_probe(dev, irq < 0 ? irq : -ENODEV,
+					     "failed to get GP interrupt\n");
+
+		st->irq = irq;
+
+		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);
+	}
+
+	return devm_iio_triggered_buffer_setup(dev, indio_dev,
+					       &iio_pollfunc_store_time,
+					       &ad4691_trigger_handler,
+					       &ad4691_manual_buffer_setup_ops);
+}
+
 static int ad4691_probe(struct spi_device *spi)
 {
 	struct device *dev = &spi->dev;
@@ -627,6 +1209,8 @@ static int ad4691_probe(struct spi_device *spi)
 	st = iio_priv(indio_dev);
 	st->info = spi_get_device_match_data(spi);
 
+	memset(st->osr, 1, sizeof(st->osr));
+
 	ret = devm_mutex_init(dev, &st->lock);
 	if (ret)
 		return ret;
@@ -655,6 +1239,10 @@ static int ad4691_probe(struct spi_device *spi)
 	indio_dev->channels = st->info->channels;
 	indio_dev->num_channels = st->info->num_channels;
 
+	ret = ad4691_setup_triggered_buffer(indio_dev, st);
+	if (ret)
+		return ret;
+
 	return devm_iio_device_register(dev, indio_dev);
 }
 

-- 
2.43.0



^ permalink raw reply related

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

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

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

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

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

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

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

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
Changes in v6:
- Replace device.h with dev_printk.h + device/devres.h; add array_size.h
- Rename osc_freqs[] → osc_freqs_Hz[] with explicit [0xN] index designators
- Move loop variable into for() declaration in set_sampling_freq
- Convert multi-line block comment to single-line in single_shot_read
- Replace (u16)~ cast with ~BIT() & GENMASK(15, 0) for ACC_MASK_REG write;
  GENMASK(15, 0) is still needed, otherwise maximum value condition line
  in reg_write() would fail.
- Extract osc_idx/period_us temporaries in single_shot_read; add comment
- Use devm_regulator_bulk_get_enable() for avdd + vio supplies
- Reformat reset_gpio_probe() comment; remove (GPIOD_OUT_HIGH) detail
- Extract REF_CTRL value into temporary before regmap_update_bits
- Use regmap_assign_bits for OSC_FREQ_REG in config
- Remove ad4691_free_scan_bufs NULL assignments; they are not checked.
- Replace indio_dev->masklength with iio_get_masklength() throughout
- Fix spi_optimize_message error path to use goto err in preenable
- Add iio_buffer_enabled() guard in sampling_frequency_store and
  set_oversampling_ratio
- Move ad4691_gpio_setup call from ad4691_config into
  setup_triggered_buffer after IRQ lookup; remove duplicate
  fwnode_irq_get_byname loop
- Replace oversampling ratio search loop with is_power_of_2 + ilog2
- Link to v5: https://lore.kernel.org/r/20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com

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

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

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

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

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

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

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



^ permalink raw reply

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

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

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

The binding describes the hardware connections:

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

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

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

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

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

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

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

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

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

-- 
2.43.0



^ permalink raw reply related

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

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

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

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

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

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

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

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

diff --git a/MAINTAINERS b/MAINTAINERS
index 438ca850fa1c..24e4502b8292 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L:	linux-iio@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F:	drivers/iio/adc/ad4691.c
 
 ANALOG DEVICES INC AD4695 DRIVER
 M:	Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 60038ae8dfc4..3685a03aa8dc 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -139,6 +139,17 @@ config AD4170_4
 	  To compile this driver as a module, choose M here: the module will be
 	  called ad4170-4.
 
+config AD4691
+	tristate "Analog Devices AD4691 Family ADC Driver"
+	depends on SPI
+	select REGMAP
+	help
+	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
+	  SPI analog to digital converters (ADC).
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called ad4691.
+
 config AD4695
 	tristate "Analog Device AD4695 ADC Driver"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index c76550415ff1..4ac1ea09d773 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
 obj-$(CONFIG_AD4130) += ad4130.o
 obj-$(CONFIG_AD4134) += ad4134.o
 obj-$(CONFIG_AD4170_4) += ad4170-4.o
+obj-$(CONFIG_AD4691) += ad4691.o
 obj-$(CONFIG_AD4695) += ad4695.o
 obj-$(CONFIG_AD4851) += ad4851.o
 obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
new file mode 100644
index 000000000000..43bd408c3d11
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,691 @@
+// 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/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,
+};
+
+struct ad4691_chip_info {
+	const struct iio_chan_spec *channels;
+	const char *name;
+	unsigned int num_channels;
+	unsigned int max_rate;
+};
+
+#define AD4691_CHANNEL(ch)						\
+	{								\
+		.type = IIO_VOLTAGE,					\
+		.indexed = 1,						\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)		\
+				    | BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+		.info_mask_separate_available =				\
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
+		.channel = ch,						\
+		.scan_index = ch,					\
+		.scan_type = {						\
+			.sign = 'u',					\
+			.realbits = 16,					\
+			.storagebits = 16,				\
+		},							\
+	}
+
+static const struct iio_chan_spec ad4691_channels[] = {
+	AD4691_CHANNEL(0),
+	AD4691_CHANNEL(1),
+	AD4691_CHANNEL(2),
+	AD4691_CHANNEL(3),
+	AD4691_CHANNEL(4),
+	AD4691_CHANNEL(5),
+	AD4691_CHANNEL(6),
+	AD4691_CHANNEL(7),
+	AD4691_CHANNEL(8),
+	AD4691_CHANNEL(9),
+	AD4691_CHANNEL(10),
+	AD4691_CHANNEL(11),
+	AD4691_CHANNEL(12),
+	AD4691_CHANNEL(13),
+	AD4691_CHANNEL(14),
+	AD4691_CHANNEL(15),
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+	AD4691_CHANNEL(0),
+	AD4691_CHANNEL(1),
+	AD4691_CHANNEL(2),
+	AD4691_CHANNEL(3),
+	AD4691_CHANNEL(4),
+	AD4691_CHANNEL(5),
+	AD4691_CHANNEL(6),
+	AD4691_CHANNEL(7),
+};
+
+/*
+ * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
+ * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
+ * up to 500 kHz and use index 1 as their highest valid rate.
+ */
+static const int ad4691_osc_freqs_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_chip_info ad4691_chip_info = {
+	.channels = ad4691_channels,
+	.name = "ad4691",
+	.num_channels = ARRAY_SIZE(ad4691_channels),
+	.max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4692_chip_info = {
+	.channels = ad4691_channels,
+	.name = "ad4692",
+	.num_channels = ARRAY_SIZE(ad4691_channels),
+	.max_rate = 1 * HZ_PER_MHZ,
+};
+
+static const struct ad4691_chip_info ad4693_chip_info = {
+	.channels = ad4693_channels,
+	.name = "ad4693",
+	.num_channels = ARRAY_SIZE(ad4693_channels),
+	.max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4694_chip_info = {
+	.channels = ad4693_channels,
+	.name = "ad4694",
+	.num_channels = ARRAY_SIZE(ad4693_channels),
+	.max_rate = 1 * HZ_PER_MHZ,
+};
+
+struct ad4691_state {
+	const struct ad4691_chip_info *info;
+	struct regmap *regmap;
+	int vref_uV;
+	bool refbuf_en;
+	bool ldo_en;
+	/*
+	 * Synchronize access to members of the driver state, and ensure
+	 * atomicity of consecutive SPI operations.
+	 */
+	struct mutex lock;
+};
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct spi_device *spi = context;
+	u8 tx[2], rx[4];
+	int ret;
+
+	/* Set bit 15 to mark the operation as READ. */
+	put_unaligned_be16(0x8000 | reg, tx);
+
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 1);
+		if (ret)
+			return ret;
+		*val = rx[0];
+		return 0;
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 2);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be16(rx);
+		return 0;
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 3);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be24(rx);
+		return 0;
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		ret = spi_write_then_read(spi, tx, 2, rx, 4);
+		if (ret)
+			return ret;
+		*val = get_unaligned_be32(rx);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct spi_device *spi = context;
+	u8 tx[4];
+
+	put_unaligned_be16(reg, tx);
+
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
+	case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
+		if (val > 0xFF)
+			return -EINVAL;
+		tx[2] = val;
+		return spi_write_then_read(spi, tx, 3, NULL, 0);
+	case AD4691_ACC_MASK_REG:
+	case AD4691_STD_SEQ_CONFIG:
+		if (val > 0xFFFF)
+			return -EINVAL;
+		put_unaligned_be16(val, &tx[2]);
+		return spi_write_then_read(spi, tx, 4, NULL, 0);
+	default:
+		return -EINVAL;
+	}
+}
+
+static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case AD4691_STATUS_REG:
+	case AD4691_CLAMP_STATUS1_REG:
+	case AD4691_CLAMP_STATUS2_REG:
+	case AD4691_GPIO_READ:
+	case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
+	case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0 ... AD4691_OSC_FREQ_REG:
+	case AD4691_STD_SEQ_CONFIG:
+	case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config ad4691_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 32,
+	.reg_read = ad4691_reg_read,
+	.reg_write = ad4691_reg_write,
+	.volatile_reg = ad4691_volatile_reg,
+	.readable_reg = ad4691_readable_reg,
+	.writeable_reg = ad4691_writeable_reg,
+	.max_register = AD4691_ACC_STS_DATA(15),
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
+{
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
+	if (ret)
+		return ret;
+
+	*val = ad4691_osc_freqs_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 = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+
+	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);
+	}
+
+	return -EINVAL;
+}
+
+static int ad4691_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type,
+			     int *length, long mask)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*vals = &ad4691_osc_freqs_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, &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), &reg_val);
+	if (ret)
+		return ret;
+
+	*val = reg_val;
+
+	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+static int ad4691_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan, int *val,
+			   int *val2, long info)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW: {
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		return ad4691_single_shot_read(indio_dev, chan, val);
+	}
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ad4691_get_sampling_freq(st, val);
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->vref_uV / (MICRO / MILLI);
+		*val2 = chan->scan_type.realbits;
+		return IIO_VAL_FRACTIONAL_LOG2;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ad4691_set_sampling_freq(indio_dev, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+			     unsigned int writeval, unsigned int *readval)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+
+	guard(mutex)(&st->lock);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad4691_info = {
+	.read_raw = &ad4691_read_raw,
+	.write_raw = &ad4691_write_raw,
+	.read_avail = &ad4691_read_avail,
+	.debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_regulator_setup(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	int ret;
+
+	ret = devm_regulator_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");
+
+	ret = devm_regulator_get_enable(dev, "ldo-in");
+	if (ret == -ENODEV)
+		st->ldo_en = true;
+	else if (ret)
+		return dev_err_probe(dev, ret, "Failed to get and enable LDO-IN\n");
+
+	st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+	if (st->vref_uV == -ENODEV) {
+		st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
+		st->refbuf_en = true;
+	}
+	if (st->vref_uV < 0)
+		return dev_err_probe(dev, st->vref_uV,
+				     "Failed to get reference supply\n");
+
+	if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
+		return dev_err_probe(dev, -EINVAL,
+				     "vref(%d) must be in the range [%u...%u]\n",
+				     st->vref_uV, AD4691_VREF_uV_MIN,
+				     AD4691_VREF_uV_MAX);
+
+	return 0;
+}
+
+static int ad4691_reset(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	struct reset_control *rst;
+
+	rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+	if (IS_ERR(rst))
+		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
+
+	if (rst) {
+		/*
+		 * The GPIO is already asserted by reset_gpio_probe().
+		 * Wait for the reset pulse width required by the chip.
+		 * See datasheet Table 5.
+		 */
+		fsleep(300);
+		return reset_control_deassert(rst);
+	}
+
+	/* No hardware reset available, fall back to software reset. */
+	return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
+			    AD4691_SW_RESET);
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+	struct device *dev = regmap_get_device(st->regmap);
+	enum ad4691_ref_ctrl ref_val;
+	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_update_bits(st->regmap, AD4691_REF_CTRL,
+				 AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN, val);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+	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_assign_bits(st->regmap, AD4691_OSC_FREQ_REG,
+				 AD4691_OSC_FREQ_MASK,
+				 (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
+
+	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+				 AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+	return 0;
+}
+
+static int ad4691_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct ad4691_state *st;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->info = spi_get_device_match_data(spi);
+
+	ret = devm_mutex_init(dev, &st->lock);
+	if (ret)
+		return ret;
+
+	st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
+	if (IS_ERR(st->regmap))
+		return dev_err_probe(dev, PTR_ERR(st->regmap),
+				     "Failed to initialize regmap\n");
+
+	ret = ad4691_regulator_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ad4691_reset(st);
+	if (ret)
+		return ret;
+
+	ret = ad4691_config(st);
+	if (ret)
+		return ret;
+
+	indio_dev->name = st->info->name;
+	indio_dev->info = &ad4691_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	indio_dev->channels = st->info->channels;
+	indio_dev->num_channels = st->info->num_channels;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+	{ .compatible = "adi,ad4691", .data = &ad4691_chip_info },
+	{ .compatible = "adi,ad4692", .data = &ad4692_chip_info },
+	{ .compatible = "adi,ad4693", .data = &ad4693_chip_info },
+	{ .compatible = "adi,ad4694", .data = &ad4694_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+	{ "ad4691", (kernel_ulong_t)&ad4691_chip_info },
+	{ "ad4692", (kernel_ulong_t)&ad4692_chip_info },
+	{ "ad4693", (kernel_ulong_t)&ad4693_chip_info },
+	{ "ad4694", (kernel_ulong_t)&ad4694_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ad4691_id);
+
+static struct spi_driver ad4691_driver = {
+	.driver = {
+		.name = "ad4691",
+		.of_match_table = ad4691_of_match,
+	},
+	.probe = ad4691_probe,
+	.id_table = ad4691_id,
+};
+module_spi_driver(ad4691_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0



^ permalink raw reply related

* Re: [PATCH v12 00/15] arm64/riscv: Add support for crashkernel CMA reservation
From: Jinjie Ruan @ 2026-04-03  9:14 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: corbet, skhan, catalin.marinas, will, chenhuacai, kernel, maddy,
	mpe, npiggin, chleroy, pjw, palmer, aou, alex, tglx, mingo,
	dave.hansen, hpa, robh, saravanak, akpm, bhe, vgoyal, dyoung,
	rdunlap, peterz, pawan.kumar.gupta, feng.tang, dapeng1.mi, kees,
	elver, paulmck, lirongqing, rppt, leitao, ardb, jbohac, cfsworks,
	tangyouling, sourabhjain, ritesh.list, hbathini, eajames, guoren,
	songshuaishuai, kevin.brodsky, vishal.moola, junhui.liu, coxu,
	fuqiang.wang, liaoyuanhong, takahiro.akashi, james.morse,
	lizhengyu3, x86, linux-doc, linux-kernel, linux-arm-kernel,
	loongarch, linuxppc-dev, linux-riscv, devicetree, kexec
In-Reply-To: <20260402133644.GBac5w7OYl9MwvVxY_@fat_crate.local>



On 2026/4/2 21:36, Borislav Petkov wrote:
> On Thu, Apr 02, 2026 at 07:47:53PM +0800, Jinjie Ruan wrote:
>> Thank you for the reminder and for your patience. I apologize for the
>> frequent updates; I am becoming more familiar with the community's
>> workflow.
> 
> Yap, and you can use that time while waiting to learn about it:

Thanks for the pointer! I'm using the wait time to dive into the process
docs. Definitely a lot to learn about the workflow, but I'm getting there.

> 
> Documentation/process/
> 

^ permalink raw reply

* [cornelisnetworks:for-upstream_20260402-1611 31/31] Warning: drivers/infiniband/hw/hfi2/chip_gen.c:577 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
From: kernel test robot @ 2026-04-03  8:47 UTC (permalink / raw)
  To: Dennis Dalessandro; +Cc: llvm, oe-kbuild-all, linux-doc

tree:   https://github.com/cornelisnetworks/linux.git for-upstream_20260402-1611
head:   f09b053c0b35d3b9de750922b56cbf341eac7434
commit: f09b053c0b35d3b9de750922b56cbf341eac7434 [31/31] RDMA/hfi2: Modernize mmap to use rdma_user_mmap_entry infrastructure
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20260403/202604031658.ZEU8LAvq-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260403/202604031658.ZEU8LAvq-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604031658.ZEU8LAvq-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> Warning: drivers/infiniband/hw/hfi2/chip_gen.c:577 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
    * Ask cport firmware for the temperature.
--
>> Warning: drivers/infiniband/hw/hfi2/affinity.c:93 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
    * Remove HT/SMT threads from cores in @cpus.
--
>> Warning: drivers/infiniband/hw/hfi2/init.c:969 function parameter 'index' not described in 'allocate_rcd_index'
>> Warning: drivers/infiniband/hw/hfi2/init.c:1984 function parameter 'params' not described in 'hfi2_alloc_devdata'
>> Warning: drivers/infiniband/hw/hfi2/init.c:969 function parameter 'index' not described in 'allocate_rcd_index'
>> Warning: drivers/infiniband/hw/hfi2/init.c:1984 function parameter 'params' not described in 'hfi2_alloc_devdata'
--
>> Warning: drivers/infiniband/hw/hfi2/user_exp_rcv.c:962 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
    * Unprogram TID for @node, updating user TID invalidation events when
--
>> Warning: drivers/infiniband/hw/hfi2/chip.c:8163 function parameter 'ppd' not described in 'set_hdrq_regs'
>> Warning: drivers/infiniband/hw/hfi2/chip.c:10792 function parameter 'ppd' not described in 'hfi2_get_qp_map'
>> Warning: drivers/infiniband/hw/hfi2/chip.c:10836 function parameter 'ppd' not described in 'init_qpmap_table'
>> Warning: drivers/infiniband/hw/hfi2/chip.c:8163 function parameter 'ppd' not described in 'set_hdrq_regs'
>> Warning: drivers/infiniband/hw/hfi2/chip.c:10792 function parameter 'ppd' not described in 'hfi2_get_qp_map'
>> Warning: drivers/infiniband/hw/hfi2/chip.c:10836 function parameter 'ppd' not described in 'init_qpmap_table'

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [mszeredi-fuse:for-next 35/53] Warning: fs/fuse/dev.c:524 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
From: kernel test robot @ 2026-04-03  8:46 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: oe-kbuild-all, fuse-devel, linux-doc

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git for-next
head:   e0d07024fdb2588cd6afcc1b1f1e4bc62ba2c886
commit: ca520dba20d4472694a1895fb4de513be8dab3eb [35/53] fuse: don't access transport layer structs directly from the fs layer
config: i386-randconfig-141-20260403 (https://download.01.org/0day-ci/archive/20260403/202604031126.BGSurGYv-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
smatch: v0.5.0-9004-gb810ac53
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260403/202604031126.BGSurGYv-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604031126.BGSurGYv-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> Warning: fs/fuse/dev.c:524 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst
    * Checks if @fc matches the one installed in @fud

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v6 01/10] KVM: x86: Define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT
From: kernel test robot @ 2026-04-03  8:39 UTC (permalink / raw)
  To: Jim Mattson, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
	Sean Christopherson, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, kvm, linux-doc,
	linux-kernel, linux-kselftest, Yosry Ahmed
  Cc: llvm, oe-kbuild-all, Jim Mattson
In-Reply-To: <20260326174944.3820245-2-jmattson@google.com>

Hi Jim,

kernel test robot noticed the following build errors:

[auto build test ERROR on 3d6cdcc8883b5726513d245eef0e91cabfc397f7]

url:    https://github.com/intel-lab-lkp/linux/commits/Jim-Mattson/KVM-x86-Define-KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT/20260330-195326
base:   3d6cdcc8883b5726513d245eef0e91cabfc397f7
patch link:    https://lore.kernel.org/r/20260326174944.3820245-2-jmattson%40google.com
patch subject: [PATCH v6 01/10] KVM: x86: Define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20260402/202604021052.kPiiRT2h-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260402/202604021052.kPiiRT2h-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604021052.kPiiRT2h-lkp@intel.com/

All errors (new ones prefixed by >>):

   In file included from arch/x86/kvm/svm/svm_onhyperv.c:11:
>> arch/x86/kvm/svm/svm.h:622:4: error: call to undeclared function 'kvm_check_has_quirk'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     622 |                 !kvm_check_has_quirk(svm->vcpu.kvm,
         |                  ^
   In file included from arch/x86/kvm/svm/svm_onhyperv.c:12:
   In file included from arch/x86/kvm/svm/svm_ops.h:7:
>> arch/x86/kvm/x86.h:429:20: error: conflicting types for 'kvm_check_has_quirk'
     429 | static inline bool kvm_check_has_quirk(struct kvm *kvm, u64 quirk)
         |                    ^
   arch/x86/kvm/svm/svm.h:622:4: note: previous implicit declaration is here
     622 |                 !kvm_check_has_quirk(svm->vcpu.kvm,
         |                  ^
   2 errors generated.


vim +/kvm_check_has_quirk +622 arch/x86/kvm/svm/svm.h

   618	
   619	static inline bool l2_has_separate_pat(struct vcpu_svm *svm)
   620	{
   621		return nested_npt_enabled(svm) &&
 > 622			!kvm_check_has_quirk(svm->vcpu.kvm,
   623					     KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
   624	}
   625	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: Re: Re: Re: [PATCH v7 4/4] RISC-V: KVM: add KVM_CAP_RISCV_SET_HGATP_MODE
From: Anup Patel @ 2026-04-03  8:11 UTC (permalink / raw)
  To: fangyu.yu
  Cc: alex, andrew.jones, aou, atish.patra, corbet, guoren, kvm-riscv,
	kvm, linux-doc, linux-kernel, linux-riscv, palmer, pbonzini, pjw,
	radim.krcmar, skhan
In-Reply-To: <20260403070719.64284-1-fangyu.yu@linux.alibaba.com>

On Fri, Apr 3, 2026 at 12:37 PM <fangyu.yu@linux.alibaba.com> wrote:
>
> >>
> >> >>On Thu, Apr 2, 2026 at 6:53 PM <fangyu.yu@linux.alibaba.com> wrote:
> >> >>>
> >> >>> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> >> >>>
> >> >>> Add a VM capability that allows userspace to select the G-stage page table
> >> >>> format by setting HGATP.MODE on a per-VM basis.
> >> >>>
> >> >>> Userspace enables the capability via KVM_ENABLE_CAP, passing the requested
> >> >>> HGATP.MODE in args[0]. The request is rejected with -EINVAL if the mode is
> >> >>> not supported by the host, and with -EBUSY if the VM has already been
> >> >>> committed (e.g. vCPUs have been created or any memslot is populated).
> >> >>>
> >> >>> KVM_CHECK_EXTENSION(KVM_CAP_RISCV_SET_HGATP_MODE) returns a bitmask of the
> >> >>> HGATP.MODE formats supported by the host.
> >> >>>
> >> >>> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> >> >>> Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
> >> >>> Reviewed-by: Guo Ren <guoren@kernel.org>
> >> >>> ---
> >> >>>  Documentation/virt/kvm/api.rst | 27 +++++++++++++++++++++++++++
> >> >>>  arch/riscv/kvm/vm.c            | 18 ++++++++++++++++--
> >> >>>  include/uapi/linux/kvm.h       |  1 +
> >> >>>  3 files changed, 44 insertions(+), 2 deletions(-)
> >> >>>
> >> >>> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> >> >>> index 032516783e96..9d7f6958fa81 100644
> >> >>> --- a/Documentation/virt/kvm/api.rst
> >> >>> +++ b/Documentation/virt/kvm/api.rst
> >> >>> @@ -8902,6 +8902,33 @@ helpful if user space wants to emulate instructions which are not
> >> >>>  This capability can be enabled dynamically even if VCPUs were already
> >> >>>  created and are running.
> >> >>>
> >> >>> +7.47 KVM_CAP_RISCV_SET_HGATP_MODE
> >> >>> +---------------------------------
> >> >>> +
> >> >>> +:Architectures: riscv
> >> >>> +:Type: VM
> >> >>> +:Parameters: args[0] contains the requested HGATP mode
> >> >>> +:Returns:
> >> >>> +  - 0 on success.
> >> >>> +  - -EINVAL if args[0] is outside the range of HGATP modes supported by the
> >> >>> +    hardware.
> >> >>> +  - -EBUSY if vCPUs have already been created for the VM, if the VM has any
> >> >>> +    non-empty memslots.
> >> >>> +
> >> >>> +This capability allows userspace to explicitly select the HGATP mode for
> >> >>> +the VM. The selected mode must be supported by both KVM and hardware. This
> >> >>> +capability must be enabled before creating any vCPUs or memslots.
> >> >>> +
> >> >>> +If this capability is not enabled, KVM will select the default HGATP mode
> >> >>> +automatically. The default is the highest HGATP.MODE value supported by
> >> >>> +hardware.
> >> >>> +
> >> >>> +``KVM_CHECK_EXTENSION(KVM_CAP_RISCV_SET_HGATP_MODE)`` returns a bitmask of
> >> >>> +HGATP.MODE values supported by the host. A return value of 0 indicates that
> >> >>> +the capability is not supported. Supported-mode bitmask use HGATP.MODE
> >> >>> +encodings as defined by the RISC-V privileged specification, such as Sv39x4
> >> >>> +corresponds to HGATP.MODE=8, so userspace should test bitmask & BIT(8).
> >> >>> +
> >> >>>  8. Other capabilities.
> >> >>>  ======================
> >> >>>
> >> >>> diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
> >> >>> index 4d82a886102c..5e82a3ad3ad0 100644
> >> >>> --- a/arch/riscv/kvm/vm.c
> >> >>> +++ b/arch/riscv/kvm/vm.c
> >> >>> @@ -201,6 +201,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
> >> >>>         case KVM_CAP_VM_GPA_BITS:
> >> >>>                 r = kvm_riscv_gstage_gpa_bits(kvm->arch.pgd_levels);
> >> >>>                 break;
> >> >>> +       case KVM_CAP_RISCV_SET_HGATP_MODE:
> >> >>> +               r = kvm_riscv_get_hgatp_mode_mask();
> >> >>> +               break;
> >> >>
> >> >>Introducing a new RISC-V capability looks a bit complex.
> >> >>Instead of KVM_CAP_RISCV_SET_HGATP_MODE, we can
> >> >>simply re-use KVM_CAP_VM_GPA_BITS.
> >> >>
> >> >>The kvm_vm_ioctl_check_extension() for KVM_CAP_VM_GPA_BITS
> >> >>return number of GPA bits which in-directly implies the underlying
> >> >>hgatp.MODE. As we know, if it return 59 bits GPA then it means
> >> >>Sv57x4 is the selected hgatp.MODE and Sv48x4 and Sv39x4 modes
> >> >>are also supported as-per RISC-V privileged specification.
> >> >>
> >> >>The kvm_vm_ioctl_enable_cap() for KVM_CAP_VM_GPA_BITS
> >> >>will take the desired number of GPA bits and downsize the selected
> >> >>hgatp.MODE. For example, if user-space ask GPA bits <= 50 and
> >> >>GPA bits > 41 then we select Sv48x4. If user-space ask GPA
> >> >>bits <= 41 then we select Sv39x4. If user-space ask GPA bits <= 59
> >> >>and GPA bits > 50 then we select Sv57x4.
> >> >>
> >> >
> >> >Thanks, that makes sense.
> >> >
> >> >In v8 I’ll drop KVM_CAP_RISCV_SET_HGATP_MODE and re-use KVM_CAP_VM_GPA_BITS
> >> >for both discovery and selection.
> >> >
> >>
> >> Hi Anup,
> >>
> >> While working on the respin reusing KVM_CAP_VM_GPA_BITS, I realized
> >> a potential ambiguity in CHECK_EXTENSION semantics and wanted to confirm the
> >> intended ABI before posting v8.
> >>
> >> One concern about the semantics: today KVM_CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS)
> >> on a VM fd may be interpreted as “the GPA bits for this VM” (or at least what
> >> this VM can use). If we also use KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) to downsize
> >> the selected HGATP.MODE for a particular VM (e.g. to Sv48x4 => 50 bits), then a
> >> subsequent CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS) on the same VM fd would return 50.
> >> Userspace might then assume 50 is the maximum supported by that VM/host and lose
> >> the information that the host actually supports 59 (Sv57x4).
> >
> >I think there is no violation of the semantics because we are providing
> >a way to allow KVM user space change "the GPA bits for this VM”
> >using KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) so subsequent
> >CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS) must return
> >effective number of GPA bits visible to the VM.
>
> Thanks, agreed.
>
> >The only additional constraint I would enforce is that the
> >KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) must
> >return -EBUSY if any of the Guest VCPUs have
> >ran_atleast_once set.
> >
>
> In my current implementation I already return -EBUSY if kvm->created_vcpus
> is non-zero, i.e. the GPA bits can only be changed before any vCPU is created.

Checking kvm->created_vcpus is perfectly fine so no need to change this.

Regards,
Anup

>
> Thanks,
> Fangyu
>
> >Regards,
> >Anup
> >
> >>
> >> Thanks,
> >> Fangyu
> >>
> >> >Thanks,
> >> >Fangyu
> >> >
> >> >>>         default:
> >> >>>                 r = 0;
> >> >>>                 break;
> >> >>> @@ -211,12 +214,23 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
> >> >>>
> >> >>>  int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
> >> >>>  {
> >> >>> +       if (cap->flags)
> >> >>> +               return -EINVAL;
> >> >>> +
> >> >>>         switch (cap->cap) {
> >> >>>         case KVM_CAP_RISCV_MP_STATE_RESET:
> >> >>> -               if (cap->flags)
> >> >>> -                       return -EINVAL;
> >> >>>                 kvm->arch.mp_state_reset = true;
> >> >>>                 return 0;
> >> >>> +       case KVM_CAP_RISCV_SET_HGATP_MODE:
> >> >>> +               if (!kvm_riscv_hgatp_mode_is_valid(cap->args[0]))
> >> >>> +                       return -EINVAL;
> >> >>> +
> >> >>> +               if (kvm->created_vcpus || !kvm_are_all_memslots_empty(kvm))
> >> >>> +                       return -EBUSY;
> >> >>> +#ifdef CONFIG_64BIT
> >> >>> +               kvm->arch.pgd_levels = 3 + cap->args[0] - HGATP_MODE_SV39X4;
> >> >>> +#endif
> >> >>> +               return 0;
> >> >>>         default:
> >> >>>                 return -EINVAL;
> >> >>>         }
> >> >>> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> >> >>> index 80364d4dbebb..a74a80fd4046 100644
> >> >>> --- a/include/uapi/linux/kvm.h
> >> >>> +++ b/include/uapi/linux/kvm.h
> >> >>> @@ -989,6 +989,7 @@ struct kvm_enable_cap {
> >> >>>  #define KVM_CAP_ARM_SEA_TO_USER 245
> >> >>>  #define KVM_CAP_S390_USER_OPEREXEC 246
> >> >>>  #define KVM_CAP_S390_KEYOP 247
> >> >>> +#define KVM_CAP_RISCV_SET_HGATP_MODE 248
> >> >>>
> >> >>>  struct kvm_irq_routing_irqchip {
> >> >>>         __u32 irqchip;
> >> >>> --
> >> >>> 2.50.1
> >> >>>
> >> >>
> >> >>Regards,
> >> >>Anup
> >

^ permalink raw reply

* Re: Re: Re: Re: [PATCH v7 4/4] RISC-V: KVM: add KVM_CAP_RISCV_SET_HGATP_MODE
From: fangyu.yu @ 2026-04-03  7:07 UTC (permalink / raw)
  To: anup
  Cc: alex, andrew.jones, aou, atish.patra, corbet, fangyu.yu, guoren,
	kvm-riscv, kvm, linux-doc, linux-kernel, linux-riscv, palmer,
	pbonzini, pjw, radim.krcmar, skhan
In-Reply-To: <CAAhSdy2CibJNXJYxCvyofXC3CUpCT5KdricNt2aViRSYCOWrrA@mail.gmail.com>

>>
>> >>On Thu, Apr 2, 2026 at 6:53 PM <fangyu.yu@linux.alibaba.com> wrote:
>> >>>
>> >>> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>> >>>
>> >>> Add a VM capability that allows userspace to select the G-stage page table
>> >>> format by setting HGATP.MODE on a per-VM basis.
>> >>>
>> >>> Userspace enables the capability via KVM_ENABLE_CAP, passing the requested
>> >>> HGATP.MODE in args[0]. The request is rejected with -EINVAL if the mode is
>> >>> not supported by the host, and with -EBUSY if the VM has already been
>> >>> committed (e.g. vCPUs have been created or any memslot is populated).
>> >>>
>> >>> KVM_CHECK_EXTENSION(KVM_CAP_RISCV_SET_HGATP_MODE) returns a bitmask of the
>> >>> HGATP.MODE formats supported by the host.
>> >>>
>> >>> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>> >>> Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
>> >>> Reviewed-by: Guo Ren <guoren@kernel.org>
>> >>> ---
>> >>>  Documentation/virt/kvm/api.rst | 27 +++++++++++++++++++++++++++
>> >>>  arch/riscv/kvm/vm.c            | 18 ++++++++++++++++--
>> >>>  include/uapi/linux/kvm.h       |  1 +
>> >>>  3 files changed, 44 insertions(+), 2 deletions(-)
>> >>>
>> >>> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
>> >>> index 032516783e96..9d7f6958fa81 100644
>> >>> --- a/Documentation/virt/kvm/api.rst
>> >>> +++ b/Documentation/virt/kvm/api.rst
>> >>> @@ -8902,6 +8902,33 @@ helpful if user space wants to emulate instructions which are not
>> >>>  This capability can be enabled dynamically even if VCPUs were already
>> >>>  created and are running.
>> >>>
>> >>> +7.47 KVM_CAP_RISCV_SET_HGATP_MODE
>> >>> +---------------------------------
>> >>> +
>> >>> +:Architectures: riscv
>> >>> +:Type: VM
>> >>> +:Parameters: args[0] contains the requested HGATP mode
>> >>> +:Returns:
>> >>> +  - 0 on success.
>> >>> +  - -EINVAL if args[0] is outside the range of HGATP modes supported by the
>> >>> +    hardware.
>> >>> +  - -EBUSY if vCPUs have already been created for the VM, if the VM has any
>> >>> +    non-empty memslots.
>> >>> +
>> >>> +This capability allows userspace to explicitly select the HGATP mode for
>> >>> +the VM. The selected mode must be supported by both KVM and hardware. This
>> >>> +capability must be enabled before creating any vCPUs or memslots.
>> >>> +
>> >>> +If this capability is not enabled, KVM will select the default HGATP mode
>> >>> +automatically. The default is the highest HGATP.MODE value supported by
>> >>> +hardware.
>> >>> +
>> >>> +``KVM_CHECK_EXTENSION(KVM_CAP_RISCV_SET_HGATP_MODE)`` returns a bitmask of
>> >>> +HGATP.MODE values supported by the host. A return value of 0 indicates that
>> >>> +the capability is not supported. Supported-mode bitmask use HGATP.MODE
>> >>> +encodings as defined by the RISC-V privileged specification, such as Sv39x4
>> >>> +corresponds to HGATP.MODE=8, so userspace should test bitmask & BIT(8).
>> >>> +
>> >>>  8. Other capabilities.
>> >>>  ======================
>> >>>
>> >>> diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
>> >>> index 4d82a886102c..5e82a3ad3ad0 100644
>> >>> --- a/arch/riscv/kvm/vm.c
>> >>> +++ b/arch/riscv/kvm/vm.c
>> >>> @@ -201,6 +201,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>> >>>         case KVM_CAP_VM_GPA_BITS:
>> >>>                 r = kvm_riscv_gstage_gpa_bits(kvm->arch.pgd_levels);
>> >>>                 break;
>> >>> +       case KVM_CAP_RISCV_SET_HGATP_MODE:
>> >>> +               r = kvm_riscv_get_hgatp_mode_mask();
>> >>> +               break;
>> >>
>> >>Introducing a new RISC-V capability looks a bit complex.
>> >>Instead of KVM_CAP_RISCV_SET_HGATP_MODE, we can
>> >>simply re-use KVM_CAP_VM_GPA_BITS.
>> >>
>> >>The kvm_vm_ioctl_check_extension() for KVM_CAP_VM_GPA_BITS
>> >>return number of GPA bits which in-directly implies the underlying
>> >>hgatp.MODE. As we know, if it return 59 bits GPA then it means
>> >>Sv57x4 is the selected hgatp.MODE and Sv48x4 and Sv39x4 modes
>> >>are also supported as-per RISC-V privileged specification.
>> >>
>> >>The kvm_vm_ioctl_enable_cap() for KVM_CAP_VM_GPA_BITS
>> >>will take the desired number of GPA bits and downsize the selected
>> >>hgatp.MODE. For example, if user-space ask GPA bits <= 50 and
>> >>GPA bits > 41 then we select Sv48x4. If user-space ask GPA
>> >>bits <= 41 then we select Sv39x4. If user-space ask GPA bits <= 59
>> >>and GPA bits > 50 then we select Sv57x4.
>> >>
>> >
>> >Thanks, that makes sense.
>> >
>> >In v8 I’ll drop KVM_CAP_RISCV_SET_HGATP_MODE and re-use KVM_CAP_VM_GPA_BITS
>> >for both discovery and selection.
>> >
>>
>> Hi Anup,
>>
>> While working on the respin reusing KVM_CAP_VM_GPA_BITS, I realized
>> a potential ambiguity in CHECK_EXTENSION semantics and wanted to confirm the
>> intended ABI before posting v8.
>>
>> One concern about the semantics: today KVM_CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS)
>> on a VM fd may be interpreted as “the GPA bits for this VM” (or at least what
>> this VM can use). If we also use KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) to downsize
>> the selected HGATP.MODE for a particular VM (e.g. to Sv48x4 => 50 bits), then a
>> subsequent CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS) on the same VM fd would return 50.
>> Userspace might then assume 50 is the maximum supported by that VM/host and lose
>> the information that the host actually supports 59 (Sv57x4).
>
>I think there is no violation of the semantics because we are providing
>a way to allow KVM user space change "the GPA bits for this VM”
>using KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) so subsequent
>CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS) must return
>effective number of GPA bits visible to the VM.

Thanks, agreed.

>The only additional constraint I would enforce is that the
>KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) must
>return -EBUSY if any of the Guest VCPUs have
>ran_atleast_once set.
>

In my current implementation I already return -EBUSY if kvm->created_vcpus
is non-zero, i.e. the GPA bits can only be changed before any vCPU is created.

Thanks,
Fangyu

>Regards,
>Anup
>
>>
>> Thanks,
>> Fangyu
>>
>> >Thanks,
>> >Fangyu
>> >
>> >>>         default:
>> >>>                 r = 0;
>> >>>                 break;
>> >>> @@ -211,12 +214,23 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>> >>>
>> >>>  int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
>> >>>  {
>> >>> +       if (cap->flags)
>> >>> +               return -EINVAL;
>> >>> +
>> >>>         switch (cap->cap) {
>> >>>         case KVM_CAP_RISCV_MP_STATE_RESET:
>> >>> -               if (cap->flags)
>> >>> -                       return -EINVAL;
>> >>>                 kvm->arch.mp_state_reset = true;
>> >>>                 return 0;
>> >>> +       case KVM_CAP_RISCV_SET_HGATP_MODE:
>> >>> +               if (!kvm_riscv_hgatp_mode_is_valid(cap->args[0]))
>> >>> +                       return -EINVAL;
>> >>> +
>> >>> +               if (kvm->created_vcpus || !kvm_are_all_memslots_empty(kvm))
>> >>> +                       return -EBUSY;
>> >>> +#ifdef CONFIG_64BIT
>> >>> +               kvm->arch.pgd_levels = 3 + cap->args[0] - HGATP_MODE_SV39X4;
>> >>> +#endif
>> >>> +               return 0;
>> >>>         default:
>> >>>                 return -EINVAL;
>> >>>         }
>> >>> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
>> >>> index 80364d4dbebb..a74a80fd4046 100644
>> >>> --- a/include/uapi/linux/kvm.h
>> >>> +++ b/include/uapi/linux/kvm.h
>> >>> @@ -989,6 +989,7 @@ struct kvm_enable_cap {
>> >>>  #define KVM_CAP_ARM_SEA_TO_USER 245
>> >>>  #define KVM_CAP_S390_USER_OPEREXEC 246
>> >>>  #define KVM_CAP_S390_KEYOP 247
>> >>> +#define KVM_CAP_RISCV_SET_HGATP_MODE 248
>> >>>
>> >>>  struct kvm_irq_routing_irqchip {
>> >>>         __u32 irqchip;
>> >>> --
>> >>> 2.50.1
>> >>>
>> >>
>> >>Regards,
>> >>Anup
>

^ permalink raw reply

* [PATCH v2 3/3] Documentation: clarify the mandatory and desirable info for security reports
From: Willy Tarreau @ 2026-04-03  6:20 UTC (permalink / raw)
  To: greg
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel, Willy Tarreau
In-Reply-To: <20260403062018.31080-1-w@1wt.eu>

A significant part of the effort of the security team consists in begging
reporters for patch proposals, or asking them to provide them in regular
format, and most of the time they're willing to provide this, they just
didn't know that it would help. So let's add a section detailing the
required and desirable contents in a security report to help reporters
write more actionable reports which do not require round trips.

Cc: Eric Dumazet <edumazet@google.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Willy Tarreau <w@1wt.eu>
---
 Documentation/process/security-bugs.rst | 66 ++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 7 deletions(-)

diff --git a/Documentation/process/security-bugs.rst b/Documentation/process/security-bugs.rst
index ac97fc78fecd..0b1f6d8e3cbe 100644
--- a/Documentation/process/security-bugs.rst
+++ b/Documentation/process/security-bugs.rst
@@ -7,6 +7,65 @@ Linux kernel developers take security very seriously.  As such, we'd
 like to know when a security bug is found so that it can be fixed and
 disclosed as quickly as possible.
 
+Preparing your report
+---------------------
+
+Like with any bug report, a security bug report requires a lot of analysis work
+from the developers, so the more information you can share about the issue, the
+better.  Please review the procedure outlined in
+Documentation/admin-guide/reporting-issues.rst if you are unclear about what
+information is helpful.  The following information are absolutely necessary in
+**any** security bug report:
+
+  * **affected kernel version range**: with no version indication, your report
+    will not be processed.  A significant part of reports are for bugs that
+    have already been fixed, so it is extremely important that vulnerabilities
+    are verified on recent versions (development tree or latest stable
+    version), at least by verifying that the code has not changed since the
+    version where it was detected.
+
+  * **description of the problem**: a detailed description of the problem, with
+    traces showing its manifestation, and why you consider that the observed
+    behavior as a problem in the kernel, is necessary.
+
+  * **reproducer**: developers will need to be able to reproduce the problem to
+    consider a fix as effective.  This includes both a way to trigger the issue
+    and a way to confirm it happens.  A reproducer with low complexity
+    dependencies will be needed (source code, shell script, sequence of
+    instructions, file-system image etc).  Binary-only executables are not
+    accepted.  Working exploits are extremely helpful and will not be released
+    without consent from the reporter, unless they are already public.  By
+    definition if an issue cannot be reproduced, it is not exploitable, thus it
+    is not a security bug.
+
+  * **conditions**: if the bug depends on certain configuration options,
+    sysctls, permissions, timing, code modifications etc, these should be
+    indicated.
+
+In addition, the following information are highly desirable:
+
+  * **suspected location of the bug**: the file names and functions where the
+    bug is suspected to be present are very important, at least to help forward
+    the report to the appropriate maintainers.  When not possible (for example,
+    "system freezes each time I run this command"), the security team will help
+    identify the source of the bug.
+
+  * **a proposed fix**: bug reporters who have analyzed the cause of a bug in
+    the source code almost always have an accurate idea on how to fix it,
+    because they spent a long time studying it and its implications.  Proposing
+    a tested fix will save maintainers a lot of time, even if the fix ends up
+    not being the right one, because it helps understand the bug.  When
+    proposing a tested fix, please always format it in a way that can be
+    immediately merged (see Documentation/process/submitting-patches.rst).
+    This will save some back-and-forth exchanges if it is accepted, and you
+    will be credited for finding and fixing this issue.  Note that in this case
+    only a ``Signed-off-by:`` tag is needed, without ``Reported-by:` when the
+    reporter and author are the same.
+
+  * **mitigations**: very often during a bug analysis, some ways of mitigating
+    the issue appear. It is useful to share them, as they can be helpful to
+    keep end users protected during the time it takes them to apply the fix.
+
 Identifying contacts
 --------------------
 
@@ -89,13 +148,6 @@ run additional tests.  Reports where the reporter does not respond promptly
 or cannot effectively discuss their findings may be abandoned if the
 communication does not quickly improve.
 
-As it is with any bug, the more information provided the easier it
-will be to diagnose and fix.  Please review the procedure outlined in
-'Documentation/admin-guide/reporting-issues.rst' if you are unclear about what
-information is helpful.  Any exploit code is very helpful and will not
-be released without consent from the reporter unless it has already been
-made public.
-
 The report must be sent to maintainers, with the security team in ``Cc:``.
 The Linux kernel security team can be contacted by email at
 <security@kernel.org>.  This is a private list of security officers
-- 
2.52.0


^ permalink raw reply related

* [PATCH v2 2/3] Documentation: explain how to find maintainers addresses for security reports
From: Willy Tarreau @ 2026-04-03  6:20 UTC (permalink / raw)
  To: greg
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel, Willy Tarreau
In-Reply-To: <20260403062018.31080-1-w@1wt.eu>

These days, 80% of the work done by the security team consists in
locating the affected subsystem in a report, running get_maintainers on
it, forwarding the report to these persons and responding to the reporter
with them in Cc. This is a huge and unneeded overhead that we must try to
lower for a better overall efficiency. This patch adds a complete section
explaining how to figure the list of recipients to send the report to.

Cc: Eric Dumazet <edumazet@google.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Willy Tarreau <w@1wt.eu>
---
 Documentation/process/security-bugs.rst | 76 ++++++++++++++++++++++++-
 1 file changed, 73 insertions(+), 3 deletions(-)

diff --git a/Documentation/process/security-bugs.rst b/Documentation/process/security-bugs.rst
index da7937fd59df..ac97fc78fecd 100644
--- a/Documentation/process/security-bugs.rst
+++ b/Documentation/process/security-bugs.rst
@@ -5,8 +5,75 @@ Security bugs
 
 Linux kernel developers take security very seriously.  As such, we'd
 like to know when a security bug is found so that it can be fixed and
-disclosed as quickly as possible.  Please report security bugs to the
-Linux kernel security team.
+disclosed as quickly as possible.
+
+Identifying contacts
+--------------------
+
+The most effective way to report a security bug is to send it directly to the
+affected subsystem's maintainers and Cc: the Linux kernel security team.  Do
+not send it to a public list at this stage, unless you have good reasons to
+consider the issue as being public or trivial to discover (e.g. result of a
+widely available automated vulnerability scanning tool that can be repeated by
+anyone).
+
+If you're sending a report for issues affecting multiple parts in the kernel,
+even if they're fairly similar issues, please send individual messages (think
+that maintainers will not all work on the issues at the same time). The only
+exception is when an issue concerns closely related parts maintained by the
+exact same subset of maintainers, and these parts are expected to be fixed all
+at once by the same commit, then it may be acceptable to report them at once.
+
+One difficulty for most first-time reporters is to figure the right list of
+recipients to send a report to.  In the Linux kernel, all official maintainers
+are trusted, so the consequences of accidentally including the wrong maintainer
+are essentially a bit more noise for that person, i.e. nothing dramatic.  As
+such, a suitable method to figure the list of maintainers (which kernel
+security officers use) is to rely on the get_maintainers.pl script, tuned to
+only report maintainers.  This script, when passed a file name, will look for
+its path in the MAINTAINERS file to figure a hierarchical list of relevant
+maintainers.  Calling it a first time with the finest level of filtering will
+most of the time return a short list of this specific file's maintainers::
+
+  $ ./scripts/get_maintainer.pl --no-l --no-r --pattern-depth 1 \
+    drivers/example.c
+  Developer One <dev1@example.com> (maintainer:example driver)
+  Developer Two <dev2@example.org> (maintainer:example driver)
+
+These two maintainers should then receive the message.  If the command does not
+return anything, it means the affected file is part of a wider subsystem, so we
+should be less specific::
+
+  $ ./scripts/get_maintainer.pl --no-l --no-r drivers/example.c
+  Developer One <dev1@example.com> (maintainer:example subsystem)
+  Developer Two <dev2@example.org> (maintainer:example subsystem)
+  Developer Three <dev3@example.com> (maintainer:example subsystem [GENERAL])
+  Developer Four <dev4@example.org> (maintainer:example subsystem [GENERAL])
+
+Here, picking the first, most specific ones, is sufficient.  When the list is
+long, it is possible to produce a comma-delimited e-mail address list on a
+single line suitable for use in the To: field of a mailer like this::
+
+  $ ./scripts/get_maintainer.pl --no-tree --no-l --no-r --no-n --m \
+    --no-git-fallback --no-substatus --no-rolestats --no-multiline \
+    --pattern-depth 1 drivers/example.c
+  dev1@example.com, dev2@example.org
+
+or this for the wider list::
+
+  $ ./scripts/get_maintainer.pl --no-tree --no-l --no-r --no-n --m \
+    --no-git-fallback --no-substatus --no-rolestats --no-multiline \
+    drivers/example.c
+  dev1@example.com, dev2@example.org, dev3@example.com, dev4@example.org
+
+If at this point you're still facing difficulties spotting the right
+maintainers, **and only in this case**, it's possible to send your report to
+the Linux kernel security team only.  Your message will be triaged, and you
+will receive instructions about whom to contact, if needed.  Your message may
+equally be forwarded as-is to the relevant maintainers.
+
+Sending the report
+------------------
 
 Reports are to be sent over e-mail exclusively.  Please use a working e-mail
 address, preferably the same that you want to appear in ``Reported-by`` tags
@@ -29,6 +96,7 @@ information is helpful.  Any exploit code is very helpful and will not
 be released without consent from the reporter unless it has already been
 made public.
 
+The report must be sent to maintainers, with the security team in ``Cc:``.
 The Linux kernel security team can be contacted by email at
 <security@kernel.org>.  This is a private list of security officers
 who will help verify the bug report and assist developers working on a fix.
@@ -44,7 +112,9 @@ reproduction steps, and follow it with a proposed fix, all in plain text.
 Markdown, HTML and RST formatted reports are particularly frowned upon since
 they're quite hard to read for humans and encourage to use dedicated viewers,
 sometimes online, which by definition is not acceptable for a confidential
-security report.
+security report. Note that some mailers tend to mangle formatting of plain
+text by default, please consult Documentation/process/email-clients.rst for
+more info.
 
 Disclosure and embargoed information
 ------------------------------------
-- 
2.52.0


^ permalink raw reply related

* [PATCH v2 0/3] Documentation: clarify required info in security reports
From: Willy Tarreau @ 2026-04-03  6:20 UTC (permalink / raw)
  To: greg
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel, Willy Tarreau

Hi Greg,

I'm sending you the doc clarifications we discussed for the process of
reporting security issues. It's cut into the 3 patches I shared this
morning on the security list (plus two typos fixed and a paragraph
asking for one single issue per report):

  - one patch that reminds our need for a valid e-mail address
  - one that explains to reporters how to proceed to find maintainers
    addresses, hoping we won't have to do it for 90% of reports anymore
  - one that enumerates basic requirements for every report

I think it covers the difficulties we've faced this week. As always,
we might possibly find tiny adjustments to add, but my goal would be
for such updates to be merged in time to update the public page ASAP
so that we can redirect incomplete reports in an attempt to lower the
team's current load.

Thanks!
Willy

---

v2:
  - dropped quotes around a doc link and turned two relative doc links
    to absolute ones (thanks Randy).

---
Willy Tarreau (3):
  Documentation: minor updates to the security contacts
  Documentation: explain how to find maintainers addresses for security
    reports
  Documentation: clarify the mandatory and desirable info for security
    reports

 Documentation/process/security-bugs.rst | 147 +++++++++++++++++++++---
 1 file changed, 132 insertions(+), 15 deletions(-)

-- 
2.52.0


^ permalink raw reply


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