mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com>
To: "Jonathan Cameron" <jic23@kernel.org>,
	"Michal Simek" <michal.simek@amd.com>,
	"Sai Krishna Potthuri" <sai.krishna.potthuri@amd.com>,
	"David Lechner" <dlechner@baylibre.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>
Cc: <linux-iio@vger.kernel.org>, <devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-kernel@vger.kernel.org>, <amitrkcian2002@gmail.com>,
	Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com>
Subject: [PATCH 4/6] iio: adc: versal-sysmon: add over-temperature alarm events
Date: Mon, 2 Nov 2026 17:51:10 +0530	[thread overview]
Message-ID: <20261102122112.1723130-5-amit.kumar-mahapatra@amd.com> (raw)
In-Reply-To: <20261102122112.1723130-1-amit.kumar-mahapatra@amd.com>

The SysMon has two independent temperature comparators. The driver
exposes only DEVICE_TEMP on interrupt bit 9; the over-temperature alarm
on bit 8, with its own threshold pair at OT_TEMP_TH_LOWER and
OT_TEMP_TH_UPPER, is not reported to userspace at all.

The two are structurally identical: an upper threshold, a lower
threshold acting as the hysteresis floor, and a hysteresis mode bit in
ALARM_CONFIG, bit 0 for OT and bit 1 for DEVICE_TEMP. Per AM006 the
over-temperature shutdown response is a PMC function configured at
design time, so the kernel's role here is threshold configuration and
notification rather than acting on the alarm.

IIO identifies an event by channel, type and direction, and both
comparators are rising thresholds on the same aggregate temperature, so
they cannot share a channel. Add an event-only channel labelled "ot"
carrying the OT thresholds.

The channel is appended after the device tree derived channels because
io-channels phandle arguments are raw array positions, and placing it
earlier would silently repoint existing consumers. It is created only
when the device has an interrupt, as without one it would carry no
events.

Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com>
---
 drivers/iio/adc/versal-sysmon-core.c | 128 ++++++++++++++++++++++-----
 drivers/iio/adc/versal-sysmon.h      |   8 +-
 2 files changed, 113 insertions(+), 23 deletions(-)

diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
index d39aaba9a8b6..bb8737ac5638 100644
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -34,7 +34,8 @@
  */
 static const int sysmon_oversampling_avail[] = { 1, 2, 4, 8, 16 };
 
-/* TEMP hysteresis mode bit in SYSMON_TEMP_EV_CFG */
+/* Hysteresis mode bits in SYSMON_TEMP_EV_CFG (AM006 ALARM_CONFIG) */
+#define SYSMON_OT_HYST_MASK		BIT(0)
 #define SYSMON_TEMP_HYST_MASK		BIT(1)
 
 /* Compute alarm register offset from a channel address */
@@ -62,9 +63,41 @@ enum sysmon_alarm_bit {
 	SYSMON_BIT_ALARM2 = 2,
 	SYSMON_BIT_ALARM3 = 3,
 	SYSMON_BIT_ALARM4 = 4,
+	SYSMON_BIT_OT = 8,
 	SYSMON_BIT_TEMP = 9,
 };
 
+/*
+ * The two temperature comparators are identical in layout: an upper
+ * threshold, a lower threshold acting as the hysteresis floor, and a
+ * hysteresis cached in millicelsius because the hardware only stores the
+ * two thresholds. DEVICE_TEMP drives bit 9, the over-temperature alarm
+ * bit 8.
+ */
+struct sysmon_comparator {
+	unsigned int th_up;
+	unsigned int th_low;
+	int *hysteresis;
+};
+
+static bool sysmon_chan_is_ot(const struct iio_chan_spec *chan)
+{
+	return chan->address == SYSMON_OT_TH_UP;
+}
+
+static struct sysmon_comparator sysmon_get_comparator(struct sysmon *sysmon,
+						      const struct iio_chan_spec *chan)
+{
+	if (sysmon_chan_is_ot(chan))
+		return (struct sysmon_comparator){
+			SYSMON_OT_TH_UP, SYSMON_OT_TH_LOW, &sysmon->ot_hysteresis
+		};
+
+	return (struct sysmon_comparator){
+		SYSMON_TEMP_TH_UP, SYSMON_TEMP_TH_LOW, &sysmon->temp_hysteresis
+	};
+}
+
 /* Temperature event specification: rising threshold + hysteresis only */
 static const struct iio_event_spec sysmon_temp_events[] = {
 	{
@@ -221,7 +254,8 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
 static u32 sysmon_get_event_mask(const struct iio_chan_spec *chan)
 {
 	if (chan->type == IIO_TEMP)
-		return BIT(SYSMON_BIT_TEMP);
+		return sysmon_chan_is_ot(chan) ? BIT(SYSMON_BIT_OT) :
+						 BIT(SYSMON_BIT_TEMP);
 
 	return BIT(chan->address / SYSMON_ALARM_BITS_PER_REG);
 }
@@ -340,23 +374,24 @@ static int sysmon_write_event_config(struct iio_dev *indio_dev,
  * cached hysteresis. Called when either upper threshold or hysteresis
  * is written.
  */
-static int sysmon_update_temp_lower(struct sysmon *sysmon)
+static int sysmon_update_temp_lower(struct sysmon *sysmon,
+				    const struct sysmon_comparator *cmp)
 {
 	unsigned int upper_reg;
 	int upper_mc, lower_mc;
 	u32 raw_val;
 	int ret;
 
-	ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, &upper_reg);
+	ret = regmap_read(sysmon->regmap, cmp->th_up, &upper_reg);
 	if (ret)
 		return ret;
 
 	sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc);
-	lower_mc = clamp(upper_mc - sysmon->temp_hysteresis,
+	lower_mc = clamp(upper_mc - *cmp->hysteresis,
 			 SYSMON_TEMP_MIN_MC, SYSMON_TEMP_MAX_MC);
 	sysmon_millicelsius_to_q8p7(&raw_val, lower_mc);
 
-	return regmap_write(sysmon->regmap, SYSMON_TEMP_TH_LOW, raw_val);
+	return regmap_write(sysmon->regmap, cmp->th_low, raw_val);
 }
 
 static int sysmon_read_event_value(struct iio_dev *indio_dev,
@@ -374,10 +409,12 @@ static int sysmon_read_event_value(struct iio_dev *indio_dev,
 	guard(mutex)(&sysmon->lock);
 
 	switch (chan->type) {
-	case IIO_TEMP:
+	case IIO_TEMP: {
+		struct sysmon_comparator cmp = sysmon_get_comparator(sysmon, chan);
+
 		switch (info) {
 		case IIO_EV_INFO_VALUE:
-			ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, &reg_val);
+			ret = regmap_read(sysmon->regmap, cmp.th_up, &reg_val);
 			if (ret)
 				return ret;
 
@@ -386,12 +423,13 @@ static int sysmon_read_event_value(struct iio_dev *indio_dev,
 			return IIO_VAL_INT;
 
 		case IIO_EV_INFO_HYSTERESIS:
-			*val = sysmon->temp_hysteresis;
+			*val = *cmp.hysteresis;
 			return IIO_VAL_INT;
 
 		default:
 			return -EINVAL;
 		}
+	}
 
 	case IIO_VOLTAGE:
 		offset = sysmon_supply_thresh_offset(chan->address, dir);
@@ -427,7 +465,9 @@ static int sysmon_write_event_value(struct iio_dev *indio_dev,
 	guard(mutex)(&sysmon->lock);
 
 	switch (chan->type) {
-	case IIO_TEMP:
+	case IIO_TEMP: {
+		struct sysmon_comparator cmp = sysmon_get_comparator(sysmon, chan);
+
 		switch (info) {
 		case IIO_EV_INFO_VALUE:
 			if (val < SYSMON_TEMP_MIN_MC || val > SYSMON_TEMP_MAX_MC)
@@ -435,24 +475,25 @@ static int sysmon_write_event_value(struct iio_dev *indio_dev,
 
 			sysmon_millicelsius_to_q8p7(&raw_val, val);
 
-			ret = regmap_write(sysmon->regmap, SYSMON_TEMP_TH_UP, raw_val);
+			ret = regmap_write(sysmon->regmap, cmp.th_up, raw_val);
 			if (ret)
 				return ret;
 
 			/* Recompute lower = upper - hysteresis */
-			return sysmon_update_temp_lower(sysmon);
+			return sysmon_update_temp_lower(sysmon, &cmp);
 
 		case IIO_EV_INFO_HYSTERESIS:
 			if (val < 0 || val > SYSMON_TEMP_MAX_MC - SYSMON_TEMP_MIN_MC)
 				return -EINVAL;
 
-			sysmon->temp_hysteresis = val;
+			*cmp.hysteresis = val;
 
-			return sysmon_update_temp_lower(sysmon);
+			return sysmon_update_temp_lower(sysmon, &cmp);
 
 		default:
 			return -EINVAL;
 		}
+	}
 
 	case IIO_VOLTAGE:
 		offset = sysmon_supply_thresh_offset(chan->address, dir);
@@ -672,6 +713,16 @@ static int sysmon_handle_event(struct iio_dev *indio_dev, u32 event)
 		sysmon->masked_temp |= BIT(SYSMON_BIT_TEMP);
 		return 0;
 
+	case SYSMON_BIT_OT:
+		sysmon_push_event(indio_dev, SYSMON_OT_TH_UP);
+
+		ret = regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_OT));
+		if (ret)
+			return ret;
+
+		sysmon->masked_temp |= BIT(SYSMON_BIT_OT);
+		return 0;
+
 	case SYSMON_BIT_ALARM0:
 	case SYSMON_BIT_ALARM1:
 	case SYSMON_BIT_ALARM2:
@@ -822,23 +873,24 @@ static int sysmon_init_interrupt(struct sysmon *sysmon,
  * Initialize the cached hysteresis for a temperature channel from the
  * current hardware threshold registers: hysteresis = upper - lower.
  */
-static int sysmon_init_hysteresis(struct sysmon *sysmon, int *hysteresis)
+static int sysmon_init_hysteresis(struct sysmon *sysmon,
+				  const struct sysmon_comparator *cmp)
 {
 	unsigned int upper_reg, lower_reg;
 	int upper_mc, lower_mc;
 	int ret;
 
-	ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, &upper_reg);
+	ret = regmap_read(sysmon->regmap, cmp->th_up, &upper_reg);
 	if (ret)
 		return ret;
 
-	ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_LOW, &lower_reg);
+	ret = regmap_read(sysmon->regmap, cmp->th_low, &lower_reg);
 	if (ret)
 		return ret;
 
 	sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc);
 	sysmon_q8p7_to_millicelsius(lower_reg, &lower_mc);
-	*hysteresis = max(upper_mc - lower_mc, 0);
+	*cmp->hysteresis = max(upper_mc - lower_mc, 0);
 
 	return 0;
 }
@@ -880,6 +932,9 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev, int ir
 
 	num_static = ARRAY_SIZE(temp_channels);
 	num_chan = size_add(num_temp, size_add(num_static, num_supply));
+	/* The over-temperature channel exists only when events do */
+	if (irq > 0)
+		num_chan = size_add(num_chan, 1);
 	sysmon_channels = devm_kcalloc(dev, num_chan, sizeof(*sysmon_channels), GFP_KERNEL);
 	if (!sysmon_channels)
 		return -ENOMEM;
@@ -960,6 +1015,25 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev, int ir
 		};
 	}
 
+	/*
+	 * Over-temperature alarm. The OT comparator watches the same
+	 * aggregate temperature as channel 0 but against its own threshold
+	 * pair, and IIO cannot express two rising thresholds on one channel.
+	 * It has no reading of its own, so only event attributes and a label
+	 * are exposed. Appended last, because io-channels phandle arguments
+	 * are raw array positions and inserting it earlier would silently
+	 * repoint every existing consumer.
+	 */
+	if (irq > 0)
+		sysmon_channels[idx++] = (struct iio_chan_spec) {
+			.type = IIO_TEMP,
+			.indexed = 1,
+			.address = SYSMON_OT_TH_UP,
+			.event_spec = sysmon_temp_events,
+			.num_event_specs = ARRAY_SIZE(sysmon_temp_events),
+			.datasheet_name = "ot",
+		};
+
 	indio_dev->num_channels = idx;
 	indio_dev->info = &sysmon_iio_info;
 
@@ -1032,14 +1106,26 @@ int devm_versal_sysmon_core_probe(struct device *dev, struct regmap *regmap)
 		return ret;
 
 	if (irq > 0) {
-		/* Set hysteresis mode for temperature threshold */
+		struct sysmon_comparator cmp;
+
+		/* Set hysteresis mode for both temperature comparators */
 		ret = regmap_set_bits(sysmon->regmap, SYSMON_TEMP_EV_CFG,
-				      SYSMON_TEMP_HYST_MASK);
+				      SYSMON_TEMP_HYST_MASK | SYSMON_OT_HYST_MASK);
 		if (ret)
 			return ret;
 
 		/* Initialize cached hysteresis from hardware registers */
-		ret = sysmon_init_hysteresis(sysmon, &sysmon->temp_hysteresis);
+		cmp = (struct sysmon_comparator){
+			SYSMON_TEMP_TH_UP, SYSMON_TEMP_TH_LOW, &sysmon->temp_hysteresis
+		};
+		ret = sysmon_init_hysteresis(sysmon, &cmp);
+		if (ret)
+			return ret;
+
+		cmp = (struct sysmon_comparator){
+			SYSMON_OT_TH_UP, SYSMON_OT_TH_LOW, &sysmon->ot_hysteresis
+		};
+		ret = sysmon_init_hysteresis(sysmon, &cmp);
 		if (ret)
 			return ret;
 
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
index a21dacc13471..d0a9bb1293c7 100644
--- a/drivers/iio/adc/versal-sysmon.h
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -32,6 +32,8 @@ struct regmap;
 #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
+#define SYSMON_OT_TH_UP			0x197C
 #define SYSMON_SUPPLY_TH_LOW		0x1980
 #define SYSMON_SUPPLY_TH_UP		0x1C80
 #define SYSMON_TEMP_EV_CFG		0x1F84
@@ -53,8 +55,8 @@ struct regmap;
 #define SYSMON_NO_OF_EVENTS		32
 #define SYSMON_INTR_ALL_MASK		GENMASK(31, 0)
 
-/* ISR/IMR temperature alarm mask (bit 9) */
-#define SYSMON_TEMP_INTR_MASK		BIT(9)
+/* ISR/IMR temperature alarm mask: OT (bit 8) and DEVICE_TEMP (bit 9) */
+#define SYSMON_TEMP_INTR_MASK		GENMASK(9, 8)
 
 /* SYSMON_CONFIG: supply oversampling ratio */
 #define SYSMON_CONFIG_SUPPLY_OSR	GENMASK(17, 14)
@@ -94,6 +96,7 @@ struct regmap;
  * @masked_temp: currently masked temperature alarm bits
  * @temp_mask: temperature interrupt configuration mask
  * @temp_hysteresis: cached DEVICE_TEMP hysteresis in millicelsius
+ * @ot_hysteresis: cached OT hysteresis in millicelsius
  * @sysmon_unmask_work: re-enables events after alarm condition clears
  * @temp_oversampling: current temp oversampling ratio
  * @supply_oversampling: current supply oversampling ratio
@@ -116,6 +119,7 @@ struct sysmon {
 	unsigned int masked_temp;
 	unsigned int temp_mask;
 	int temp_hysteresis;
+	int ot_hysteresis;
 	struct delayed_work sysmon_unmask_work;
 	unsigned int temp_oversampling;
 	unsigned int supply_oversampling;
-- 
2.34.1


  parent reply	other threads:[~2026-11-02 12:21 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 12:21 [PATCH 0/6] iio: adc: versal-sysmon: Add OT alarm, shutdown and thermal zone support Amit Kumar Mahapatra
2026-11-02 12:21 ` [PATCH 1/6] iio: adc: versal-sysmon: fix race in interrupt teardown Amit Kumar Mahapatra
2026-11-02 12:21 ` [PATCH 2/6] iio: adc: versal-sysmon: validate temperature hysteresis Amit Kumar Mahapatra
2026-11-02 12:21 ` [PATCH 3/6] iio: adc: versal-sysmon: disable interrupts on shutdown Amit Kumar Mahapatra
2026-11-02 12:21 ` Amit Kumar Mahapatra [this message]
2026-11-02 12:21 ` [PATCH 5/6] arm64: versal-net: Add SysMon and thermal zone Amit Kumar Mahapatra
2026-11-02 14:53   ` Michal Simek
2026-11-02 12:21 ` [PATCH 6/6] MAINTAINERS: update Versal SysMon maintainers Amit Kumar Mahapatra
2026-11-02 12:53   ` Michal Simek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261102122112.1723130-5-amit.kumar-mahapatra@amd.com \
    --to=amit.kumar-mahapatra@amd.com \
    --cc=amitrkcian2002@gmail.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=michal.simek@amd.com \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    --cc=sai.krishna.potthuri@amd.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®