Alpha arch development list
 help / color / mirror / Atom feed
* [PATCH 0/7] alpha: marvel: environmental sensors
@ 2026-11-14 17:39 Matt Turner
  2026-11-14 17:39 ` [PATCH 1/7] alpha: marvel: report environmental events without VERBOSE_MCHECK Matt Turner
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

The service processors of the Marvel AlphaServers (GS1280, ES80, ES47)
monitor temperatures, fans, voltages and power supplies. Linux can't read
these sensors, and the events the firmware raises when one changes state
are printed only with CONFIG_VERBOSE_MCHECK, and then decoded wrongly.

Patches 1 and 2 fix the event reporting. Patch 3 adds the SRM callback
that refreshes the readings of one config tree node, and patch 4
registers a platform device. Patch 5 is the hwmon driver, which polls.
Patches 6 and 7 make it reread the sensors and notify userspace when the
firmware reports an event.

The hwmon patches depend on the alpha ones, so I'd suggest the whole
series go through the alpha tree with an ack from Guenter.

Tested on an ES47, where sensors(1) shows the temperatures, fans,
voltages and power supply alarms. No environmental event fired during
testing, so patches 6 and 7 are only build tested.

Signed-off-by: Matt Turner <mattst88@gmail.com>
---
Matt Turner (7):
      alpha: marvel: report environmental events without VERBOSE_MCHECK
      alpha: marvel: fix the environmental subpacket layout
      alpha: marvel: refresh environmental readings through the console
      alpha: marvel: register a platform device for the environmental sensors
      hwmon: Add driver for Marvel AlphaServer environmental sensors
      alpha: marvel: notify environmental events
      hwmon: (marvel-env) Notify userspace of environmental events

 Documentation/hwmon/index.rst         |   1 +
 Documentation/hwmon/marvel-env.rst    |  64 ++++
 MAINTAINERS                           |   8 +
 arch/alpha/include/asm/console.h      |   2 +
 arch/alpha/include/asm/err_ev7.h      |  16 +-
 arch/alpha/include/asm/marvel_env.h   |  17 +
 arch/alpha/include/uapi/asm/console.h |   2 +
 arch/alpha/kernel/core_marvel.c       |  59 ++++
 arch/alpha/kernel/err_marvel.c        |  39 ++-
 arch/alpha/kernel/proto.h             |   1 +
 arch/alpha/kernel/srmcons.c           |   3 +-
 arch/alpha/lib/callback_srm.S         |   1 +
 drivers/hwmon/Kconfig                 |  12 +
 drivers/hwmon/Makefile                |   1 +
 drivers/hwmon/marvel-env.c            | 633 ++++++++++++++++++++++++++++++++++
 15 files changed, 840 insertions(+), 19 deletions(-)
---
base-commit: 0b4cadbe7c39d830dec35c4a2655483de0dae568
change-id: 20261114-marvel-env-d0289c736ba3

Best regards,
--  
Matt Turner <mattst88@gmail.com>


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/7] alpha: marvel: report environmental events without VERBOSE_MCHECK
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 2/7] alpha: marvel: fix the environmental subpacket layout Matt Turner
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

The service processors report temperature, fan, voltage and power supply
alarms as system event (0x680) logout frames. The frame is already
classified as reportable, but without CONFIG_VERBOSE_MCHECK only the
header line reaches the log, which does not say which sensor or what
happened. Print the subpackets unconditionally.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 arch/alpha/kernel/err_marvel.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/arch/alpha/kernel/err_marvel.c b/arch/alpha/kernel/err_marvel.c
index c0c0ccefc467..832567a3ccbb 100644
--- a/arch/alpha/kernel/err_marvel.c
+++ b/arch/alpha/kernel/err_marvel.c
@@ -24,7 +24,6 @@
 static void
 marvel_print_680_frame(struct ev7_lf_subpackets *lf_subpackets)
 {
-#ifdef CONFIG_VERBOSE_MCHECK
 	struct ev7_pal_environmental_subpacket *env;
 	struct { int type; char *name; } ev_packets[] = {
 		{ EL_TYPE__PAL__ENV__AMBIENT_TEMPERATURE,
@@ -62,7 +61,6 @@ marvel_print_680_frame(struct ev7_lf_subpackets *lf_subpackets)
 		       env->unit_id,
 		       env->condition);
 	}
-#endif /* CONFIG_VERBOSE_MCHECK */
 }
 
 static int

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/7] alpha: marvel: fix the environmental subpacket layout
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
  2026-11-14 17:39 ` [PATCH 1/7] alpha: marvel: report environmental events without VERBOSE_MCHECK Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 3/7] alpha: marvel: refresh environmental readings through the console Matt Turner
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

struct ev7_pal_environmental_subpacket does not match what the MBM and
CMM firmware (V2.7) put in system event frames, which is:

  +0   cabinet
  +2   drawer
  +8   PE (the power and environment node holding the sensor)
  +10  sensor index
  +14  state: 0 ok, 1 warning, 2 fault, 0xfffe unknown, 0xffff removed

So the byte printed as "Condition" was half of the sensor index, and the
state was never printed. Use the firmware's layout and print the state
along with the sensor's key. This matches the frames in the error log of
an ES47.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 arch/alpha/include/asm/err_ev7.h | 16 ++++++++++++----
 arch/alpha/kernel/err_marvel.c   | 28 +++++++++++++++++-----------
 2 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/arch/alpha/include/asm/err_ev7.h b/arch/alpha/include/asm/err_ev7.h
index ab17e6333764..5d30af0221fb 100644
--- a/arch/alpha/include/asm/err_ev7.h
+++ b/arch/alpha/include/asm/err_ev7.h
@@ -147,17 +147,25 @@ struct ev7_pal_io_subpacket {
  *	   class PAL (14), type POWER_SUPPLY (14)
  *	   class PAL (14), type LAN (15)
  *	   class PAL (14), type HOT_PLUG (16)
+ *
+ * Cabinet, drawer, PE and sensor are the keys of the sensor's GCT packet.
  */
 struct ev7_pal_environmental_subpacket {
 	u16 cabinet;
 	u16 drawer;
 	u16 reserved1[2];
-	u8 module_type;
-	u8 unit_id;		/* unit reporting condition */
-	u8 reserved2;
-	u8 condition;		/* condition reported       */
+	u16 pe;
+	u16 sensor;
+	u16 reserved2;
+	u16 state;		/* EV7_ENV_STATE_* */
 };
 
+#define EV7_ENV_STATE_OK	0
+#define EV7_ENV_STATE_WARNING	1
+#define EV7_ENV_STATE_FAULT	2
+#define EV7_ENV_STATE_UNKNOWN	0xfffe
+#define EV7_ENV_STATE_REMOVED	0xffff
+
 /*
  * Convert environmental type to index
  */
diff --git a/arch/alpha/kernel/err_marvel.c b/arch/alpha/kernel/err_marvel.c
index 832567a3ccbb..46c521cea9a0 100644
--- a/arch/alpha/kernel/err_marvel.c
+++ b/arch/alpha/kernel/err_marvel.c
@@ -21,6 +21,19 @@
 #include "err_impl.h"
 #include "proto.h"
 
+static const char *
+marvel_env_state(u16 state)
+{
+	switch (state) {
+	case EV7_ENV_STATE_OK:		return "ok";
+	case EV7_ENV_STATE_WARNING:	return "warning";
+	case EV7_ENV_STATE_FAULT:	return "fault";
+	case EV7_ENV_STATE_UNKNOWN:	return "unknown";
+	case EV7_ENV_STATE_REMOVED:	return "removed";
+	default:			return "unrecognized";
+	}
+}
+
 static void
 marvel_print_680_frame(struct ev7_lf_subpackets *lf_subpackets)
 {
@@ -49,17 +62,10 @@ marvel_print_680_frame(struct ev7_lf_subpackets *lf_subpackets)
 		if (!env)
 			continue;
 
-		printk("%s**%s event (cabinet %d, drawer %d)\n",
-		       err_print_prefix,
-		       ev_packets[i].name,
-		       env->cabinet,
-		       env->drawer);
-		printk("%s   Module Type: 0x%x - Unit ID 0x%x - "
-		       "Condition 0x%x\n",
-		       err_print_prefix,
-		       env->module_type,
-		       env->unit_id,
-		       env->condition);
+		printk("%s**%s %s (cabinet %u, drawer %u, PE %u, sensor %u, state %#x)\n",
+		       err_print_prefix, ev_packets[i].name,
+		       marvel_env_state(env->state), env->cabinet,
+		       env->drawer, env->pe, env->sensor, env->state);
 	}
 }
 

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/7] alpha: marvel: refresh environmental readings through the console
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
  2026-11-14 17:39 ` [PATCH 1/7] alpha: marvel: report environmental events without VERBOSE_MCHECK Matt Turner
  2026-11-14 17:39 ` [PATCH 2/7] alpha: marvel: fix the environmental subpacket layout Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 4/7] alpha: marvel: register a platform device for the environmental sensors Matt Turner
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

The SRM console keeps the readings of the temperature, fan, voltage and
power supply sensors in the power and environment (P&E) nodes of the
config tree (GCT), and refreshes those of one node when asked through
config tree callback 0x28, function 0x1f. Add the callback and
marvel_cfgtree_partial_update() for a sensor driver to use, retrying,
as Tru64 UNIX does, while the console reports busy.

On an ES47 the callback takes 13 to 300 ms per node with interrupts
disabled. The console callbacks are not reentrant: a refresh on one CPU
while another printed to the SRM console killed the second CPU, and the
service processor reset the partition. So export the srmcons callback
lock and hold it around the call.

Tru64 UNIX makes this and its other runtime config tree callbacks only on
the master CPU, and the Marvel PAL TOY calls are already known to work
only on the boot CPU, so make the call there as well.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 arch/alpha/include/asm/console.h      |  2 ++
 arch/alpha/include/asm/marvel_env.h   | 12 +++++++++
 arch/alpha/include/uapi/asm/console.h |  2 ++
 arch/alpha/kernel/core_marvel.c       | 48 +++++++++++++++++++++++++++++++++++
 arch/alpha/kernel/proto.h             |  1 +
 arch/alpha/kernel/srmcons.c           |  3 ++-
 arch/alpha/lib/callback_srm.S         |  1 +
 7 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/arch/alpha/include/asm/console.h b/arch/alpha/include/asm/console.h
index 1cabdb6064bb..9b4a0f73c125 100644
--- a/arch/alpha/include/asm/console.h
+++ b/arch/alpha/include/asm/console.h
@@ -15,6 +15,8 @@ extern long callback_read(long channel, long count, const char *buf, long lbn);
 extern long callback_getenv(long id, const char *buf, unsigned long buf_size);
 extern long callback_setenv(long id, const char *buf, unsigned long buf_size);
 extern long callback_save_env(void);
+extern long callback_cfgtree(long func, unsigned long arg1, unsigned long arg2,
+			     unsigned long arg3, unsigned long arg4);
 
 extern int srm_fixup(unsigned long new_callback_addr,
 		     unsigned long new_hwrpb_addr);
diff --git a/arch/alpha/include/asm/marvel_env.h b/arch/alpha/include/asm/marvel_env.h
new file mode 100644
index 000000000000..583dca6c02d1
--- /dev/null
+++ b/arch/alpha/include/asm/marvel_env.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ALPHA_MARVEL_ENV_H
+#define __ALPHA_MARVEL_ENV_H
+
+/*
+ * Ask the console to refresh the environmental readings of the GCT node at
+ * @offset from the start of the GCT. Returns the console status, which is 1
+ * or -0x4a on success, or -ENODEV when not booted from SRM.
+ */
+long marvel_cfgtree_partial_update(unsigned long offset);
+
+#endif /* __ALPHA_MARVEL_ENV_H */
diff --git a/arch/alpha/include/uapi/asm/console.h b/arch/alpha/include/uapi/asm/console.h
index 5fcb65300b56..ed2f0959e035 100644
--- a/arch/alpha/include/uapi/asm/console.h
+++ b/arch/alpha/include/uapi/asm/console.h
@@ -25,6 +25,8 @@
 #define CCB_GET_ENV		0x22
 #define CCB_SAVE_ENV		0x23
 
+#define CCB_CFGTREE		0x28
+
 #define CCB_PSWITCH		0x30
 #define CCB_BIOS_EMUL		0x32
 
diff --git a/arch/alpha/kernel/core_marvel.c b/arch/alpha/kernel/core_marvel.c
index 92e3312e786e..bd918028b410 100644
--- a/arch/alpha/kernel/core_marvel.c
+++ b/arch/alpha/kernel/core_marvel.c
@@ -20,10 +20,13 @@
 #include <linux/string.h>
 #include <linux/module.h>
 #include <linux/memblock.h>
+#include <linux/workqueue.h>
 
 #include <asm/ptrace.h>
 #include <asm/smp.h>
 #include <asm/gct.h>
+#include <asm/console.h>
+#include <asm/marvel_env.h>
 #include <asm/tlbflush.h>
 #include <asm/vga.h>
 
@@ -1094,3 +1097,48 @@ marvel_agp_info(void)
 
 	return agp;
 }
+
+/*
+ * The console refreshes the environmental packets of a GCT node through
+ * config tree callback function 0x1f, returning -26 while it is busy. The
+ * callback runs for up to 300 ms with interrupts disabled. Hold the console
+ * callback lock: a concurrent callback from another CPU, such as SRM console
+ * output, kills that CPU. Make the call on the boot CPU, as Tru64 UNIX does
+ * for its config tree callbacks; the PAL TOY calls also work only there.
+ */
+#define CFGTREE_PARTIAL_UPDATE	0x1f
+#define CFGTREE_BUSY		(-26)
+
+static long marvel_cfgtree_partial_update_local(void *arg)
+{
+	unsigned long flags;
+	long status;
+
+	spin_lock_irqsave(&srmcons_callback_lock, flags);
+	status = callback_cfgtree(CFGTREE_PARTIAL_UPDATE, (unsigned long)arg,
+				  0, 0, 0);
+	spin_unlock_irqrestore(&srmcons_callback_lock, flags);
+
+	return status;
+}
+
+long marvel_cfgtree_partial_update(unsigned long offset)
+{
+	long status;
+	int tries;
+
+	if (!alpha_using_srm)
+		return -ENODEV;
+
+	for (tries = 0; tries < 100; tries++) {
+		status = work_on_cpu(boot_cpuid,
+				     marvel_cfgtree_partial_update_local,
+				     (void *)offset);
+		if (status != CFGTREE_BUSY)
+			break;
+		usleep_range(10000, 20000);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(marvel_cfgtree_partial_update);
diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h
index c418333d0b89..dc4ae0e0ed05 100644
--- a/arch/alpha/kernel/proto.h
+++ b/arch/alpha/kernel/proto.h
@@ -111,6 +111,7 @@ extern void * __init move_initrd(unsigned long);
 extern struct screen_info vgacon_screen_info;
 
 /* srmcons.c */
+extern spinlock_t srmcons_callback_lock;
 #if defined(CONFIG_ALPHA_GENERIC) || defined(CONFIG_ALPHA_SRM)
 extern void register_srm_console(void);
 extern void unregister_srm_console(void);
diff --git a/arch/alpha/kernel/srmcons.c b/arch/alpha/kernel/srmcons.c
index d19e51ec711d..45a10d539f19 100644
--- a/arch/alpha/kernel/srmcons.c
+++ b/arch/alpha/kernel/srmcons.c
@@ -24,7 +24,8 @@
 #include "proto.h"
 
 
-static DEFINE_SPINLOCK(srmcons_callback_lock);
+/* The console callbacks are not reentrant. */
+DEFINE_SPINLOCK(srmcons_callback_lock);
 static int srm_is_registered_console = 0;
 
 /* 
diff --git a/arch/alpha/lib/callback_srm.S b/arch/alpha/lib/callback_srm.S
index 36b63f295170..6d3c9ce6b815 100644
--- a/arch/alpha/lib/callback_srm.S
+++ b/arch/alpha/lib/callback_srm.S
@@ -94,6 +94,7 @@ CALLBACK(reset_env, CCB_RESET_ENV, 4)
 CALLBACK(save_env, CCB_SAVE_ENV, 1)
 CALLBACK(pswitch, CCB_PSWITCH, 3)
 CALLBACK(bios_emul, CCB_BIOS_EMUL, 5)
+CALLBACK(cfgtree, CCB_CFGTREE, 6)
 
 EXPORT_SYMBOL(callback_getenv)
 EXPORT_SYMBOL(callback_setenv)

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/7] alpha: marvel: register a platform device for the environmental sensors
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
                   ` (2 preceding siblings ...)
  2026-11-14 17:39 ` [PATCH 3/7] alpha: marvel: refresh environmental readings through the console Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 5/7] hwmon: Add driver for Marvel AlphaServer " Matt Turner
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

Register a "marvel_env" platform device on Marvel systems booted from
SRM, for the hwmon driver to bind to.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 arch/alpha/kernel/core_marvel.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/alpha/kernel/core_marvel.c b/arch/alpha/kernel/core_marvel.c
index bd918028b410..d2e1e45cdf1e 100644
--- a/arch/alpha/kernel/core_marvel.c
+++ b/arch/alpha/kernel/core_marvel.c
@@ -21,6 +21,7 @@
 #include <linux/module.h>
 #include <linux/memblock.h>
 #include <linux/workqueue.h>
+#include <linux/platform_device.h>
 
 #include <asm/ptrace.h>
 #include <asm/smp.h>
@@ -1142,3 +1143,13 @@ long marvel_cfgtree_partial_update(unsigned long offset)
 	return status;
 }
 EXPORT_SYMBOL_GPL(marvel_cfgtree_partial_update);
+
+static int __init marvel_env_init(void)
+{
+	if (alpha_mv.init_arch != marvel_init_arch || !alpha_using_srm)
+		return 0;
+
+	return PTR_ERR_OR_ZERO(platform_device_register_simple("marvel_env",
+							       -1, NULL, 0));
+}
+device_initcall(marvel_env_init);

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/7] hwmon: Add driver for Marvel AlphaServer environmental sensors
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
                   ` (3 preceding siblings ...)
  2026-11-14 17:39 ` [PATCH 4/7] alpha: marvel: register a platform device for the environmental sensors Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 6/7] alpha: marvel: notify environmental events Matt Turner
  2026-11-14 17:39 ` [PATCH 7/7] hwmon: (marvel-env) Notify userspace of " Matt Turner
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

The management firmware of the Marvel (EV7) AlphaServers, the GS1280,
ES80 and ES47, monitors temperatures, fans, voltages and power supplies.
The SRM console describes each sensor as a packet in a power and
environment node of its config tree (GCT), and fills in the readings
when asked to refresh the node. The packet layout was taken from Tru64
UNIX.

Export the sensors through hwmon. Temperatures come with their warning
and fault thresholds and fans with their warning threshold. Voltages
export a reading and an alarm, power supplies only an alarm.

A refresh keeps the boot CPU in the console with interrupts disabled for
up to 300 ms per node, so refresh a node at most once per
update_interval.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 Documentation/hwmon/index.rst      |   1 +
 Documentation/hwmon/marvel-env.rst |  60 ++++
 MAINTAINERS                        |   8 +
 drivers/hwmon/Kconfig              |  11 +
 drivers/hwmon/Makefile             |   1 +
 drivers/hwmon/marvel-env.c         | 552 +++++++++++++++++++++++++++++++++++++
 6 files changed, 633 insertions(+)

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 199ca79621e9..b062ad1ff53f 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -157,6 +157,7 @@ Hardware Monitoring Kernel Drivers
    ltc4286
    lx1308
    macsmc-hwmon
+   marvel-env
    max127
    max15301
    max16064
diff --git a/Documentation/hwmon/marvel-env.rst b/Documentation/hwmon/marvel-env.rst
new file mode 100644
index 000000000000..db36127c3805
--- /dev/null
+++ b/Documentation/hwmon/marvel-env.rst
@@ -0,0 +1,60 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver marvel-env
+========================
+
+Supported systems:
+
+  * Marvel (EV7) AlphaServers: the GS1280, ES80 and ES47 families,
+    booted from the SRM console
+
+Author: Matt Turner <mattst88@gmail.com>
+
+Description
+-----------
+
+The management firmware of these machines monitors temperatures, fans,
+voltages and power supplies, and the SRM console describes each sensor in
+its configuration tree (GCT). This driver asks the console to refresh the
+readings and exports them.
+
+The console has no sensor names, so the labels are built from the reporting
+unit (such as ``CMM0`` or ``MBM/PBM``) and the sensor's position in that
+unit. On systems with more than one drawer the label starts with the
+cabinet and drawer numbers.
+
+A refresh keeps the boot CPU in the console with interrupts disabled for up
+to 300 ms per unit, so a unit is refreshed at most once every
+``update_interval`` milliseconds (10000 by default).
+
+Sysfs entries
+-------------
+
+=========================== =================================================
+``update_interval``         Minimum time between refreshes of a unit, in ms
+
+``temp[1-*]_input``         Temperature
+``temp[1-*]_max``           Warning threshold
+``temp[1-*]_crit``          Fault threshold
+``temp[1-*]_max_alarm``     Sensor is in the warning or fault state
+``temp[1-*]_crit_alarm``    Sensor is in the fault state
+``temp[1-*]_label``         Sensor label
+
+``fan[1-*]_input``          Fan speed
+``fan[1-*]_min``            Warning threshold
+``fan[1-*]_min_alarm``      Fan is in the warning or fault state
+``fan[1-*]_fault``          Fan is in the fault state
+``fan[1-*]_label``          Fan label
+
+``in[0-*]_input``           Voltage
+``in[0-*]_alarm``           Sensor is in the warning or fault state
+``in[0-*]_label``           Sensor label
+
+``power[1-*]_alarm``        Power supply is in the warning or fault state
+``power[1-*]_label``        Power supply label
+=========================== =================================================
+
+Sensors that the console reports as unknown or not present when the driver
+loads are not exported. If a sensor becomes unknown later, reading its input
+attribute fails with ``-ENODATA``, as does reading the alarm of a voltage
+sensor or power supply.
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..09dee8fab9ec 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16100,6 +16100,14 @@ L:	linux-mips@vger.kernel.org
 S:	Maintained
 F:	arch/mips/boot/dts/img/pistachio*
 
+MARVEL ALPHASERVER ENVIRONMENTAL SENSOR DRIVER
+M:	Matt Turner <mattst88@gmail.com>
+L:	linux-hwmon@vger.kernel.org
+L:	linux-alpha@vger.kernel.org
+S:	Maintained
+F:	Documentation/hwmon/marvel-env.rst
+F:	drivers/hwmon/marvel-env.c
+
 MARVELL 88E6XXX ETHERNET SWITCH FABRIC DRIVER
 M:	Andrew Lunn <andrew@lunn.ch>
 L:	netdev@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 18c682e1a475..50a1bf966f30 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1255,6 +1255,17 @@ config SENSORS_MACSMC_HWMON
 	  This driver can also be built as a module. If so, the module will
 	  be called macsmc-hwmon.
 
+config SENSORS_MARVEL_ENV
+	tristate "Marvel (EV7) AlphaServer environmental sensors"
+	depends on ALPHA_MARVEL || ALPHA_GENERIC
+	help
+	  If you say yes here you get support for the temperature, fan,
+	  voltage and power supply sensors of Marvel AlphaServers (the
+	  GS1280, ES80 and ES47), as described by the SRM console.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called marvel-env.
+
 config SENSORS_MAX1111
 	tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles"
 	depends on SPI_MASTER
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d4c345ed7261..2b1b1850d641 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -157,6 +157,7 @@ obj-$(CONFIG_SENSORS_LTC4282)	+= ltc4282.o
 obj-$(CONFIG_SENSORS_LTC4283)	+= ltc4283.o
 obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
 obj-$(CONFIG_SENSORS_MACSMC_HWMON)	+= macsmc-hwmon.o
+obj-$(CONFIG_SENSORS_MARVEL_ENV)	+= marvel-env.o
 obj-$(CONFIG_SENSORS_MAX1111)	+= max1111.o
 obj-$(CONFIG_SENSORS_MAX127)	+= max127.o
 obj-$(CONFIG_SENSORS_MAX16065)	+= max16065.o
diff --git a/drivers/hwmon/marvel-env.c b/drivers/hwmon/marvel-env.c
new file mode 100644
index 000000000000..cdc7e059e17b
--- /dev/null
+++ b/drivers/hwmon/marvel-env.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Environmental sensors of Marvel (EV7) AlphaServers: the GS1280, ES80 and
+ * ES47 families.
+ *
+ * The SRM console describes every sensor in the config tree (GCT). Each power
+ * and environment (P&E) node may carry a list of environmental packets:
+ * temperature and voltage sensors, fans and power supplies. The console fills
+ * in the readings when asked to refresh the node. The packet layout was
+ * taken from Tru64 UNIX.
+ */
+
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <asm/gct.h>
+#include <asm/marvel_env.h>
+
+#define DRVNAME "marvel_env"
+
+#define GCT_TYPE_PE		0x13
+#define GCT_FLAGS_REQUIRED	BIT(9)
+#define GCT_FLAGS_EXCLUDED	(BIT(2) | BIT(7) | BIT(12) | BIT(14))
+
+/* Packet types. */
+#define ENV_PKT_POWER_SUPPLY	0x08
+#define ENV_PKT_FAN		0x09
+#define ENV_PKT_SENSOR		0x2a
+
+/*
+ * Sensor packet: kind, status, reading, high warning, high fault.
+ * Temperatures are in tenths of a degree Celsius, voltages in millivolts.
+ * For voltages the two thresholds look like the nominal voltage and an upper
+ * limit, so they are not exported.
+ */
+#define SENSOR_KIND		0x28
+#define SENSOR_STATUS		0x2c
+#define SENSOR_VALUE		0x30
+#define SENSOR_WARN		0x34
+#define SENSOR_FAULT		0x38
+#define SENSOR_SIZE		0x3c
+
+#define SENSOR_KIND_THERMAL	2
+#define SENSOR_KIND_VOLT_FIRST	3
+#define SENSOR_KIND_VOLT_LAST	5
+
+/* Fan packet: status, rpm, low warning, low fault. */
+#define FAN_STATUS		0x24
+#define FAN_VALUE		0x28
+#define FAN_WARN		0x2c
+#define FAN_SIZE		0x34
+
+/* Power supply packet: status. */
+#define PS_STATUS		0x24
+#define PS_SIZE			0x28
+
+/* Status values, in the low byte of the status word. */
+#define ENV_OK			0
+#define ENV_WARNING		1
+#define ENV_FAULT		2
+#define ENV_UNKNOWN		0xfe
+#define ENV_NOT_PRESENT		0xff
+
+/* Console status of a refresh that Tru64 also treats as success. */
+#define CFGTREE_STATUS_OK2	(-0x4a)
+
+struct marvel_env_node {
+	gct6_node *node;
+	unsigned long offset;		/* from the start of the GCT */
+	unsigned long last_refresh;
+	bool valid;
+	unsigned int cabinet, drawer, pe;
+};
+
+struct marvel_env_chan {
+	struct marvel_env_node *node;
+	unsigned int pkt;		/* packet offset in the node */
+	char label[32];
+};
+
+struct marvel_env_data {
+	struct device *dev;
+	struct mutex lock;		/* serializes refreshes and reads */
+	unsigned int update_interval;	/* ms */
+	struct marvel_env_node *nodes;
+	unsigned int nr_nodes;
+	struct marvel_env_chan *chans[hwmon_max];
+	unsigned int nr_chans[hwmon_max];
+};
+
+static const char * const pe_names[] = {
+	[0x09] = "MBM",
+	[0x0a] = "MBM/PBM",
+	[0x0c] = "PS",
+	[0x0d] = "FAN",
+	[0x0f] = "CMM0", [0x10] = "CMM1", [0x11] = "CMM2", [0x12] = "CMM3",
+	[0x14] = "VRM0", [0x15] = "VRM1", [0x16] = "VRM2", [0x17] = "VRM3",
+	[0x19] = "PBM",
+	[0x1a] = "XSHELF",
+	[0x1b] = "PCIPS",
+	[0x1c] = "PCIFANS",
+};
+
+static inline u32 env_rd(struct marvel_env_node *n, unsigned int off)
+{
+	return *(u32 *)((char *)n->node + off);
+}
+
+static inline u8 env_status(struct marvel_env_node *n, unsigned int off)
+{
+	return env_rd(n, off) & 0xff;
+}
+
+static bool env_status_valid(u8 status)
+{
+	return status != ENV_UNKNOWN && status != ENV_NOT_PRESENT;
+}
+
+static bool env_status_alarm(u8 status)
+{
+	return status == ENV_WARNING || status == ENV_FAULT;
+}
+
+static unsigned int env_status_off(enum hwmon_sensor_types type)
+{
+	switch (type) {
+	case hwmon_fan:
+		return FAN_STATUS;
+	case hwmon_power:
+		return PS_STATUS;
+	default:
+		return SENSOR_STATUS;
+	}
+}
+
+static inline gct6_node *gct_node(gct6_handle h)
+{
+	return GCT_NODE_PTR(h);
+}
+
+/* Cabinet and drawer come from the P&E node's parent. */
+static void marvel_env_keys(struct marvel_env_node *n)
+{
+	gct6_node *parent = n->node->parent ? gct_node(n->node->parent) : NULL;
+
+	n->pe = n->node->id & 0xff;
+	if (!parent)
+		return;
+	if (parent->type == GCT_TYPE_PE && parent->subtype == 0x3d)
+		n->cabinet = (parent->id >> 48) & 0xff;
+	else
+		n->cabinet = (parent->id >> 48) & 0x3f;
+	n->drawer = (parent->id >> 32) & 0xff;
+}
+
+static int marvel_env_refresh(struct marvel_env_data *data,
+			      struct marvel_env_node *n)
+{
+	long status;
+
+	if (n->valid && time_before(jiffies, n->last_refresh +
+				    msecs_to_jiffies(data->update_interval)))
+		return 0;
+
+	status = marvel_cfgtree_partial_update(n->offset);
+	if (status != 1 && status != CFGTREE_STATUS_OK2) {
+		dev_warn_ratelimited(data->dev,
+				     "refresh of GCT node %#lx failed: %ld\n",
+				     n->offset, status);
+		return -EIO;
+	}
+	n->last_refresh = jiffies;
+	n->valid = true;
+	return 0;
+}
+
+/* The hwmon type of a packet, or hwmon_max to skip it. */
+static enum hwmon_sensor_types marvel_env_pkt_type(struct marvel_env_node *n,
+						   unsigned int pkt,
+						   unsigned int type)
+{
+	unsigned int len = env_rd(n, pkt) & 0xffff;
+	u32 kind;
+
+	switch (type) {
+	case ENV_PKT_SENSOR:
+		if (len < SENSOR_SIZE)
+			break;
+		kind = env_rd(n, pkt + SENSOR_KIND);
+		if (kind == SENSOR_KIND_THERMAL)
+			return hwmon_temp;
+		if (kind >= SENSOR_KIND_VOLT_FIRST && kind <= SENSOR_KIND_VOLT_LAST)
+			return hwmon_in;
+		break;
+	case ENV_PKT_FAN:
+		if (len >= FAN_SIZE)
+			return hwmon_fan;
+		break;
+	case ENV_PKT_POWER_SUPPLY:
+		if (len >= PS_SIZE)
+			return hwmon_power;
+		break;
+	}
+	return hwmon_max;
+}
+
+static void marvel_env_add_chan(struct marvel_env_data *data,
+				struct marvel_env_node *n, unsigned int pkt,
+				enum hwmon_sensor_types type,
+				unsigned int index)
+{
+	static const char * const what[hwmon_max] = {
+		[hwmon_temp] = "Temp", [hwmon_fan] = "Fan",
+		[hwmon_in] = "Volt", [hwmon_power] = "PS",
+	};
+	struct marvel_env_chan *c;
+	const char *pe = NULL;
+	size_t len = 0;
+
+	c = &data->chans[type][data->nr_chans[type]++];
+	c->node = n;
+	c->pkt = pkt;
+
+	if (n->cabinet || n->drawer)
+		len = scnprintf(c->label, sizeof(c->label), "Cab%u Dra%u ",
+				n->cabinet, n->drawer);
+
+	if (n->pe < ARRAY_SIZE(pe_names))
+		pe = pe_names[n->pe];
+	if (pe && !strncasecmp(pe, what[type], strlen(what[type])))
+		scnprintf(c->label + len, sizeof(c->label) - len, "%s %u",
+			  pe, index);
+	else if (pe)
+		scnprintf(c->label + len, sizeof(c->label) - len, "%s %s %u",
+			  pe, what[type], index);
+	else
+		scnprintf(c->label + len, sizeof(c->label) - len, "PE%u %s %u",
+			  n->pe, what[type], index);
+}
+
+/*
+ * Walk a P&E node's packets, counting them, or adding them when @add. The
+ * header at hd_extension holds the count at +0x10 and the offset of the
+ * first packet at +0x14. As Tru64 does, visit count + 1 packets and number
+ * sensors from their position minus one.
+ */
+static void marvel_env_walk(struct marvel_env_data *data,
+			    struct marvel_env_node *n, bool add)
+{
+	unsigned int size = n->node->size;
+	unsigned int hdr = n->node->hd_extension;
+	unsigned int count, pkt, i;
+
+	if (!hdr || hdr + 0x18 > size)
+		return;
+	count = env_rd(n, hdr + 0x10);
+	pkt = env_rd(n, hdr + 0x14);
+
+	for (i = 0; i <= count; i++) {
+		enum hwmon_sensor_types type;
+		unsigned int len, pkt_type;
+
+		if (pkt < 8 || pkt + 8 > size)
+			break;
+		len = env_rd(n, pkt) & 0xffff;
+		pkt_type = env_rd(n, pkt + 4) & 0xffff;
+		if (len < 8 || pkt + len > size)
+			break;
+
+		type = marvel_env_pkt_type(n, pkt, pkt_type);
+		if (type != hwmon_max && add)
+			marvel_env_add_chan(data, n, pkt, type,
+					    pkt_type == ENV_PKT_SENSOR && i ?
+					    i - 1 : i);
+		else if (type != hwmon_max)
+			data->nr_chans[type]++;
+		pkt += len;
+	}
+}
+
+/* Collect the P&E nodes with an environmental packet list. */
+static void marvel_env_find_nodes(struct marvel_env_data *data,
+				  gct6_node *node, bool record)
+{
+	for (; node; node = node->next ? gct_node(node->next) : NULL) {
+		if (node->magic != GCT_NODE_MAGIC)
+			return;
+		if (node->type == GCT_TYPE_PE && node->hd_extension &&
+		    (node->flags & GCT_FLAGS_REQUIRED) &&
+		    !(node->flags & GCT_FLAGS_EXCLUDED)) {
+			if (record) {
+				struct marvel_env_node *n =
+					&data->nodes[data->nr_nodes];
+
+				n->node = node;
+				n->offset = (char *)node - (char *)gct_node(0);
+				marvel_env_keys(n);
+			}
+			data->nr_nodes++;
+		}
+		if (node->child)
+			marvel_env_find_nodes(data, gct_node(node->child),
+					      record);
+	}
+}
+
+static umode_t marvel_env_is_visible(const void *drvdata,
+				     enum hwmon_sensor_types type,
+				     u32 attr, int channel)
+{
+	const struct marvel_env_data *data = drvdata;
+	const struct marvel_env_chan *c;
+
+	if (type == hwmon_chip)
+		return 0644;
+
+	/* Hide sensors that are absent when the driver loads. */
+	c = &data->chans[type][channel];
+	if (!env_status_valid(env_status(c->node, c->pkt + env_status_off(type))))
+		return 0;
+	return 0444;
+}
+
+static int marvel_env_read(struct device *dev, enum hwmon_sensor_types type,
+			   u32 attr, int channel, long *val)
+{
+	struct marvel_env_data *data = dev_get_drvdata(dev);
+	struct marvel_env_chan *c;
+	struct marvel_env_node *n;
+	unsigned int pkt;
+	u8 status;
+	int ret;
+
+	if (type == hwmon_chip) {
+		*val = data->update_interval;
+		return 0;
+	}
+
+	c = &data->chans[type][channel];
+	n = c->node;
+	pkt = c->pkt;
+
+	mutex_lock(&data->lock);
+	ret = marvel_env_refresh(data, n);
+	if (ret)
+		goto out;
+
+	switch (type) {
+	case hwmon_temp:
+		status = env_status(n, pkt + SENSOR_STATUS);
+		switch (attr) {
+		case hwmon_temp_input:
+			if (!env_status_valid(status))
+				ret = -ENODATA;
+			else
+				*val = (s32)env_rd(n, pkt + SENSOR_VALUE) * 100L;
+			break;
+		case hwmon_temp_max:
+			*val = (s32)env_rd(n, pkt + SENSOR_WARN) * 100L;
+			break;
+		case hwmon_temp_crit:
+			*val = (s32)env_rd(n, pkt + SENSOR_FAULT) * 100L;
+			break;
+		case hwmon_temp_max_alarm:
+			*val = env_status_alarm(status);
+			break;
+		case hwmon_temp_crit_alarm:
+			*val = status == ENV_FAULT;
+			break;
+		default:
+			ret = -EOPNOTSUPP;
+		}
+		break;
+	case hwmon_fan:
+		status = env_status(n, pkt + FAN_STATUS);
+		switch (attr) {
+		case hwmon_fan_input:
+			if (!env_status_valid(status))
+				ret = -ENODATA;
+			else
+				*val = env_rd(n, pkt + FAN_VALUE);
+			break;
+		case hwmon_fan_min:
+			*val = env_rd(n, pkt + FAN_WARN);
+			break;
+		case hwmon_fan_min_alarm:
+			*val = env_status_alarm(status);
+			break;
+		case hwmon_fan_fault:
+			*val = status == ENV_FAULT;
+			break;
+		default:
+			ret = -EOPNOTSUPP;
+		}
+		break;
+	case hwmon_in:
+	case hwmon_power:
+		status = env_status(n, pkt + env_status_off(type));
+		if (!env_status_valid(status))
+			ret = -ENODATA;
+		else if (type == hwmon_in && attr == hwmon_in_input)
+			*val = (s32)env_rd(n, pkt + SENSOR_VALUE);
+		else
+			*val = env_status_alarm(status);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+	}
+out:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int marvel_env_write(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long val)
+{
+	struct marvel_env_data *data = dev_get_drvdata(dev);
+
+	if (type != hwmon_chip || attr != hwmon_chip_update_interval)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&data->lock);
+	data->update_interval = clamp_val(val, 0, 3600 * MSEC_PER_SEC);
+	mutex_unlock(&data->lock);
+	return 0;
+}
+
+static int marvel_env_read_string(struct device *dev,
+				  enum hwmon_sensor_types type, u32 attr,
+				  int channel, const char **str)
+{
+	struct marvel_env_data *data = dev_get_drvdata(dev);
+
+	*str = data->chans[type][channel].label;
+	return 0;
+}
+
+static const struct hwmon_ops marvel_env_ops = {
+	.is_visible = marvel_env_is_visible,
+	.read = marvel_env_read,
+	.write = marvel_env_write,
+	.read_string = marvel_env_read_string,
+};
+
+static const struct {
+	enum hwmon_sensor_types type;
+	u32 config;
+} marvel_env_hwmon[] = {
+	{ hwmon_temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+		      HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LABEL },
+	{ hwmon_fan, HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MIN_ALARM |
+		     HWMON_F_FAULT | HWMON_F_LABEL },
+	{ hwmon_in, HWMON_I_INPUT | HWMON_I_ALARM | HWMON_I_LABEL },
+	{ hwmon_power, HWMON_P_ALARM | HWMON_P_LABEL },
+};
+
+static const struct hwmon_channel_info * const marvel_env_chip_info =
+	HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL);
+
+static int marvel_env_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct hwmon_channel_info **info;
+	struct marvel_env_data *data;
+	struct hwmon_chip_info *chip;
+	struct device *hwmon;
+	unsigned int i, k, nr_info = 0;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->dev = dev;
+	data->update_interval = 10 * MSEC_PER_SEC;
+	mutex_init(&data->lock);
+
+	marvel_env_find_nodes(data, gct_node(0), false);
+	if (!data->nr_nodes)
+		return -ENODEV;
+	data->nodes = devm_kcalloc(dev, data->nr_nodes, sizeof(*data->nodes),
+				   GFP_KERNEL);
+	if (!data->nodes)
+		return -ENOMEM;
+	data->nr_nodes = 0;
+	marvel_env_find_nodes(data, gct_node(0), true);
+
+	for (i = 0; i < data->nr_nodes; i++) {
+		marvel_env_refresh(data, &data->nodes[i]);
+		marvel_env_walk(data, &data->nodes[i], false);
+	}
+
+	info = devm_kcalloc(dev, ARRAY_SIZE(marvel_env_hwmon) + 2,
+			    sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	info[nr_info++] = marvel_env_chip_info;
+
+	for (k = 0; k < ARRAY_SIZE(marvel_env_hwmon); k++) {
+		enum hwmon_sensor_types type = marvel_env_hwmon[k].type;
+		struct hwmon_channel_info *ci;
+		u32 *config;
+
+		if (!data->nr_chans[type])
+			continue;
+		data->chans[type] = devm_kcalloc(dev, data->nr_chans[type],
+						 sizeof(*data->chans[type]),
+						 GFP_KERNEL);
+		ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL);
+		config = devm_kcalloc(dev, data->nr_chans[type] + 1,
+				      sizeof(*config), GFP_KERNEL);
+		if (!data->chans[type] || !ci || !config)
+			return -ENOMEM;
+		for (i = 0; i < data->nr_chans[type]; i++)
+			config[i] = marvel_env_hwmon[k].config;
+		ci->type = type;
+		ci->config = config;
+		info[nr_info++] = ci;
+		data->nr_chans[type] = 0;
+	}
+	if (nr_info == 1)
+		return -ENODEV;
+
+	for (i = 0; i < data->nr_nodes; i++)
+		marvel_env_walk(data, &data->nodes[i], true);
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+	chip->ops = &marvel_env_ops;
+	chip->info = info;
+
+	hwmon = devm_hwmon_device_register_with_info(dev, DRVNAME, data, chip,
+						     NULL);
+	return PTR_ERR_OR_ZERO(hwmon);
+}
+
+static struct platform_driver marvel_env_driver = {
+	.driver = {
+		.name = DRVNAME,
+	},
+	.probe = marvel_env_probe,
+};
+module_platform_driver(marvel_env_driver);
+
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_DESCRIPTION("Marvel (EV7) AlphaServer environmental sensors");
+MODULE_LICENSE("GPL");

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 6/7] alpha: marvel: notify environmental events
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
                   ` (4 preceding siblings ...)
  2026-11-14 17:39 ` [PATCH 5/7] hwmon: Add driver for Marvel AlphaServer " Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  2026-11-14 17:39 ` [PATCH 7/7] hwmon: (marvel-env) Notify userspace of " Matt Turner
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

A change in the state of a sensor arrives as a system event logout
frame. Call an atomic notifier chain on each one, so that a sensor
driver can reread its sensors and tell userspace without polling.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 arch/alpha/include/asm/marvel_env.h | 5 +++++
 arch/alpha/kernel/err_marvel.c      | 9 ++++++++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/arch/alpha/include/asm/marvel_env.h b/arch/alpha/include/asm/marvel_env.h
index 583dca6c02d1..14b05efa6f51 100644
--- a/arch/alpha/include/asm/marvel_env.h
+++ b/arch/alpha/include/asm/marvel_env.h
@@ -2,6 +2,8 @@
 #ifndef __ALPHA_MARVEL_ENV_H
 #define __ALPHA_MARVEL_ENV_H
 
+#include <linux/notifier.h>
+
 /*
  * Ask the console to refresh the environmental readings of the GCT node at
  * @offset from the start of the GCT. Returns the console status, which is 1
@@ -9,4 +11,7 @@
  */
 long marvel_cfgtree_partial_update(unsigned long offset);
 
+/* Called from machine check context on each environmental system event. */
+extern struct atomic_notifier_head marvel_env_notifier;
+
 #endif /* __ALPHA_MARVEL_ENV_H */
diff --git a/arch/alpha/kernel/err_marvel.c b/arch/alpha/kernel/err_marvel.c
index 46c521cea9a0..6e0b0aabdaca 100644
--- a/arch/alpha/kernel/err_marvel.c
+++ b/arch/alpha/kernel/err_marvel.c
@@ -17,10 +17,14 @@
 #include <asm/smp.h>
 #include <asm/err_common.h>
 #include <asm/err_ev7.h>
+#include <asm/marvel_env.h>
 
 #include "err_impl.h"
 #include "proto.h"
 
+ATOMIC_NOTIFIER_HEAD(marvel_env_notifier);
+EXPORT_SYMBOL_GPL(marvel_env_notifier);
+
 static const char *
 marvel_env_state(u16 state)
 {
@@ -82,8 +86,11 @@ marvel_process_680_frame(struct ev7_lf_subpackets *lf_subpackets, int print)
 			status = MCHK_DISPOSITION_REPORT;
 	}
 
-	if (print)
+	/* The frame is processed twice; notify on the printing pass only. */
+	if (print) {
 		marvel_print_680_frame(lf_subpackets);
+		atomic_notifier_call_chain(&marvel_env_notifier, 0, NULL);
+	}
 
 	return status;
 }

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 7/7] hwmon: (marvel-env) Notify userspace of environmental events
  2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
                   ` (5 preceding siblings ...)
  2026-11-14 17:39 ` [PATCH 6/7] alpha: marvel: notify environmental events Matt Turner
@ 2026-11-14 17:39 ` Matt Turner
  6 siblings, 0 replies; 8+ messages in thread
From: Matt Turner @ 2026-11-14 17:39 UTC (permalink / raw)
  To: Richard Henderson, Magnus Lindholm, Guenter Roeck
  Cc: linux-alpha, linux-kernel, linux-hwmon, linux-doc, Matt Turner

The firmware reports each change of a sensor's state as a system event
machine check. On one, reread every node from a workqueue and notify
userspace of the alarms that changed. The notifier runs in machine check
context, so it only queues an irq_work, which queues the work.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
 Documentation/hwmon/marvel-env.rst |  4 ++
 drivers/hwmon/Kconfig              |  1 +
 drivers/hwmon/marvel-env.c         | 89 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/Documentation/hwmon/marvel-env.rst b/Documentation/hwmon/marvel-env.rst
index db36127c3805..f88feba7bfe4 100644
--- a/Documentation/hwmon/marvel-env.rst
+++ b/Documentation/hwmon/marvel-env.rst
@@ -27,6 +27,10 @@ A refresh keeps the boot CPU in the console with interrupts disabled for up
 to 300 ms per unit, so a unit is refreshed at most once every
 ``update_interval`` milliseconds (10000 by default).
 
+The firmware also reports every change of state as a system event machine
+check. When one arrives, the driver refreshes all units and notifies
+userspace through the alarm attributes.
+
 Sysfs entries
 -------------
 
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 50a1bf966f30..6d17c2e89a7c 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1258,6 +1258,7 @@ config SENSORS_MACSMC_HWMON
 config SENSORS_MARVEL_ENV
 	tristate "Marvel (EV7) AlphaServer environmental sensors"
 	depends on ALPHA_MARVEL || ALPHA_GENERIC
+	select IRQ_WORK
 	help
 	  If you say yes here you get support for the temperature, fan,
 	  voltage and power supply sensors of Marvel AlphaServers (the
diff --git a/drivers/hwmon/marvel-env.c b/drivers/hwmon/marvel-env.c
index cdc7e059e17b..3dd2ea658fa3 100644
--- a/drivers/hwmon/marvel-env.c
+++ b/drivers/hwmon/marvel-env.c
@@ -11,13 +11,16 @@
  */
 
 #include <linux/hwmon.h>
+#include <linux/irq_work.h>
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/notifier.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/string.h>
+#include <linux/workqueue.h>
 
 #include <asm/gct.h>
 #include <asm/marvel_env.h>
@@ -81,17 +84,22 @@ struct marvel_env_node {
 struct marvel_env_chan {
 	struct marvel_env_node *node;
 	unsigned int pkt;		/* packet offset in the node */
+	u8 last_status;
 	char label[32];
 };
 
 struct marvel_env_data {
 	struct device *dev;
+	struct device *hwmon;
 	struct mutex lock;		/* serializes refreshes and reads */
 	unsigned int update_interval;	/* ms */
 	struct marvel_env_node *nodes;
 	unsigned int nr_nodes;
 	struct marvel_env_chan *chans[hwmon_max];
 	unsigned int nr_chans[hwmon_max];
+	struct irq_work irq_work;
+	struct work_struct work;
+	struct notifier_block nb;
 };
 
 static const char * const pe_names[] = {
@@ -226,6 +234,7 @@ static void marvel_env_add_chan(struct marvel_env_data *data,
 	c = &data->chans[type][data->nr_chans[type]++];
 	c->node = n;
 	c->pkt = pkt;
+	c->last_status = env_status(n, pkt + env_status_off(type));
 
 	if (n->cabinet || n->drawer)
 		len = scnprintf(c->label, sizeof(c->label), "Cab%u Dra%u ",
@@ -460,6 +469,72 @@ static const struct {
 	{ hwmon_power, HWMON_P_ALARM | HWMON_P_LABEL },
 };
 
+static const u32 marvel_env_alarm[hwmon_max] = {
+	[hwmon_temp] = hwmon_temp_max_alarm,
+	[hwmon_fan] = hwmon_fan_min_alarm,
+	[hwmon_in] = hwmon_in_alarm,
+	[hwmon_power] = hwmon_power_alarm,
+};
+
+/* Reread everything after a system event and report changed alarms. */
+static void marvel_env_event_work(struct work_struct *work)
+{
+	struct marvel_env_data *data =
+		container_of(work, struct marvel_env_data, work);
+	unsigned int i, k;
+
+	mutex_lock(&data->lock);
+	for (i = 0; i < data->nr_nodes; i++) {
+		data->nodes[i].valid = false;
+		marvel_env_refresh(data, &data->nodes[i]);
+	}
+
+	for (k = 0; k < ARRAY_SIZE(marvel_env_hwmon); k++) {
+		enum hwmon_sensor_types type = marvel_env_hwmon[k].type;
+
+		for (i = 0; i < data->nr_chans[type]; i++) {
+			struct marvel_env_chan *c = &data->chans[type][i];
+			u8 status = env_status(c->node,
+					       c->pkt + env_status_off(type));
+
+			if (status == c->last_status)
+				continue;
+			c->last_status = status;
+			hwmon_notify_event(data->hwmon, type,
+					   marvel_env_alarm[type], i);
+		}
+	}
+	mutex_unlock(&data->lock);
+}
+
+static void marvel_env_irq_work(struct irq_work *irq_work)
+{
+	struct marvel_env_data *data =
+		container_of(irq_work, struct marvel_env_data, irq_work);
+
+	schedule_work(&data->work);
+}
+
+/* Runs in machine check context, so only queue the irq_work. */
+static int marvel_env_event(struct notifier_block *nb, unsigned long action,
+			    void *unused)
+{
+	struct marvel_env_data *data =
+		container_of(nb, struct marvel_env_data, nb);
+
+	irq_work_queue(&data->irq_work);
+	return NOTIFY_OK;
+}
+
+static void marvel_env_unregister(void *arg)
+{
+	struct marvel_env_data *data = arg;
+
+	atomic_notifier_chain_unregister(&marvel_env_notifier, &data->nb);
+	irq_work_sync(&data->irq_work);
+	cancel_work_sync(&data->work);
+}
+
 static const struct hwmon_channel_info * const marvel_env_chip_info =
 	HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL);
 
@@ -469,7 +544,6 @@ static int marvel_env_probe(struct platform_device *pdev)
 	const struct hwmon_channel_info **info;
 	struct marvel_env_data *data;
 	struct hwmon_chip_info *chip;
-	struct device *hwmon;
 	unsigned int i, k, nr_info = 0;
 
 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
@@ -534,9 +608,16 @@ static int marvel_env_probe(struct platform_device *pdev)
 	chip->ops = &marvel_env_ops;
 	chip->info = info;
 
-	hwmon = devm_hwmon_device_register_with_info(dev, DRVNAME, data, chip,
-						     NULL);
-	return PTR_ERR_OR_ZERO(hwmon);
+	data->hwmon = devm_hwmon_device_register_with_info(dev, DRVNAME, data,
+							   chip, NULL);
+	if (IS_ERR(data->hwmon))
+		return PTR_ERR(data->hwmon);
+
+	init_irq_work(&data->irq_work, marvel_env_irq_work);
+	INIT_WORK(&data->work, marvel_env_event_work);
+	data->nb.notifier_call = marvel_env_event;
+	atomic_notifier_chain_register(&marvel_env_notifier, &data->nb);
+	return devm_add_action_or_reset(dev, marvel_env_unregister, data);
 }
 
 static struct platform_driver marvel_env_driver = {

-- 
2.56.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-11-14 17:40 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 17:39 [PATCH 0/7] alpha: marvel: environmental sensors Matt Turner
2026-11-14 17:39 ` [PATCH 1/7] alpha: marvel: report environmental events without VERBOSE_MCHECK Matt Turner
2026-11-14 17:39 ` [PATCH 2/7] alpha: marvel: fix the environmental subpacket layout Matt Turner
2026-11-14 17:39 ` [PATCH 3/7] alpha: marvel: refresh environmental readings through the console Matt Turner
2026-11-14 17:39 ` [PATCH 4/7] alpha: marvel: register a platform device for the environmental sensors Matt Turner
2026-11-14 17:39 ` [PATCH 5/7] hwmon: Add driver for Marvel AlphaServer " Matt Turner
2026-11-14 17:39 ` [PATCH 6/7] alpha: marvel: notify environmental events Matt Turner
2026-11-14 17:39 ` [PATCH 7/7] hwmon: (marvel-env) Notify userspace of " Matt Turner

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