* [PATCH v2 1/5] dt-bindings: iio: adc: add xlnx,versal-sysmon binding
2026-05-02 11:19 [PATCH v2 0/5] iio: adc: add AMD/Xilinx Versal SysMon driver Salih Erim
@ 2026-05-02 11:19 ` Salih Erim
2026-05-02 11:19 ` [PATCH v2 2/5] iio: adc: add Versal SysMon driver Salih Erim
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Salih Erim @ 2026-05-02 11:19 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, git
Cc: nuno.sa, andy, dlechner, michal.simek, conall.ogriofa, erimsalih,
linux-iio, devicetree, linux-kernel, Salih Erim
Add devicetree binding for the AMD/Xilinx Versal System Monitor (SysMon).
The Versal SysMon is the successor to the Zynq UltraScale+ AMS block,
providing on-chip voltage and temperature monitoring. The hardware
supports up to 160 supply voltage measurement points and up to 64
temperature satellites distributed across the SoC, with configurable
threshold alarms and oversampling. The device can be accessed via
memory-mapped I/O or via an I2C interface.
Supply and temperature channels are described as child nodes under
container nodes, referencing the standard adc.yaml binding for
channel properties.
Co-developed-by: Michal Simek <michal.simek@amd.com>
Signed-off-by: Michal Simek <michal.simek@amd.com>
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v2:
- Restructured to container nodes (supply-channels, temperature-channels)
with channel@N children referencing adc.yaml
- Added xlnx,versal-sysmon-i2c compatible
- Descriptions rewritten to describe hardware only
- Example simplified to #address-cells = <1>
- Interrupt example uses GIC_SPI/IRQ_TYPE_LEVEL_HIGH constants
- Commit description explains hardware context instead of schema layout
- reg required for both MMIO and I2C, interrupts optional
- Hex unit-addresses (channel@a not channel@10) per DTSpec
- patternProperties regex updated to accept hex digits [0-9a-f]
- Example trimmed to minimal variants (one basic + one bipolar supply,
one AIE temperature channel)
.../bindings/iio/adc/xlnx,versal-sysmon.yaml | 172 ++++++++++++++++++
1 file changed, 172 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/xlnx,versal-sysmon.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/xlnx,versal-sysmon.yaml b/Documentation/devicetree/bindings/iio/adc/xlnx,versal-sysmon.yaml
new file mode 100644
index 00000000000..cdd8706fc02
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/xlnx,versal-sysmon.yaml
@@ -0,0 +1,172 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/xlnx,versal-sysmon.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AMD/Xilinx Versal System Monitor
+
+maintainers:
+ - Salih Erim <salih.erim@amd.com>
+
+description:
+ The AMD/Xilinx Versal System Monitor (SysMon) is the successor to the
+ Zynq UltraScale+ AMS block. It provides on-chip voltage and temperature
+ monitoring with up to 160 supply voltage measurement points and up to
+ 64 temperature satellites distributed across the SoC. The hardware
+ supports configurable threshold alarms and oversampling. The device
+ can be accessed via memory-mapped I/O or via an I2C interface.
+
+properties:
+ compatible:
+ enum:
+ - xlnx,versal-sysmon
+ - xlnx,versal-sysmon-i2c
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ '#io-channel-cells':
+ const: 1
+
+ supply-channels:
+ type: object
+ description:
+ Container for supply voltage measurement channels.
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ "^channel@([0-9a-f]|[1-9][0-9a-f])$":
+ $ref: adc.yaml
+
+ description:
+ Measures a supply rail voltage. The register index and rail
+ name are assigned by the hardware design tool (Vivado).
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 159
+ description:
+ Supply measurement register index assigned by the hardware
+ design tool.
+
+ label:
+ description:
+ Name of the supply rail being monitored.
+
+ bipolar: true
+
+ required:
+ - reg
+ - label
+
+ unevaluatedProperties: false
+
+ required:
+ - '#address-cells'
+ - '#size-cells'
+
+ additionalProperties: false
+
+ temperature-channels:
+ type: object
+ description:
+ Container for temperature satellite measurement channels.
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ "^channel@([1-9a-f]|[1-3][0-9a-f]|40)$":
+ $ref: adc.yaml
+
+ description:
+ Reads a temperature satellite sensor. Each satellite monitors
+ a specific region of the SoC die.
+
+ properties:
+ reg:
+ minimum: 1
+ maximum: 64
+ description:
+ Temperature satellite number (1-based hardware index).
+
+ label:
+ description:
+ Name identifying this temperature satellite.
+
+ xlnx,aie-temp:
+ type: boolean
+ description:
+ Indicates this satellite monitors an AI Engine tile.
+
+ required:
+ - reg
+ - label
+
+ unevaluatedProperties: false
+
+ required:
+ - '#address-cells'
+ - '#size-cells'
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ sysmon@f1270000 {
+ compatible = "xlnx,versal-sysmon";
+ reg = <0xf1270000 0x4000>;
+ interrupts = <GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>;
+ #io-channel-cells = <1>;
+
+ supply-channels {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ channel@0 {
+ reg = <0>;
+ label = "vccaux";
+ };
+
+ channel@3 {
+ reg = <3>;
+ label = "vcc_ram";
+ bipolar;
+ };
+ };
+
+ temperature-channels {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ channel@a {
+ reg = <10>;
+ label = "aie-temp-ch1";
+ xlnx,aie-temp;
+ };
+ };
+ };
--
2.48.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/5] iio: adc: add Versal SysMon driver
2026-05-02 11:19 [PATCH v2 0/5] iio: adc: add AMD/Xilinx Versal SysMon driver Salih Erim
2026-05-02 11:19 ` [PATCH v2 1/5] dt-bindings: iio: adc: add xlnx,versal-sysmon binding Salih Erim
@ 2026-05-02 11:19 ` Salih Erim
2026-05-02 11:19 ` [PATCH v2 3/5] iio: adc: versal-sysmon: add I2C driver Salih Erim
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Salih Erim @ 2026-05-02 11:19 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, git
Cc: nuno.sa, andy, dlechner, michal.simek, conall.ogriofa, erimsalih,
linux-iio, devicetree, linux-kernel, Salih Erim
Add the AMD/Xilinx Versal System Monitor (SysMon) IIO driver.
The driver is split into a bus-agnostic core module
(versal-sysmon-core) and a memory-mapped I/O platform driver
(versal-sysmon). The core uses the regmap API so that different
bus implementations can share the same IIO logic.
The core provides:
- Static temperature channels (current max/min, peak max/min)
- Supply voltage channels parsed from DT container nodes
- Temperature satellite channels parsed from DT container nodes
- read_raw for IIO_CHAN_INFO_RAW and IIO_CHAN_INFO_PROCESSED
- read_label using the DT label property
The MMIO platform driver provides:
- Memory-mapped register access via custom regmap callbacks
- NPI unlock before every register write (platform management
controller may re-lock NPI unpredictably on Versal devices)
Threshold events, oversampling, and I2C bus support are added in
subsequent patches.
Co-developed-by: Michal Simek <michal.simek@amd.com>
Signed-off-by: Michal Simek <michal.simek@amd.com>
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v2:
- Split into core (versal-sysmon-core.c) + MMIO platform driver
(versal-sysmon.c) + shared header (versal-sysmon.h)
- Uses regmap API instead of direct readl/writel
- MMIO regmap uses custom callbacks with NPI unlock in write path
- Reverse Christmas Tree variable ordering throughout
- Header include order fixed
- MAINTAINERS entry folded in with wildcard F: pattern
- Kconfig: hidden VERSAL_SYSMON_CORE + VERSAL_SYSMON selects it
- Kconfig/Makefile: alphabetical ordering (VERSAL before VF610)
- Bounds validation on DT reg values
- Named constants replace magic numbers (SYSMON_REG_STRIDE,
SYSMON_SUPPLY_MANTISSA_BITS, SYSMON_MILLI)
- kernel-doc for exported sysmon_core_probe() and sysmon_parse_fw()
- Supply voltage conversion uses proper two's complement sign
extension (s16 cast) matching the hardware specification
- Register offsets sorted by address in header
- Each patch introduces only the defines, fields, and includes
it uses (no dead code in any commit)
- Removed unused linux/limits.h and linux/units.h includes
- Renamed iio_dev_info to sysmon_iio_info
- regmap_write return values checked in probe init path
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 20 ++
drivers/iio/adc/Makefile | 2 +
drivers/iio/adc/versal-sysmon-core.c | 320 +++++++++++++++++++++++++++
drivers/iio/adc/versal-sysmon.c | 94 ++++++++
drivers/iio/adc/versal-sysmon.h | 69 ++++++
6 files changed, 512 insertions(+)
create mode 100644 drivers/iio/adc/versal-sysmon-core.c
create mode 100644 drivers/iio/adc/versal-sysmon.c
create mode 100644 drivers/iio/adc/versal-sysmon.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd1..46762c8496d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29216,6 +29216,13 @@ F: Documentation/devicetree/bindings/memory-controllers/xlnx,versal-net-ddrmc5.y
F: drivers/edac/versalnet_edac.c
F: include/linux/cdx/edac_cdx_pcol.h
+XILINX VERSAL SYSMON DRIVER
+M: Salih Erim <salih.erim@amd.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/xlnx,versal-sysmon.yaml
+F: drivers/iio/adc/versal-sysmon*
+
XILINX WATCHDOG DRIVER
M: Srinivas Neeli <srinivas.neeli@amd.com>
R: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a9dedbb8eb4..c7f19057484 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1943,6 +1943,26 @@ config TWL6030_GPADC
This driver can also be built as a module. If so, the module will be
called twl6030-gpadc.
+config VERSAL_SYSMON_CORE
+ tristate
+ select REGMAP
+
+config VERSAL_SYSMON
+ tristate "AMD Versal SysMon driver"
+ depends on ARCH_ZYNQMP || COMPILE_TEST
+ depends on HAS_IOMEM
+ select VERSAL_SYSMON_CORE
+ help
+ Say yes here to have support for the AMD/Xilinx Versal System
+ Monitor (SysMon). This driver provides voltage and temperature
+ monitoring through the IIO subsystem.
+
+ The SysMon measures up to 160 supply voltages and reads up to
+ 64 temperature satellites distributed across the SoC.
+
+ To compile this driver as a module, choose M here: the module
+ will be called versal-sysmon.
+
config VF610_ADC
tristate "Freescale vf610 ADC driver"
depends on HAS_IOMEM
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 097357d146b..d7696b1b157 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -167,6 +167,8 @@ obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
obj-$(CONFIG_TI_TSC2046) += ti-tsc2046.o
obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
+obj-$(CONFIG_VERSAL_SYSMON_CORE) += versal-sysmon-core.o
+obj-$(CONFIG_VERSAL_SYSMON) += versal-sysmon.o
obj-$(CONFIG_VF610_ADC) += vf610_adc.o
obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
new file mode 100644
index 00000000000..37736c2900b
--- /dev/null
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Versal SysMon core driver
+ *
+ * Copyright (C) 2019 - 2022, Xilinx, Inc.
+ * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "versal-sysmon.h"
+
+#define SYSMON_CHAN_TEMP(_chan, _address, _ext) { \
+ .type = IIO_TEMP, \
+ .indexed = 1, \
+ .address = _address, \
+ .channel = _chan, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_PROCESSED), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 15, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+ .datasheet_name = _ext, \
+}
+
+/* Static temperature channels (always present) */
+static const struct iio_chan_spec temp_channels[] = {
+ SYSMON_CHAN_TEMP(0, SYSMON_TEMP_MAX, "temp"),
+ SYSMON_CHAN_TEMP(1, SYSMON_TEMP_MIN, "min"),
+ SYSMON_CHAN_TEMP(2, SYSMON_TEMP_MAX_MAX, "max_max"),
+ SYSMON_CHAN_TEMP(3, SYSMON_TEMP_MIN_MIN, "min_min"),
+};
+
+static void sysmon_q8p7_to_millicelsius(int raw_data, int *val)
+{
+ *val = ((s16)raw_data * SYSMON_MILLI) >> SYSMON_FRACTIONAL_SHIFT;
+}
+
+static void sysmon_supply_rawtoprocessed(int raw_data, int *val)
+{
+ int mantissa, format, exponent;
+
+ mantissa = FIELD_GET(SYSMON_MANTISSA_MASK, raw_data);
+ exponent = SYSMON_SUPPLY_MANTISSA_BITS - FIELD_GET(SYSMON_MODE_MASK, raw_data);
+ format = FIELD_GET(SYSMON_FMT_MASK, raw_data);
+ /*
+ * When format bit is set the mantissa is two's complement
+ * (per hardware spec); sign-extend to int for correct arithmetic.
+ */
+ if (format)
+ mantissa = (int)(s16)mantissa;
+
+ *val = (mantissa * SYSMON_MILLI) >> exponent;
+}
+
+static int sysmon_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned int regval;
+ int ret;
+
+ if (mask != IIO_CHAN_INFO_RAW && mask != IIO_CHAN_INFO_PROCESSED)
+ return -EINVAL;
+
+ guard(mutex)(&sysmon->lock);
+
+ switch (chan->type) {
+ case IIO_TEMP:
+ ret = regmap_read(sysmon->regmap, chan->address, ®val);
+ if (ret)
+ return ret;
+ if (mask == IIO_CHAN_INFO_PROCESSED)
+ sysmon_q8p7_to_millicelsius(regval, val);
+ else
+ *val = (int)regval;
+ return IIO_VAL_INT;
+
+ case IIO_VOLTAGE:
+ ret = regmap_read(sysmon->regmap,
+ (chan->address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_BASE,
+ ®val);
+ if (ret)
+ return ret;
+ if (mask == IIO_CHAN_INFO_PROCESSED)
+ sysmon_supply_rawtoprocessed(regval, val);
+ else
+ *val = (int)regval;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sysmon_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ if (chan->datasheet_name)
+ return sysfs_emit(label, "%s\n", chan->datasheet_name);
+
+ return -EINVAL;
+}
+
+static const struct iio_info sysmon_iio_info = {
+ .read_raw = sysmon_read_raw,
+ .read_label = sysmon_read_label,
+};
+
+/**
+ * sysmon_parse_fw() - Parse firmware nodes and configure IIO channels.
+ * @indio_dev: IIO device instance
+ * @dev: Parent device
+ *
+ * Reads supply-channels and temperature-channels container nodes from
+ * firmware and builds the IIO channel array. Static temperature channels
+ * are prepended, followed by supply and satellite channels from DT.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
+{
+ unsigned int idx, temp_chan_idx, volt_chan_idx;
+ struct fwnode_handle *supply_node, *temp_node;
+ unsigned int num_supply = 0, num_temp = 0;
+ struct iio_chan_spec *sysmon_channels;
+ const char *label;
+ u32 reg;
+ int ret;
+
+ supply_node = device_get_named_child_node(dev, "supply-channels");
+ if (supply_node)
+ num_supply = fwnode_get_child_node_count(supply_node);
+
+ temp_node = device_get_named_child_node(dev, "temperature-channels");
+ if (temp_node)
+ num_temp = fwnode_get_child_node_count(temp_node);
+
+ sysmon_channels = devm_kcalloc(dev,
+ ARRAY_SIZE(temp_channels) +
+ num_supply + num_temp,
+ sizeof(*sysmon_channels), GFP_KERNEL);
+ if (!sysmon_channels) {
+ ret = -ENOMEM;
+ goto err_put;
+ }
+
+ /* Static temperature channels first (fixed indices) */
+ idx = 0;
+ memcpy(sysmon_channels, temp_channels, sizeof(temp_channels));
+ idx += ARRAY_SIZE(temp_channels);
+
+ /* Supply channels from DT */
+ if (supply_node) {
+ fwnode_for_each_child_node_scoped(supply_node, child) {
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret < 0)
+ goto err_put;
+
+ if (reg > SYSMON_SUPPLY_IDX_MAX) {
+ ret = -EINVAL;
+ dev_err(dev, "supply reg %u exceeds max %u\n",
+ reg, SYSMON_SUPPLY_IDX_MAX);
+ goto err_put;
+ }
+
+ ret = fwnode_property_read_string(child, "label",
+ &label);
+ if (ret < 0)
+ goto err_put;
+
+ sysmon_channels[idx++] = (struct iio_chan_spec) {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .address = reg,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_type = {
+ .realbits = 19,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ .sign = fwnode_property_read_bool(child,
+ "bipolar") ? 's' : 'u',
+ },
+ .datasheet_name = label,
+ };
+ }
+ fwnode_handle_put(supply_node);
+ supply_node = NULL;
+ }
+
+ /* Temperature satellite channels from DT */
+ if (temp_node) {
+ fwnode_for_each_child_node_scoped(temp_node, child) {
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret < 0)
+ goto err_put;
+
+ if (reg < 1 || reg > SYSMON_TEMP_SAT_MAX) {
+ ret = -EINVAL;
+ dev_err(dev, "temp reg %u out of range [1..%u]\n",
+ reg, SYSMON_TEMP_SAT_MAX);
+ goto err_put;
+ }
+
+ ret = fwnode_property_read_string(child, "label",
+ &label);
+ if (ret < 0)
+ goto err_put;
+
+ sysmon_channels[idx++] = (struct iio_chan_spec) {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .address = SYSMON_TEMP_SAT_BASE +
+ ((reg - 1) * SYSMON_REG_STRIDE),
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_type = {
+ .sign = 's',
+ .realbits = 15,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ .datasheet_name = label,
+ };
+ }
+ fwnode_handle_put(temp_node);
+ temp_node = NULL;
+ }
+
+ indio_dev->num_channels = idx;
+ indio_dev->info = &sysmon_iio_info;
+
+ /*
+ * Assign per-type sequential channel numbers.
+ * IIO sysfs uses type prefix (in_tempN, in_voltageN)
+ * so numbers only need to be unique within each type.
+ */
+ temp_chan_idx = 0;
+ volt_chan_idx = 0;
+ for (idx = 0; idx < indio_dev->num_channels; idx++) {
+ if (sysmon_channels[idx].type == IIO_TEMP)
+ sysmon_channels[idx].channel = temp_chan_idx++;
+ else
+ sysmon_channels[idx].channel = volt_chan_idx++;
+ }
+
+ indio_dev->channels = sysmon_channels;
+
+ return 0;
+
+err_put:
+ fwnode_handle_put(supply_node);
+ fwnode_handle_put(temp_node);
+ return ret;
+}
+
+/**
+ * sysmon_core_probe() - Initialize Versal SysMon core
+ * @dev: Parent device
+ * @regmap: Register map for hardware access
+ * @irq: Interrupt number (negative if not available)
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq)
+{
+ struct iio_dev *indio_dev;
+ struct sysmon *sysmon;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*sysmon));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ sysmon = iio_priv(indio_dev);
+ sysmon->dev = dev;
+ sysmon->indio_dev = indio_dev;
+ sysmon->regmap = regmap;
+ sysmon->irq = irq;
+
+ ret = devm_mutex_init(dev, &sysmon->lock);
+ if (ret)
+ return ret;
+
+ /* Disable all interrupts and clear pending status */
+ ret = regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK);
+ if (ret)
+ return ret;
+ ret = regmap_write(sysmon->regmap, SYSMON_ISR, SYSMON_INTR_ALL_MASK);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "versal-sysmon";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = sysmon_parse_fw(indio_dev, dev);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_GPL(sysmon_core_probe);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Versal SysMon Core Driver");
+MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>");
diff --git a/drivers/iio/adc/versal-sysmon.c b/drivers/iio/adc/versal-sysmon.c
new file mode 100644
index 00000000000..c597934e869
--- /dev/null
+++ b/drivers/iio/adc/versal-sysmon.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Versal SysMon MMIO platform driver
+ *
+ * Copyright (C) 2019 - 2022, Xilinx, Inc.
+ * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "versal-sysmon.h"
+
+struct sysmon_mmio {
+ void __iomem *base;
+};
+
+static int sysmon_mmio_reg_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct sysmon_mmio *mmio = context;
+
+ *val = readl(mmio->base + reg);
+ return 0;
+}
+
+static int sysmon_mmio_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct sysmon_mmio *mmio = context;
+
+ /* NPI must be unlocked before any register write except to NPI_LOCK */
+ if (reg != SYSMON_NPI_LOCK)
+ writel(SYSMON_NPI_UNLOCK_CODE, mmio->base + SYSMON_NPI_LOCK);
+ writel(val, mmio->base + reg);
+
+ return 0;
+}
+
+static const struct regmap_config sysmon_mmio_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = SYSMON_REG_STRIDE,
+ .max_register = SYSMON_MAX_REG,
+ .reg_read = sysmon_mmio_reg_read,
+ .reg_write = sysmon_mmio_reg_write,
+ .fast_io = true,
+};
+
+static int sysmon_platform_probe(struct platform_device *pdev)
+{
+ struct sysmon_mmio *mmio;
+ struct regmap *regmap;
+ int irq;
+
+ mmio = devm_kzalloc(&pdev->dev, sizeof(*mmio), GFP_KERNEL);
+ if (!mmio)
+ return -ENOMEM;
+
+ mmio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mmio->base))
+ return PTR_ERR(mmio->base);
+
+ regmap = devm_regmap_init(&pdev->dev, NULL, mmio,
+ &sysmon_mmio_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ irq = platform_get_irq_optional(pdev, 0);
+
+ return sysmon_core_probe(&pdev->dev, regmap, irq);
+}
+
+static const struct of_device_id sysmon_of_match_table[] = {
+ { .compatible = "xlnx,versal-sysmon" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sysmon_of_match_table);
+
+static struct platform_driver sysmon_platform_driver = {
+ .probe = sysmon_platform_probe,
+ .driver = {
+ .name = "versal-sysmon",
+ .of_match_table = sysmon_of_match_table,
+ },
+};
+module_platform_driver(sysmon_platform_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Versal SysMon Platform Driver");
+MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>");
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
new file mode 100644
index 00000000000..fc4d2338328
--- /dev/null
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Versal SysMon driver
+ *
+ * Copyright (C) 2019 - 2022, Xilinx, Inc.
+ * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc.
+ */
+
+#ifndef _VERSAL_SYSMON_H_
+#define _VERSAL_SYSMON_H_
+
+#include <linux/iio/iio.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+/* Register offsets (sorted by address) */
+#define SYSMON_NPI_LOCK 0x000C
+#define SYSMON_ISR 0x0044
+#define SYSMON_IDR 0x0050
+#define SYSMON_TEMP_MAX 0x1030
+#define SYSMON_TEMP_MIN 0x1034
+#define SYSMON_SUPPLY_BASE 0x1040
+#define SYSMON_TEMP_MIN_MIN 0x1F8C
+#define SYSMON_TEMP_MAX_MAX 0x1F90
+#define SYSMON_TEMP_SAT_BASE 0x1FAC
+#define SYSMON_MAX_REG 0x24C0
+
+/* NPI unlock value written to SYSMON_NPI_LOCK */
+#define SYSMON_NPI_UNLOCK_CODE 0xF9E8D7C6
+
+/* Register stride: 4 bytes per 32-bit register */
+#define SYSMON_REG_STRIDE 4
+
+#define SYSMON_SUPPLY_IDX_MAX 159
+#define SYSMON_TEMP_SAT_MAX 64
+#define SYSMON_INTR_ALL_MASK GENMASK(31, 0)
+
+/* Supply voltage conversion register fields */
+#define SYSMON_MANTISSA_MASK GENMASK(15, 0)
+#define SYSMON_FMT_MASK BIT(16)
+#define SYSMON_MODE_MASK GENMASK(18, 17)
+
+/* Q8.7 fractional shift */
+#define SYSMON_FRACTIONAL_SHIFT 7U
+#define SYSMON_SUPPLY_MANTISSA_BITS 16
+
+/* Signed milli scale (MILLI from linux/units.h is unsigned long) */
+#define SYSMON_MILLI 1000
+
+/**
+ * struct sysmon - Driver data for Versal SysMon
+ * @dev: pointer to device struct
+ * @indio_dev: pointer to the iio device (needed for work callbacks)
+ * @regmap: register map for hardware access
+ * @lock: mutex for serializing user-space access
+ * @irq: interrupt number
+ */
+struct sysmon {
+ struct device *dev;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ /* Serializes access to device registers and state */
+ struct mutex lock;
+ int irq;
+};
+
+int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq);
+
+#endif /* _VERSAL_SYSMON_H_ */
--
2.48.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 3/5] iio: adc: versal-sysmon: add I2C driver
2026-05-02 11:19 [PATCH v2 0/5] iio: adc: add AMD/Xilinx Versal SysMon driver Salih Erim
2026-05-02 11:19 ` [PATCH v2 1/5] dt-bindings: iio: adc: add xlnx,versal-sysmon binding Salih Erim
2026-05-02 11:19 ` [PATCH v2 2/5] iio: adc: add Versal SysMon driver Salih Erim
@ 2026-05-02 11:19 ` Salih Erim
2026-05-02 11:19 ` [PATCH v2 4/5] iio: adc: versal-sysmon: add threshold event support Salih Erim
2026-05-02 11:19 ` [PATCH v2 5/5] iio: adc: versal-sysmon: add oversampling support Salih Erim
4 siblings, 0 replies; 6+ messages in thread
From: Salih Erim @ 2026-05-02 11:19 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, git
Cc: nuno.sa, andy, dlechner, michal.simek, conall.ogriofa, erimsalih,
linux-iio, devicetree, linux-kernel, Salih Erim
Add I2C bus driver for Versal SysMon to enable voltage and temperature
monitoring when the Versal chip has SysMon configured with an I2C
interface.
The I2C protocol uses a custom 8-byte write format:
- Bytes 0-3: Data (little-endian, 32-bit)
- Bytes 4-5: Register offset (split into low/high parts)
- Byte 6: Instruction (read=0x4, write=0x8)
- Byte 7: Reserved
For reads, the driver sends the 8-byte command then receives 4 bytes
of data. For writes, it sends the 8-byte command with embedded data.
The driver uses the regmap API with custom read/write callbacks to
share the bus-agnostic core driver (versal-sysmon-core).
Event support is not available on I2C since the SysMon interrupt
lines are not routed over the I2C bus.
Co-developed-by: Conall O'Griofa <conall.ogriofa@amd.com>
Signed-off-by: Conall O'Griofa <conall.ogriofa@amd.com>
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v2:
- New patch (I2C was deferred to Series B in v1)
- Uses regmap API with custom I2C read/write callbacks
- Shares core module with MMIO driver via sysmon_core_probe()
- No event support (I2C has no interrupt line)
- Separate VERSAL_SYSMON_I2C Kconfig symbol
- Reverse Christmas Tree variable ordering in read/write functions
drivers/iio/adc/Kconfig | 13 +++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/versal-sysmon-i2c.c | 166 ++++++++++++++++++++++++++++
3 files changed, 180 insertions(+)
create mode 100644 drivers/iio/adc/versal-sysmon-i2c.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index c7f19057484..8f9fc9de74a 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1963,6 +1963,19 @@ config VERSAL_SYSMON
To compile this driver as a module, choose M here: the module
will be called versal-sysmon.
+config VERSAL_SYSMON_I2C
+ tristate "AMD Versal SysMon I2C driver"
+ depends on I2C
+ select VERSAL_SYSMON_CORE
+ help
+ Say yes here to have support for the AMD/Xilinx Versal System
+ Monitor (SysMon) via I2C interface. This driver enables voltage
+ and temperature monitoring when the Versal chip has SysMon
+ configured with I2C access.
+
+ To compile this driver as a module, choose M here: the module
+ will be called versal-sysmon-i2c.
+
config VF610_ADC
tristate "Freescale vf610 ADC driver"
depends on HAS_IOMEM
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index d7696b1b157..5abb611fe46 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -169,6 +169,7 @@ obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
obj-$(CONFIG_VERSAL_SYSMON_CORE) += versal-sysmon-core.o
obj-$(CONFIG_VERSAL_SYSMON) += versal-sysmon.o
+obj-$(CONFIG_VERSAL_SYSMON_I2C) += versal-sysmon-i2c.o
obj-$(CONFIG_VF610_ADC) += vf610_adc.o
obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
diff --git a/drivers/iio/adc/versal-sysmon-i2c.c b/drivers/iio/adc/versal-sysmon-i2c.c
new file mode 100644
index 00000000000..0464b356bbf
--- /dev/null
+++ b/drivers/iio/adc/versal-sysmon-i2c.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Versal SysMon I2C driver
+ *
+ * Copyright (C) 2023 - 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "versal-sysmon.h"
+
+#define SYSMON_I2C_READ_SIZE 4
+#define SYSMON_I2C_WRITE_SIZE 8
+
+#define SYSMON_I2C_INSTR_READ BIT(2)
+#define SYSMON_I2C_INSTR_WRITE BIT(3)
+
+#define SYSMON_I2C_DATA0_MASK GENMASK(7, 0)
+#define SYSMON_I2C_DATA1_MASK GENMASK(15, 8)
+#define SYSMON_I2C_DATA2_MASK GENMASK(23, 16)
+#define SYSMON_I2C_DATA3_MASK GENMASK(31, 24)
+
+#define SYSMON_I2C_OFS_LOW_MASK GENMASK(9, 2)
+#define SYSMON_I2C_OFS_HIGH_MASK GENMASK(15, 10)
+
+enum sysmon_i2c_payload_idx {
+ SYSMON_I2C_DATA0_IDX = 0,
+ SYSMON_I2C_DATA1_IDX,
+ SYSMON_I2C_DATA2_IDX,
+ SYSMON_I2C_DATA3_IDX,
+ SYSMON_I2C_OFS_LOW_IDX,
+ SYSMON_I2C_OFS_HIGH_IDX,
+ SYSMON_I2C_INSTR_IDX,
+};
+
+struct sysmon_i2c {
+ struct i2c_client *client;
+};
+
+static int sysmon_i2c_reg_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ u8 write_buf[SYSMON_I2C_WRITE_SIZE] = { 0 };
+ u8 read_buf[SYSMON_I2C_READ_SIZE];
+ struct sysmon_i2c *priv = context;
+ int ret;
+
+ write_buf[SYSMON_I2C_OFS_LOW_IDX] =
+ FIELD_GET(SYSMON_I2C_OFS_LOW_MASK, reg);
+ write_buf[SYSMON_I2C_OFS_HIGH_IDX] =
+ FIELD_GET(SYSMON_I2C_OFS_HIGH_MASK, reg);
+ write_buf[SYSMON_I2C_INSTR_IDX] = SYSMON_I2C_INSTR_READ;
+
+ ret = i2c_master_send(priv->client, write_buf, SYSMON_I2C_WRITE_SIZE);
+ if (ret < 0)
+ return ret;
+ if (ret != SYSMON_I2C_WRITE_SIZE)
+ return -EIO;
+
+ ret = i2c_master_recv(priv->client, read_buf, SYSMON_I2C_READ_SIZE);
+ if (ret < 0)
+ return ret;
+ if (ret != SYSMON_I2C_READ_SIZE)
+ return -EIO;
+
+ *val = FIELD_PREP(SYSMON_I2C_DATA0_MASK,
+ read_buf[SYSMON_I2C_DATA0_IDX]) |
+ FIELD_PREP(SYSMON_I2C_DATA1_MASK,
+ read_buf[SYSMON_I2C_DATA1_IDX]) |
+ FIELD_PREP(SYSMON_I2C_DATA2_MASK,
+ read_buf[SYSMON_I2C_DATA2_IDX]) |
+ FIELD_PREP(SYSMON_I2C_DATA3_MASK,
+ read_buf[SYSMON_I2C_DATA3_IDX]);
+
+ return 0;
+}
+
+static int sysmon_i2c_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ u8 write_buf[SYSMON_I2C_WRITE_SIZE] = { 0 };
+ struct sysmon_i2c *priv = context;
+ int ret;
+
+ write_buf[SYSMON_I2C_DATA0_IDX] =
+ FIELD_GET(SYSMON_I2C_DATA0_MASK, val);
+ write_buf[SYSMON_I2C_DATA1_IDX] =
+ FIELD_GET(SYSMON_I2C_DATA1_MASK, val);
+ write_buf[SYSMON_I2C_DATA2_IDX] =
+ FIELD_GET(SYSMON_I2C_DATA2_MASK, val);
+ write_buf[SYSMON_I2C_DATA3_IDX] =
+ FIELD_GET(SYSMON_I2C_DATA3_MASK, val);
+ write_buf[SYSMON_I2C_OFS_LOW_IDX] =
+ FIELD_GET(SYSMON_I2C_OFS_LOW_MASK, reg);
+ write_buf[SYSMON_I2C_OFS_HIGH_IDX] =
+ FIELD_GET(SYSMON_I2C_OFS_HIGH_MASK, reg);
+ write_buf[SYSMON_I2C_INSTR_IDX] = SYSMON_I2C_INSTR_WRITE;
+
+ ret = i2c_master_send(priv->client, write_buf, SYSMON_I2C_WRITE_SIZE);
+ if (ret < 0)
+ return ret;
+ if (ret != SYSMON_I2C_WRITE_SIZE)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct regmap_config sysmon_i2c_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = SYSMON_REG_STRIDE,
+ .max_register = SYSMON_MAX_REG,
+ .reg_read = sysmon_i2c_reg_read,
+ .reg_write = sysmon_i2c_reg_write,
+};
+
+static int sysmon_i2c_probe(struct i2c_client *client)
+{
+ struct sysmon_i2c *priv;
+ struct regmap *regmap;
+
+ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+
+ regmap = devm_regmap_init(&client->dev, NULL, priv,
+ &sysmon_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /* I2C has no IRQ connection; events are not supported */
+ return sysmon_core_probe(&client->dev, regmap, 0);
+}
+
+static const struct of_device_id sysmon_i2c_of_match_table[] = {
+ { .compatible = "xlnx,versal-sysmon-i2c" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sysmon_i2c_of_match_table);
+
+static const struct i2c_device_id sysmon_i2c_id_table[] = {
+ { "versal-sysmon" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sysmon_i2c_id_table);
+
+static struct i2c_driver sysmon_i2c_driver = {
+ .probe = sysmon_i2c_probe,
+ .driver = {
+ .name = "versal-sysmon-i2c",
+ .of_match_table = sysmon_i2c_of_match_table,
+ },
+ .id_table = sysmon_i2c_id_table,
+};
+module_i2c_driver(sysmon_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Versal SysMon I2C Driver");
+MODULE_AUTHOR("Conall O'Griofa <conall.ogriofa@amd.com>");
+MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>");
--
2.48.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 4/5] iio: adc: versal-sysmon: add threshold event support
2026-05-02 11:19 [PATCH v2 0/5] iio: adc: add AMD/Xilinx Versal SysMon driver Salih Erim
` (2 preceding siblings ...)
2026-05-02 11:19 ` [PATCH v2 3/5] iio: adc: versal-sysmon: add I2C driver Salih Erim
@ 2026-05-02 11:19 ` Salih Erim
2026-05-02 11:19 ` [PATCH v2 5/5] iio: adc: versal-sysmon: add oversampling support Salih Erim
4 siblings, 0 replies; 6+ messages in thread
From: Salih Erim @ 2026-05-02 11:19 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, git
Cc: nuno.sa, andy, dlechner, michal.simek, conall.ogriofa, erimsalih,
linux-iio, devicetree, linux-kernel, Salih Erim
Add threshold event support for temperature and supply voltage
channels.
Temperature events:
- Rising/falling threshold with configurable values
- Over-temperature (OT) alarm with separate thresholds
- Per-channel hysteresis configuration
Supply voltage events:
- Rising/falling threshold per supply channel
- Per-channel alarm enable via alarm configuration registers
The interrupt handler masks active threshold interrupts (which are
level-sensitive) and schedules a delayed worker to poll for condition
clear before unmasking. When no hardware IRQ is available (irq <= 0),
event channels are not created and interrupt init is skipped, since
the I2C regmap backend cannot be called from atomic context.
When disabling a supply channel alarm, the group interrupt remains
active if any other channel in the same alarm group still has an
alarm enabled.
Named constants replace magic numbers for hysteresis bit positions
(SYSMON_OT_HYST_BIT, SYSMON_TEMP_HYST_BIT) and alarm register width
(SYSMON_ALARM_BITS_PER_REG).
Hysteresis values are validated to single-bit range (0 or 1) before
writing to the hardware register.
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v2:
- Reverse Christmas Tree variable ordering in all functions
- Named constants for hysteresis bits: SYSMON_OT_HYST_BIT,
SYSMON_TEMP_HYST_BIT instead of magic 0x1/0x2
- SYSMON_ALARM_BITS_PER_REG replaces magic number 32
- SYSMON_ALARM_OFFSET() helper macro deduplicates alarm register
offset computation
- BIT() macro for shift expressions in conversion functions
- Hysteresis input validated to single-bit range (0 or 1)
- Event channels only created when irq > 0 (I2C safety)
- Group alarm interrupt stays active while any channel in the
group has an alarm enabled
- write_event_value returns -EINVAL for unhandled types
- IRQ_NONE returned for spurious interrupts
- Q8.7 write path uses multiplication instead of left-shift
to avoid undefined behavior with negative temperatures
- (u16) mask prevents garbage in reserved register bits
- regmap_write return values checked for IER/IDR writes
- devm cleanup ordering: cancel_work before request_irq
drivers/iio/adc/versal-sysmon-core.c | 539 ++++++++++++++++++++++++++-
drivers/iio/adc/versal-sysmon.h | 36 ++
2 files changed, 574 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
index 37736c2900b..857fe21db7a 100644
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -9,13 +9,24 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
+#include <linux/iio/events.h>
#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include "versal-sysmon.h"
+/* OT and TEMP hysteresis bit positions in SYSMON_TEMP_EV_CFG */
+#define SYSMON_OT_HYST_BIT BIT(0)
+#define SYSMON_TEMP_HYST_BIT BIT(1)
+
+/* Compute alarm register offset from a channel address */
+#define SYSMON_ALARM_OFFSET(addr) \
+ (SYSMON_ALARM_REG + ((addr) / SYSMON_ALARM_BITS_PER_REG) * SYSMON_REG_STRIDE)
+
#define SYSMON_CHAN_TEMP(_chan, _address, _ext) { \
.type = IIO_TEMP, \
.indexed = 1, \
@@ -32,6 +43,71 @@
.datasheet_name = _ext, \
}
+#define SYSMON_CHAN_TEMP_EVENT(_chan, _address, _ext, _events) { \
+ .type = IIO_TEMP, \
+ .indexed = 1, \
+ .address = _address, \
+ .channel = _chan, \
+ .event_spec = _events, \
+ .num_event_specs = ARRAY_SIZE(_events), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 15, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+ .datasheet_name = _ext, \
+}
+
+enum sysmon_alarm_bit {
+ SYSMON_BIT_ALARM0 = 0,
+ SYSMON_BIT_ALARM1 = 1,
+ SYSMON_BIT_ALARM2 = 2,
+ SYSMON_BIT_ALARM3 = 3,
+ SYSMON_BIT_ALARM4 = 4,
+ SYSMON_BIT_OT = 8,
+ SYSMON_BIT_TEMP = 9,
+};
+
+/* Temperature event specifications */
+static const struct iio_event_spec sysmon_temp_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_HYSTERESIS),
+ },
+};
+
+/* Supply event specifications */
+static const struct iio_event_spec sysmon_supply_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
/* Static temperature channels (always present) */
static const struct iio_chan_spec temp_channels[] = {
SYSMON_CHAN_TEMP(0, SYSMON_TEMP_MAX, "temp"),
@@ -40,11 +116,24 @@ static const struct iio_chan_spec temp_channels[] = {
SYSMON_CHAN_TEMP(3, SYSMON_TEMP_MIN_MIN, "min_min"),
};
+/* Temperature event channels (threshold alarms) */
+static const struct iio_chan_spec temp_event_channels[] = {
+ SYSMON_CHAN_TEMP_EVENT(4, SYSMON_ADDR_TEMP_EVENT, "temp",
+ sysmon_temp_events),
+ SYSMON_CHAN_TEMP_EVENT(5, SYSMON_ADDR_OT_EVENT, "ot",
+ sysmon_temp_events),
+};
+
static void sysmon_q8p7_to_millicelsius(int raw_data, int *val)
{
*val = ((s16)raw_data * SYSMON_MILLI) >> SYSMON_FRACTIONAL_SHIFT;
}
+static void sysmon_millicelsius_to_q8p7(u32 *raw_data, int val)
+{
+ *raw_data = (u16)((val * (int)BIT(SYSMON_FRACTIONAL_SHIFT)) / SYSMON_MILLI);
+}
+
static void sysmon_supply_rawtoprocessed(int raw_data, int *val)
{
int mantissa, format, exponent;
@@ -62,6 +151,49 @@ static void sysmon_supply_rawtoprocessed(int raw_data, int *val)
*val = (mantissa * SYSMON_MILLI) >> exponent;
}
+static void sysmon_supply_processedtoraw(int val, u32 reg_val, u32 *raw_data)
+{
+ int exponent = FIELD_GET(SYSMON_MODE_MASK, reg_val);
+ int format = FIELD_GET(SYSMON_FMT_MASK, reg_val);
+ int scale, tmp;
+
+ scale = BIT(SYSMON_SUPPLY_MANTISSA_BITS - exponent);
+ tmp = (val * scale) / SYSMON_MILLI;
+
+ if (format)
+ tmp = clamp(tmp, (int)S16_MIN, (int)S16_MAX);
+ else
+ tmp = clamp(tmp, 0, (int)U16_MAX);
+
+ *raw_data = tmp & U16_MAX;
+}
+
+static int sysmon_temp_thresh_offset(int address,
+ enum iio_event_direction dir)
+{
+ switch (address) {
+ case SYSMON_ADDR_TEMP_EVENT:
+ return (dir == IIO_EV_DIR_RISING) ? SYSMON_TEMP_TH_UP :
+ SYSMON_TEMP_TH_LOW;
+ case SYSMON_ADDR_OT_EVENT:
+ return (dir == IIO_EV_DIR_RISING) ? SYSMON_OT_TH_UP :
+ SYSMON_OT_TH_LOW;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sysmon_supply_thresh_offset(int address,
+ enum iio_event_direction dir)
+{
+ if (dir == IIO_EV_DIR_RISING)
+ return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_UP;
+ if (dir == IIO_EV_DIR_FALLING)
+ return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_LOW;
+
+ return -EINVAL;
+}
+
static int sysmon_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
@@ -103,6 +235,209 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
}
}
+static int sysmon_get_event_mask(unsigned long address)
+{
+ if (address == SYSMON_ADDR_TEMP_EVENT)
+ return BIT(SYSMON_BIT_TEMP);
+ if (address == SYSMON_ADDR_OT_EVENT)
+ return BIT(SYSMON_BIT_OT);
+
+ return BIT(address / SYSMON_ALARM_BITS_PER_REG);
+}
+
+static int sysmon_read_alarm_config(struct sysmon *sysmon,
+ unsigned long address)
+{
+ u32 shift = address % SYSMON_ALARM_BITS_PER_REG;
+ u32 offset = SYSMON_ALARM_OFFSET(address);
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(sysmon->regmap, offset, ®_val);
+ if (ret)
+ return ret;
+
+ return reg_val & BIT(shift);
+}
+
+static int sysmon_write_alarm_config(struct sysmon *sysmon,
+ unsigned long address, u32 val)
+{
+ u32 shift = address % SYSMON_ALARM_BITS_PER_REG;
+ u32 offset = SYSMON_ALARM_OFFSET(address);
+
+ return regmap_update_bits(sysmon->regmap, offset,
+ BIT(shift), val << shift);
+}
+
+static int sysmon_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ u32 alarm_event_mask = sysmon_get_event_mask(chan->address);
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned int imr;
+ int config_value;
+ int ret;
+
+ ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
+ if (ret)
+ return ret;
+ imr = ~imr;
+
+ if (chan->type == IIO_VOLTAGE) {
+ config_value = sysmon_read_alarm_config(sysmon, chan->address);
+ if (config_value < 0)
+ return config_value;
+ return config_value && (imr & alarm_event_mask);
+ }
+
+ return !!(imr & alarm_event_mask);
+}
+
+static int sysmon_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir, bool state)
+{
+ u32 offset = SYSMON_ALARM_OFFSET(chan->address);
+ u32 ier = sysmon_get_event_mask(chan->address);
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned int alarm_config;
+ int ret;
+
+ guard(mutex)(&sysmon->lock);
+ guard(spinlock_irqsave)(&sysmon->irq_lock);
+
+ if (chan->type == IIO_VOLTAGE) {
+ ret = sysmon_write_alarm_config(sysmon, chan->address, state);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(sysmon->regmap, offset, &alarm_config);
+ if (ret)
+ return ret;
+
+ if (alarm_config)
+ return regmap_write(sysmon->regmap, SYSMON_IER, ier);
+ else
+ return regmap_write(sysmon->regmap, SYSMON_IDR, ier);
+ } else if (chan->type == IIO_TEMP) {
+ if (state) {
+ ret = regmap_write(sysmon->regmap, SYSMON_IER, ier);
+ if (ret)
+ return ret;
+ sysmon->temp_mask &= ~ier;
+ } else {
+ ret = regmap_write(sysmon->regmap, SYSMON_IDR, ier);
+ if (ret)
+ return ret;
+ sysmon->temp_mask |= ier;
+ }
+ }
+
+ return 0;
+}
+
+static int sysmon_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val,
+ int *val2)
+{
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned int reg_val;
+ u32 mask, shift;
+ int offset;
+ int ret;
+
+ guard(mutex)(&sysmon->lock);
+
+ if (chan->type == IIO_TEMP) {
+ if (info == IIO_EV_INFO_VALUE) {
+ offset = sysmon_temp_thresh_offset(chan->address, dir);
+ if (offset < 0)
+ return offset;
+ ret = regmap_read(sysmon->regmap, offset, ®_val);
+ if (ret)
+ return ret;
+ sysmon_q8p7_to_millicelsius(reg_val, val);
+ return IIO_VAL_INT;
+ }
+ if (info == IIO_EV_INFO_HYSTERESIS) {
+ mask = (chan->address == SYSMON_ADDR_OT_EVENT) ?
+ SYSMON_OT_HYST_BIT : SYSMON_TEMP_HYST_BIT;
+ shift = (chan->address == SYSMON_ADDR_OT_EVENT) ? 0 : 1;
+ ret = regmap_read(sysmon->regmap, SYSMON_TEMP_EV_CFG,
+ ®_val);
+ if (ret)
+ return ret;
+ *val = (reg_val & mask) >> shift;
+ return IIO_VAL_INT;
+ }
+ } else if (chan->type == IIO_VOLTAGE) {
+ offset = sysmon_supply_thresh_offset(chan->address, dir);
+ if (offset < 0)
+ return offset;
+ ret = regmap_read(sysmon->regmap, offset, ®_val);
+ if (ret)
+ return ret;
+ sysmon_supply_rawtoprocessed(reg_val, val);
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int sysmon_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int val, int val2)
+{
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned int reg_val;
+ u32 mask, shift;
+ u32 raw_val;
+ int offset;
+ int ret;
+
+ guard(mutex)(&sysmon->lock);
+
+ if (chan->type == IIO_TEMP) {
+ if (info == IIO_EV_INFO_VALUE) {
+ offset = sysmon_temp_thresh_offset(chan->address, dir);
+ if (offset < 0)
+ return offset;
+ sysmon_millicelsius_to_q8p7(&raw_val, val);
+ return regmap_write(sysmon->regmap, offset, raw_val);
+ }
+ if (info == IIO_EV_INFO_HYSTERESIS) {
+ mask = (chan->address == SYSMON_ADDR_OT_EVENT) ?
+ SYSMON_OT_HYST_BIT : SYSMON_TEMP_HYST_BIT;
+ shift = (chan->address == SYSMON_ADDR_OT_EVENT) ? 0 : 1;
+ if (val & ~1)
+ return -EINVAL;
+ return regmap_update_bits(sysmon->regmap,
+ SYSMON_TEMP_EV_CFG,
+ mask, val << shift);
+ }
+ } else if (chan->type == IIO_VOLTAGE) {
+ offset = sysmon_supply_thresh_offset(chan->address, dir);
+ if (offset < 0)
+ return offset;
+ ret = regmap_read(sysmon->regmap, offset, ®_val);
+ if (ret)
+ return ret;
+ sysmon_supply_processedtoraw(val, reg_val, &raw_val);
+ return regmap_write(sysmon->regmap, offset, raw_val);
+ }
+
+ return -EINVAL;
+}
+
static int sysmon_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
char *label)
@@ -116,8 +451,182 @@ static int sysmon_read_label(struct iio_dev *indio_dev,
static const struct iio_info sysmon_iio_info = {
.read_raw = sysmon_read_raw,
.read_label = sysmon_read_label,
+ .read_event_config = sysmon_read_event_config,
+ .write_event_config = sysmon_write_event_config,
+ .read_event_value = sysmon_read_event_value,
+ .write_event_value = sysmon_write_event_value,
};
+static void sysmon_push_event(struct iio_dev *indio_dev, u32 address)
+{
+ const struct iio_chan_spec *chan;
+ unsigned int i;
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ if (indio_dev->channels[i].address != address)
+ continue;
+
+ chan = &indio_dev->channels[i];
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(chan->type,
+ chan->channel,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns(indio_dev));
+ }
+}
+
+static void sysmon_handle_event(struct iio_dev *indio_dev, u32 event)
+{
+ u32 alarm_flag_offset = SYSMON_ALARM_FLAG + (event * SYSMON_REG_STRIDE);
+ u32 alarm_reg_offset = SYSMON_ALARM_REG + (event * SYSMON_REG_STRIDE);
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ unsigned long alarm_flag_reg;
+ unsigned int reg_val;
+ u32 address, bit;
+
+ switch (event) {
+ case SYSMON_BIT_TEMP:
+ sysmon_push_event(indio_dev, SYSMON_ADDR_TEMP_EVENT);
+ regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_TEMP));
+ sysmon->masked_temp |= BIT(SYSMON_BIT_TEMP);
+ break;
+
+ case SYSMON_BIT_OT:
+ sysmon_push_event(indio_dev, SYSMON_ADDR_OT_EVENT);
+ regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_OT));
+ sysmon->masked_temp |= BIT(SYSMON_BIT_OT);
+ break;
+
+ case SYSMON_BIT_ALARM0:
+ case SYSMON_BIT_ALARM1:
+ case SYSMON_BIT_ALARM2:
+ case SYSMON_BIT_ALARM3:
+ case SYSMON_BIT_ALARM4:
+ regmap_read(sysmon->regmap, alarm_flag_offset, ®_val);
+ alarm_flag_reg = (unsigned long)reg_val;
+
+ for_each_set_bit(bit, &alarm_flag_reg,
+ SYSMON_ALARM_BITS_PER_REG) {
+ address = bit + (SYSMON_ALARM_BITS_PER_REG * event);
+ sysmon_push_event(indio_dev, address);
+ regmap_update_bits(sysmon->regmap, alarm_reg_offset,
+ BIT(bit), 0);
+ }
+ regmap_write(sysmon->regmap, alarm_flag_offset, alarm_flag_reg);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void sysmon_handle_events(struct iio_dev *indio_dev,
+ unsigned long events)
+{
+ unsigned int bit;
+
+ for_each_set_bit(bit, &events, SYSMON_NO_OF_EVENTS)
+ sysmon_handle_event(indio_dev, bit);
+}
+
+static void sysmon_unmask_temp(struct sysmon *sysmon, unsigned int isr)
+{
+ unsigned int unmask, status;
+
+ status = isr & SYSMON_TEMP_INTR_MASK;
+
+ unmask = (sysmon->masked_temp ^ status) & sysmon->masked_temp;
+ sysmon->masked_temp &= status;
+
+ unmask &= ~sysmon->temp_mask;
+
+ regmap_write(sysmon->regmap, SYSMON_IER, unmask);
+}
+
+/*
+ * Versal threshold interrupts are level-sensitive. Active threshold
+ * interrupts are masked in the handler and polled via delayed work
+ * until the condition clears, then unmasked.
+ */
+static void sysmon_unmask_worker(struct work_struct *work)
+{
+ struct sysmon *sysmon = container_of(work, struct sysmon,
+ sysmon_unmask_work.work);
+ unsigned int isr;
+
+ spin_lock_irq(&sysmon->irq_lock);
+ regmap_read(sysmon->regmap, SYSMON_ISR, &isr);
+ regmap_write(sysmon->regmap, SYSMON_ISR, isr);
+ sysmon_unmask_temp(sysmon, isr);
+ spin_unlock_irq(&sysmon->irq_lock);
+
+ if (sysmon->masked_temp)
+ schedule_delayed_work(&sysmon->sysmon_unmask_work,
+ msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS));
+ else
+ regmap_write(sysmon->regmap, SYSMON_STATUS_RESET, 1);
+}
+
+static irqreturn_t sysmon_iio_irq(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct sysmon *sysmon;
+ unsigned int isr, imr;
+
+ sysmon = iio_priv(indio_dev);
+ spin_lock(&sysmon->irq_lock);
+
+ regmap_read(sysmon->regmap, SYSMON_ISR, &isr);
+ regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
+
+ isr &= ~imr;
+ regmap_write(sysmon->regmap, SYSMON_ISR, isr);
+
+ if (isr) {
+ sysmon_handle_events(indio_dev, isr);
+ schedule_delayed_work(&sysmon->sysmon_unmask_work,
+ msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS));
+ }
+
+ spin_unlock(&sysmon->irq_lock);
+
+ return isr ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void sysmon_cancel_work(void *data)
+{
+ struct sysmon *sysmon = data;
+
+ cancel_delayed_work_sync(&sysmon->sysmon_unmask_work);
+}
+
+static int sysmon_init_interrupt(struct sysmon *sysmon)
+{
+ unsigned int imr;
+ int ret;
+
+ /* Events not supported without IRQ (e.g. I2C path) */
+ if (sysmon->irq <= 0)
+ return 0;
+
+ INIT_DELAYED_WORK(&sysmon->sysmon_unmask_work, sysmon_unmask_worker);
+
+ ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
+ if (ret)
+ return ret;
+ sysmon->temp_mask = imr & SYSMON_TEMP_INTR_MASK;
+
+ ret = devm_add_action_or_reset(sysmon->dev, sysmon_cancel_work,
+ sysmon);
+ if (ret)
+ return ret;
+
+ return devm_request_irq(sysmon->dev, sysmon->irq,
+ sysmon_iio_irq, 0, "sysmon-irq",
+ sysmon->indio_dev);
+}
+
/**
* sysmon_parse_fw() - Parse firmware nodes and configure IIO channels.
* @indio_dev: IIO device instance
@@ -125,7 +634,13 @@ static const struct iio_info sysmon_iio_info = {
*
* Reads supply-channels and temperature-channels container nodes from
* firmware and builds the IIO channel array. Static temperature channels
- * are prepended, followed by supply and satellite channels from DT.
+ * and event channels are prepended, followed by supply and satellite
+ * channels from DT.
+ *
+ * Event channels and per-channel event specs are only added when the
+ * device has an IRQ (irq > 0). I2C devices have no interrupt line,
+ * and the I2C regmap cannot be called from atomic context, so events
+ * are not supported on that path.
*
* Return: 0 on success, negative errno on failure.
*/
@@ -133,8 +648,11 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
{
unsigned int idx, temp_chan_idx, volt_chan_idx;
struct fwnode_handle *supply_node, *temp_node;
+ struct sysmon *sysmon = iio_priv(indio_dev);
unsigned int num_supply = 0, num_temp = 0;
struct iio_chan_spec *sysmon_channels;
+ bool has_events = sysmon->irq > 0;
+ unsigned int num_events;
const char *label;
u32 reg;
int ret;
@@ -147,8 +665,11 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
if (temp_node)
num_temp = fwnode_get_child_node_count(temp_node);
+ num_events = has_events ? ARRAY_SIZE(temp_event_channels) : 0;
+
sysmon_channels = devm_kcalloc(dev,
ARRAY_SIZE(temp_channels) +
+ num_events +
num_supply + num_temp,
sizeof(*sysmon_channels), GFP_KERNEL);
if (!sysmon_channels) {
@@ -161,6 +682,13 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
memcpy(sysmon_channels, temp_channels, sizeof(temp_channels));
idx += ARRAY_SIZE(temp_channels);
+ /* Temperature event channels (only when IRQ is available) */
+ if (has_events) {
+ memcpy(sysmon_channels + idx, temp_event_channels,
+ sizeof(temp_event_channels));
+ idx += ARRAY_SIZE(temp_event_channels);
+ }
+
/* Supply channels from DT */
if (supply_node) {
fwnode_for_each_child_node_scoped(supply_node, child) {
@@ -187,6 +715,10 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_PROCESSED),
+ .event_spec = has_events ?
+ sysmon_supply_events : NULL,
+ .num_event_specs = has_events ?
+ ARRAY_SIZE(sysmon_supply_events) : 0,
.scan_type = {
.realbits = 19,
.storagebits = 32,
@@ -295,6 +827,7 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq)
ret = devm_mutex_init(dev, &sysmon->lock);
if (ret)
return ret;
+ spin_lock_init(&sysmon->irq_lock);
/* Disable all interrupts and clear pending status */
ret = regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK);
@@ -311,6 +844,10 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq)
if (ret)
return ret;
+ ret = sysmon_init_interrupt(sysmon);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_GPL(sysmon_core_probe);
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
index fc4d2338328..4f20173de77 100644
--- a/drivers/iio/adc/versal-sysmon.h
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -12,16 +12,30 @@
#include <linux/iio/iio.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
/* Register offsets (sorted by address) */
#define SYSMON_NPI_LOCK 0x000C
#define SYSMON_ISR 0x0044
+#define SYSMON_IMR 0x0048
+#define SYSMON_IER 0x004C
#define SYSMON_IDR 0x0050
+#define SYSMON_ALARM_FLAG 0x1018
#define SYSMON_TEMP_MAX 0x1030
#define SYSMON_TEMP_MIN 0x1034
#define SYSMON_SUPPLY_BASE 0x1040
+#define SYSMON_ALARM_REG 0x1940
+#define SYSMON_TEMP_TH_LOW 0x1970
+#define SYSMON_TEMP_TH_UP 0x1974
+#define SYSMON_OT_TH_LOW 0x1978
+#define SYSMON_OT_TH_UP 0x197C
+#define SYSMON_SUPPLY_TH_LOW 0x1980
+#define SYSMON_SUPPLY_TH_UP 0x1C80
+#define SYSMON_TEMP_EV_CFG 0x1F84
#define SYSMON_TEMP_MIN_MIN 0x1F8C
#define SYSMON_TEMP_MAX_MAX 0x1F90
+#define SYSMON_STATUS_RESET 0x1F94
#define SYSMON_TEMP_SAT_BASE 0x1FAC
#define SYSMON_MAX_REG 0x24C0
@@ -33,8 +47,12 @@
#define SYSMON_SUPPLY_IDX_MAX 159
#define SYSMON_TEMP_SAT_MAX 64
+#define SYSMON_NO_OF_EVENTS 32
#define SYSMON_INTR_ALL_MASK GENMASK(31, 0)
+/* ISR/IMR temperature and OT alarm mask (bits 9:8) */
+#define SYSMON_TEMP_INTR_MASK GENMASK(9, 8)
+
/* Supply voltage conversion register fields */
#define SYSMON_MANTISSA_MASK GENMASK(15, 0)
#define SYSMON_FMT_MASK BIT(16)
@@ -47,13 +65,26 @@
/* Signed milli scale (MILLI from linux/units.h is unsigned long) */
#define SYSMON_MILLI 1000
+/* Event address IDs for temp event channels */
+#define SYSMON_ADDR_TEMP_EVENT 160
+#define SYSMON_ADDR_OT_EVENT 161
+
+/* Bits per alarm register */
+#define SYSMON_ALARM_BITS_PER_REG 32
+
+#define SYSMON_UNMASK_WORK_DELAY_MS 500
+
/**
* struct sysmon - Driver data for Versal SysMon
* @dev: pointer to device struct
* @indio_dev: pointer to the iio device (needed for work callbacks)
* @regmap: register map for hardware access
* @lock: mutex for serializing user-space access
+ * @irq_lock: spinlock for interrupt register access
* @irq: interrupt number
+ * @masked_temp: currently masked temperature alarm bits
+ * @temp_mask: temperature interrupt configuration mask
+ * @sysmon_unmask_work: re-enables events after alarm condition clears
*/
struct sysmon {
struct device *dev;
@@ -61,7 +92,12 @@ struct sysmon {
struct regmap *regmap;
/* Serializes access to device registers and state */
struct mutex lock;
+ /* Protects interrupt mask register updates */
+ spinlock_t irq_lock;
int irq;
+ unsigned int masked_temp;
+ unsigned int temp_mask;
+ struct delayed_work sysmon_unmask_work;
};
int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq);
--
2.48.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 5/5] iio: adc: versal-sysmon: add oversampling support
2026-05-02 11:19 [PATCH v2 0/5] iio: adc: add AMD/Xilinx Versal SysMon driver Salih Erim
` (3 preceding siblings ...)
2026-05-02 11:19 ` [PATCH v2 4/5] iio: adc: versal-sysmon: add threshold event support Salih Erim
@ 2026-05-02 11:19 ` Salih Erim
4 siblings, 0 replies; 6+ messages in thread
From: Salih Erim @ 2026-05-02 11:19 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, git
Cc: nuno.sa, andy, dlechner, michal.simek, conall.ogriofa, erimsalih,
linux-iio, devicetree, linux-kernel, Salih Erim
Add configurable oversampling ratio for supply voltage and temperature
satellite channels. The hardware supports sample counts of 1 (no
averaging), 2, 4, 8, and 16. The userspace-facing values represent
actual sample counts; the driver translates to hardware register
encoding internally.
Oversampling is shared by type: all supply channels share one ratio
and all temperature satellite channels share another. Static
temperature channels (device max/min/max_max/min_min) are hardware-
computed aggregates and do not participate in oversampling.
When oversampling is changed, the driver updates the per-channel
EN_AVG register bitmasks so that all channels in each bank have
hardware averaging enabled or disabled to match the oversampling
configuration.
Register write errors in the per-channel EN_AVG update path are
propagated to userspace.
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v2:
- EN_AVG per-channel bitmask registers written with all-ones
instead of boolean 1 when oversampling is enabled
- EN_AVG write errors propagated to userspace
- Oversampling limited to satellite temp and supply channels;
static temp channels do not participate
- Oversampling exposes actual sample counts (1,2,4,8,16) to
userspace with internal HW register translation
- write_raw_get_fmt returns IIO_VAL_INT for oversampling ratio
- HW encoding documented (sample_count/2, not log2)
- oversampling_avail is const int[] (type match fix)
drivers/iio/adc/versal-sysmon-core.c | 137 +++++++++++++++++++++++++++
drivers/iio/adc/versal-sysmon.h | 17 ++++
2 files changed, 154 insertions(+)
diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
index 857fe21db7a..9c3ce8f8fdf 100644
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -19,6 +19,12 @@
#include "versal-sysmon.h"
+/*
+ * Oversampling ratio values exposed to userspace via IIO.
+ * Actual number of samples averaged: 1=none, 2=2x, 4=4x, 8=8x, 16=16x.
+ */
+static const int sysmon_oversampling_avail[] = { 1, 2, 4, 8, 16 };
+
/* OT and TEMP hysteresis bit positions in SYSMON_TEMP_EV_CFG */
#define SYSMON_OT_HYST_BIT BIT(0)
#define SYSMON_TEMP_HYST_BIT BIT(1)
@@ -202,6 +208,12 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
unsigned int regval;
int ret;
+ if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) {
+ *val = (chan->type == IIO_TEMP) ? sysmon->temp_oversampling :
+ sysmon->supply_oversampling;
+ return IIO_VAL_INT;
+ }
+
if (mask != IIO_CHAN_INFO_RAW && mask != IIO_CHAN_INFO_PROCESSED)
return -EINVAL;
@@ -438,6 +450,118 @@ static int sysmon_write_event_value(struct iio_dev *indio_dev,
return -EINVAL;
}
+static int sysmon_set_avg_enable(struct sysmon *sysmon,
+ u32 base, u32 count, u32 val)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < count; i++) {
+ ret = regmap_write(sysmon->regmap,
+ base + (i * SYSMON_REG_STRIDE), val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sysmon_osr_write(struct sysmon *sysmon, int channel_type, int val)
+{
+ /*
+ * HW register encoding is sample_count / 2:
+ * 0=none, 1=2x, 2=4x, 4=8x, 8=16x (not log2-based).
+ */
+ int hw_val = val >> 1;
+ int ret;
+
+ if (channel_type == IIO_TEMP) {
+ ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG,
+ SYSMON_TEMP_SAT_CONFIG_MASK,
+ FIELD_PREP(SYSMON_TEMP_SAT_CONFIG_MASK,
+ hw_val));
+ if (ret)
+ return ret;
+ ret = sysmon_set_avg_enable(sysmon, SYSMON_TEMP_EN_AVG_BASE,
+ SYSMON_TEMP_EN_AVG_COUNT,
+ hw_val ? ~0U : 0);
+ if (ret)
+ return ret;
+ } else if (channel_type == IIO_VOLTAGE) {
+ ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG,
+ SYSMON_SUPPLY_CONFIG_MASK,
+ FIELD_PREP(SYSMON_SUPPLY_CONFIG_MASK,
+ hw_val));
+ if (ret)
+ return ret;
+ ret = sysmon_set_avg_enable(sysmon, SYSMON_SUPPLY_EN_AVG_BASE,
+ SYSMON_SUPPLY_EN_AVG_COUNT,
+ hw_val ? ~0U : 0);
+ if (ret)
+ return ret;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sysmon_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct sysmon *sysmon = iio_priv(indio_dev);
+ int i, ret;
+
+ if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(sysmon_oversampling_avail); i++) {
+ if (val == sysmon_oversampling_avail[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(sysmon_oversampling_avail))
+ return -EINVAL;
+
+ guard(mutex)(&sysmon->lock);
+
+ ret = sysmon_osr_write(sysmon, chan->type, val);
+ if (ret)
+ return ret;
+
+ if (chan->type == IIO_TEMP)
+ sysmon->temp_oversampling = val;
+ else
+ sysmon->supply_oversampling = val;
+
+ return 0;
+}
+
+static int sysmon_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+ return IIO_VAL_INT;
+
+ return -EINVAL;
+}
+
+static int sysmon_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+ return -EINVAL;
+
+ *vals = sysmon_oversampling_avail;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(sysmon_oversampling_avail);
+
+ return IIO_AVAIL_LIST;
+}
+
static int sysmon_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
char *label)
@@ -450,6 +574,9 @@ static int sysmon_read_label(struct iio_dev *indio_dev,
static const struct iio_info sysmon_iio_info = {
.read_raw = sysmon_read_raw,
+ .write_raw = sysmon_write_raw,
+ .write_raw_get_fmt = sysmon_write_raw_get_fmt,
+ .read_avail = sysmon_read_avail,
.read_label = sysmon_read_label,
.read_event_config = sysmon_read_event_config,
.write_event_config = sysmon_write_event_config,
@@ -715,6 +842,10 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_type =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_shared_by_type_available =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.event_spec = has_events ?
sysmon_supply_events : NULL,
.num_event_specs = has_events ?
@@ -760,6 +891,10 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_type =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_shared_by_type_available =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.scan_type = {
.sign = 's',
.realbits = 15,
@@ -823,6 +958,8 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq)
sysmon->indio_dev = indio_dev;
sysmon->regmap = regmap;
sysmon->irq = irq;
+ sysmon->temp_oversampling = 1;
+ sysmon->supply_oversampling = 1;
ret = devm_mutex_init(dev, &sysmon->lock);
if (ret)
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
index 4f20173de77..9fa7a7486de 100644
--- a/drivers/iio/adc/versal-sysmon.h
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -21,11 +21,13 @@
#define SYSMON_IMR 0x0048
#define SYSMON_IER 0x004C
#define SYSMON_IDR 0x0050
+#define SYSMON_CONFIG 0x0100
#define SYSMON_ALARM_FLAG 0x1018
#define SYSMON_TEMP_MAX 0x1030
#define SYSMON_TEMP_MIN 0x1034
#define SYSMON_SUPPLY_BASE 0x1040
#define SYSMON_ALARM_REG 0x1940
+#define SYSMON_SUPPLY_EN_AVG_BASE 0x1958
#define SYSMON_TEMP_TH_LOW 0x1970
#define SYSMON_TEMP_TH_UP 0x1974
#define SYSMON_OT_TH_LOW 0x1978
@@ -37,6 +39,7 @@
#define SYSMON_TEMP_MAX_MAX 0x1F90
#define SYSMON_STATUS_RESET 0x1F94
#define SYSMON_TEMP_SAT_BASE 0x1FAC
+#define SYSMON_TEMP_EN_AVG_BASE 0x24B4
#define SYSMON_MAX_REG 0x24C0
/* NPI unlock value written to SYSMON_NPI_LOCK */
@@ -53,6 +56,16 @@
/* ISR/IMR temperature and OT alarm mask (bits 9:8) */
#define SYSMON_TEMP_INTR_MASK GENMASK(9, 8)
+/* Config register: supply oversampling field (bits 17:14) */
+#define SYSMON_SUPPLY_CONFIG_MASK GENMASK(17, 14)
+
+/* Config register: temp satellite oversampling field (bits 27:24) */
+#define SYSMON_TEMP_SAT_CONFIG_MASK GENMASK(27, 24)
+
+/* Per-channel averaging enable register counts */
+#define SYSMON_SUPPLY_EN_AVG_COUNT 5
+#define SYSMON_TEMP_EN_AVG_COUNT 2
+
/* Supply voltage conversion register fields */
#define SYSMON_MANTISSA_MASK GENMASK(15, 0)
#define SYSMON_FMT_MASK BIT(16)
@@ -85,6 +98,8 @@
* @masked_temp: currently masked temperature alarm bits
* @temp_mask: temperature interrupt configuration mask
* @sysmon_unmask_work: re-enables events after alarm condition clears
+ * @temp_oversampling: current temp oversampling ratio
+ * @supply_oversampling: current supply oversampling ratio
*/
struct sysmon {
struct device *dev;
@@ -98,6 +113,8 @@ struct sysmon {
unsigned int masked_temp;
unsigned int temp_mask;
struct delayed_work sysmon_unmask_work;
+ unsigned int temp_oversampling;
+ unsigned int supply_oversampling;
};
int sysmon_core_probe(struct device *dev, struct regmap *regmap, int irq);
--
2.48.1
^ permalink raw reply related [flat|nested] 6+ messages in thread