--- src/sys/kern/kern_todr.c.dist	2026-01-23 16:30:48.189998898 +0100
+++ src/sys/kern/kern_todr.c	2026-02-22 19:56:51.449991529 +0100
@@ -78,6 +78,7 @@
 #include <sys/device_calls.h>
 #include <sys/intr.h>
 #include <sys/kernel.h>
+#include <sys/kmem.h>
 #include <sys/mutex.h>
 #include <sys/rndsource.h>
 #include <sys/sdt.h>
@@ -86,11 +87,17 @@
 
 #include <dev/clock_subr.h>	/* hmm.. this should probably move to sys */
 
+struct todr_list {
+	todr_chip_handle_t todr_handle;
+	struct todr_list *todr_next;
+};
+
 static int todr_gettime(todr_chip_handle_t, struct timeval *);
 static int todr_settime(todr_chip_handle_t, struct timeval *);
 
 static kmutex_t todr_mutex;
-static todr_chip_handle_t todr_handle;
+static struct todr_list todr_head =
+	{ .todr_handle = NULL, .todr_next = NULL };
 static bool todr_initialized;
 
 /* The minimum reasonable RTC date before preposterousness */
@@ -179,6 +186,7 @@
 void
 todr_attach(todr_chip_handle_t todr)
 {
+	struct todr_list *todr_newp, *todr_iterp;
 
 	/*
 	 * todr_init() is called very early in main(), but this is
@@ -191,14 +199,24 @@
 		return;
 	}
 
-	todr_lock();
-	if (todr_handle) {
+	/* Initial entry is static, others allocated. */
+	if (todr_head.todr_handle == NULL) {
+		todr_lock();
+		todr_head.todr_handle = todr;
 		todr_unlock();
-		printf("todr_attach: TOD already configured\n");
-		return;
+	} else {
+		todr_newp = kmem_alloc(sizeof(struct todr_list),
+		    KM_SLEEP);
+		todr_newp->todr_next = NULL;
+		todr_lock();
+		todr_newp->todr_handle = todr;
+		todr_iterp = &todr_head;
+		while (todr_iterp->todr_next != NULL)
+			todr_iterp = todr_iterp->todr_next;
+		todr_iterp->todr_next = todr_newp;
+		todr_unlock();
+		printf("(secondary clock)\n");
 	}
-	todr_handle = todr;
-	todr_unlock();
 }
 
 static bool timeset = false;
@@ -217,7 +235,10 @@
 	bool goodtime = false;
 	bool badrtc = false;
 	struct timespec ts;
-	struct timeval tv;
+	struct timeval tv, tv2;
+	struct todr_list *todr_iterp;
+	device_t t_dev;
+	time_t deltat;
 
 	KASSERT(todr_lock_owned());
 
@@ -246,22 +267,23 @@
 	/*
 	 * Some ports need to be supplied base in order to fabricate a time_t.
 	 */
-	if (todr_handle)
-		todr_handle->todr_base_time = base;
+	if (todr_head.todr_handle != NULL)
+		todr_head.todr_handle->todr_base_time = base;
 
 	memset(&tv, 0, sizeof(tv));
 
-	if ((todr_handle == NULL) ||
-	    (todr_gettime(todr_handle, &tv) != 0) ||
+	/* Note, that we use the first RTC for system time. */
+	if ((todr_head.todr_handle == NULL) ||
+	    (todr_gettime(todr_head.todr_handle, &tv) != 0) ||
 	    (tv.tv_sec < (PREPOSTEROUS_YEARS * SECS_PER_COMMON_YEAR))) {
 
-		if (todr_handle != NULL)
+		if (todr_head.todr_handle != NULL)
 			printf("WARNING: preposterous TOD clock time\n");
 		else
 			printf("WARNING: no TOD clock present\n");
 		badrtc = true;
 	} else {
-		time_t deltat = tv.tv_sec - base;
+		deltat = tv.tv_sec - base;
 
 		if (deltat < 0)
 			deltat = -deltat;
@@ -301,6 +323,29 @@
 		tv.tv_usec = 0;
 	}
 
+	/* Check other clocks, if any, against our time */
+	todr_iterp = &todr_head;
+	while ((todr_iterp = todr_iterp->todr_next) != NULL) {
+		if ((todr_iterp->todr_handle == NULL) || 
+		    (todr_gettime(todr_iterp->todr_handle, &tv2) != 0))
+			continue;
+		t_dev = todr_iterp->todr_handle->todr_dev;
+		if (tv2.tv_sec < (PREPOSTEROUS_YEARS * SECS_PER_COMMON_YEAR)) {
+			printf("WARNING: preposterous %s clock time\n",
+			    device_xname(t_dev));
+			deltat = 0;
+		} else {
+			deltat = tv.tv_sec - tv2.tv_sec;
+			rnd_add_data(NULL, &tv, sizeof(tv), 0);
+		}
+		if (deltat < -10)
+			printf("WARNING: %s clock lost %" PRId64 " seconds\n",
+			    device_xname(t_dev), deltat);
+		if (deltat > 10)
+			printf("WARNING: %s clock gained %" PRId64 " seconds\n",
+			    device_xname(t_dev), deltat);
+	}
+
 	timeset = true;
 
 	ts.tv_sec = tv.tv_sec;
@@ -315,13 +360,14 @@
 
 /*
  * todr_save_systime:
- *	Save the current system time back to the TOD clock.
+ *	Save the current system time back to the TOD clock(s).
  *	Must be called with the TODR lock held.
  */
 void
 todr_save_systime(void)
 {
 	struct timeval tv;
+	struct todr_list *todr_iterp;
 
 	KASSERT(todr_lock_owned());
 
@@ -338,9 +384,14 @@
 	if (tv.tv_sec == 0)
 		return;
 
-	if (todr_handle)
-		if (todr_settime(todr_handle, &tv) != 0)
-			printf("Cannot set TOD clock time\n");
+	todr_iterp = &todr_head;
+	do {
+		if ((todr_iterp->todr_handle != NULL) &&
+		    (todr_settime(todr_iterp->todr_handle, &tv) != 0))
+			printf("Cannot set %s TOD clock time\n",
+			    device_xname(todr_iterp->todr_handle->todr_dev));
+		todr_iterp = todr_iterp->todr_next;
+	} while (todr_iterp != NULL);
 }
 
 /*
--- src/sys/arch/sparc64/conf/GENERIC.dist	2026-05-27 13:08:39.017838911 +0200
+++ src/sys/arch/sparc64/conf/GENERIC	2026-05-27 08:19:20.029990725 +0200
@@ -250,6 +250,10 @@
 jbusi2c* 	at mainbus0
 iic* 	at jbusi2c?
 
+## Found in pyro-based systems
+firei2c* 	at mainbus0
+iic* 	at firei2c?
+
 # Virtual devices for sun4v systems.
 vrtc0	at vbus?
 vdsk*	at cbus?
@@ -830,9 +834,13 @@
 lmtemp* 	at iic? addr?
 tda*		at iic? addr?	# fan control on SB1000/2000
 dbcool* 	at iic? addr?	# SB25000
+nxp75a* 	at iic? addr?	# U45
+lm95221ts* 	at iic? addr?	# U45
+adt7462sm* 	at iic? addr?	# U45
 seeprom*	at iic? addr?	# i2c-at24c64 fru's
 pcagpio* 	at iic? addr?	# V210/V240 GPIO's
 pcf8574io* 	at iic? addr?	# E250 GPIO's
+dsrtc* 		at iic? addr?	# RSC clock found on V210/V240
 
 ### Other pseudo-devices
 
--- src/sys/arch/sparc64/sparc64/autoconf.c.dist	2026-05-27 13:05:37.007839020 +0200
+++ src/sys/arch/sparc64/sparc64/autoconf.c	2026-05-28 19:30:26.829966724 +0200
@@ -775,8 +775,43 @@
 			portid = -1;
 		ma.ma_upaid = portid;
 
-		if (prom_getprop(node, "reg", sizeof(*ma.ma_reg), 
-				 &ma.ma_nreg, &ma.ma_reg) != 0)
+#define NREG32 3
+		if (prom_getproplen(node, "reg") == NREG32 * 4) {
+			/*
+			 * Fix up nodes where reg is encoded as a 64-bit and
+			 * a 32-bit value (e.g. 00000400 0fc62020 00000010),
+			 * but we want 2 * 64-bit values.
+			 * The length in this case is 3 * 4 (bytes).
+			 */
+			int n;
+			int32_t *reg32p = NULL;
+
+			if (prom_getprop(node, "reg", sizeof(int32_t),
+			    &n, &reg32p) != 0)
+				continue;
+			if (n != NREG32) {
+				free(reg32p, M_DEVBUF);
+				continue;
+			}
+			ma.ma_reg = malloc(sizeof(*ma.ma_reg), M_DEVBUF,
+			    M_NOWAIT);
+			if (ma.ma_reg == NULL)
+				continue;
+			ma.ma_reg->ur_paddr = reg32p[0];
+			ma.ma_reg->ur_paddr = ma.ma_reg->ur_paddr << 32;
+			ma.ma_reg->ur_paddr |= reg32p[1];
+			ma.ma_reg->ur_len = reg32p[2];
+			ma.ma_nreg = 1;
+			free(reg32p, M_DEVBUF);
+#ifdef DEBUG
+			if (autoconf_debug & ACDB_PROBE) {
+				printf(" fixed up 64/32 reg property\n");
+			}
+#endif
+		} else
+			/* reg is encoded as we expect */
+			if (prom_getprop(node, "reg", sizeof(*ma.ma_reg),
+			    &ma.ma_nreg, &ma.ma_reg) != 0)
 			continue;
 #ifdef DEBUG
 		if (autoconf_debug & ACDB_PROBE) {
@@ -1164,7 +1199,8 @@
 		if (ia->ia_name == NULL)	/* indirect config */
 			return;
 
-		set_i2c_dev_props(dev, aux);	/* i2c device patches */
+		/* i2c device patches */
+		set_i2c_dev_props(dev, device_parent(busdev), aux);
 
 		return;
 	} else if (device_is_a(dev, "sd") || device_is_a(dev, "cd")) {
--- src/sys/arch/sparc64/sparc64/ofw_patch.c.dist	2026-05-27 13:08:39.167838911 +0200
+++ src/sys/arch/sparc64/sparc64/ofw_patch.c	2026-05-28 21:05:58.039963307 +0200
@@ -136,6 +136,24 @@
 }
 
 static void
+add_gpio_props_u45(device_t dev, void *aux)
+{
+	struct i2c_attach_args *ia = aux;
+	prop_dictionary_t dict = device_properties(dev);
+	prop_array_t pins;
+
+	switch (ia->ia_addr) {
+		case 0x18:	/* front panel LEDs */
+			pins = prop_array_create();
+			add_gpio_pin(pins, "LED power", 0, 1, -1);
+			add_gpio_pin(pins, "LED fault", 1, 0, -1);
+			prop_dictionary_set(dict, "pins", pins);
+			prop_object_release(pins);
+			break;
+	}
+}
+
+static void
 add_gpio_props_e250(device_t dev, void *aux)
 {
 	struct i2c_attach_args *ia = aux;
@@ -290,6 +308,24 @@
 	add_i2c_device(cfg, "temperature-sensor", "i2c-lm75", 0x4e, 0);
 }
 
+/*
+ * Add U45 environmental sensors that are not in the OFW tree.
+ */
+static void
+add_env_sensors_u45(device_t busdev)
+{
+	prop_array_t cfg;
+
+	DPRINTF(ACDB_PROBE, ("\nAdding sensors for %s ", machine_model));
+	cfg = create_i2c_dict(busdev);
+
+	/* LM95221 at 0x2b */
+	add_i2c_device(cfg, "temperature-sensor", "i2c-lm95221", 0x2b, 0);
+
+	/* NXP LM75A at 0x4f */
+	add_i2c_device(cfg, "temperature-sensor", "i2c-lm75a", 0x4f, 0);
+}
+
 /* Sensors and GPIO's for E450 and E250 */
 static void
 add_i2c_props_e450(device_t busdev, uint64_t node)
@@ -347,6 +383,44 @@
 	prop_object_release(cfg);
 }
 
+/*
+ * Fix-up U45 incorrect properties in the OFW tree.
+ */
+static void
+fix_properties_u45(device_t busdev)
+{
+	prop_dictionary_t props = device_properties(busdev);
+	prop_array_t cfg;
+	prop_object_t dev;
+	uint32_t addr;
+	const char *name;
+	int i, n;
+
+	cfg = prop_dictionary_get(props, "i2c-child-devices");
+	if (!cfg)
+		return;
+
+	n = prop_array_count(cfg);
+	for (i = 0; i < n; i++) {
+		dev = prop_array_get(cfg, i);
+		if (prop_object_type(dev) == PROP_TYPE_DICTIONARY &&
+		    prop_dictionary_get_uint32(dev, "addr", &addr) &&
+		    prop_dictionary_get_string(dev, "name", &name)) {
+			/* Change psu-fru-prom to standard 8k seeprom */
+			if (addr == 0x57)
+				prop_dictionary_set_data(dev, "compatible",
+				    "i2c-at24c64", strlen("i2c-at24c64") + 1);
+			/* Remove fake lm76 at addresses 2b, 48 and 4f */
+			if ((addr == 0x2b || addr == 0x48 || addr == 0x4f) &&
+			    !strcmp(name, "temperature")) {
+				prop_array_remove(cfg, i);
+				i -= 1;
+				n -= 1;
+			}
+		}
+	}
+}
+
 /* Hardware specific i2c bus properties */
 void
 set_i2c_bus_props(device_t busdev, uint64_t busnode)
@@ -360,6 +434,12 @@
 		    !strcmp(machine_model, "SUNW,Sun-Fire-V210"))
 			add_env_sensors_v210(busdev);
 
+		if (!strcmp(machine_model, "SUNW,A70") ||
+		    !strcmp(machine_model, "SUNW,Ultra-25")) {
+			add_env_sensors_u45(busdev);
+			fix_properties_u45(busdev);
+		}
+
 		/* E450 SUNW,envctrl */
 		if (!strcmp(machine_model, "SUNW,Ultra-4"))
 			add_i2c_props_e450(busdev, busnode);
@@ -373,7 +453,7 @@
 
 /* Hardware specific i2c device properties */
 void
-set_i2c_dev_props(device_t dev, void *aux)
+set_i2c_dev_props(device_t dev, device_t busdev, void *aux)
 {
 
 	if ((!strcmp(machine_model, "SUNW,Sun-Fire-V240") ||
@@ -389,12 +469,32 @@
 				prop_dictionary_set_uint8(props,
 				    "fan_mask", 0x08);
 		}
+
+		/* Sun use offsets from 2000 but range 1970 to 2069 */
+		if (device_is_a(dev, "dsrtc")) {
+			prop_dictionary_t props = device_properties(dev);
+			prop_dictionary_set_uint(props, "start-year", 2000);
+		}
 	}
 
+	/* U45 has 5 measured fans */
+	if (!strcmp(machine_model, "SUNW,A70")) {
+		if (device_is_a(dev, "adt7462sm")){
+			prop_dictionary_t props = device_properties(dev);
+			prop_dictionary_set_uint8(props, "fan_conf", 0x1f);
+		}
+	}
+
+	/* U45 pcagpio @ firei2c controls the front panel LED */
+	if (!strcmp(machine_model, "SUNW,A70") ||
+	    !strcmp(machine_model, "SUNW,Ultra-25"))
+		if (device_is_a(dev, "pcagpio") &&
+		    device_is_a(busdev, "firei2c"))
+			add_gpio_props_u45(dev, aux);
+
 	if (!strcmp(machine_model, "SUNW,Ultra-250"))
 		if (device_is_a(dev, "pcf8574io"))
 			add_gpio_props_e250(dev, aux);
-
 }
 
 /* Static EDID definitions */
--- src/sys/arch/sparc64/dev/fire_i2c.c.dist	2026-02-26 21:45:44.659987635 +0100
+++ src/sys/arch/sparc64/dev/fire_i2c.c	2026-05-29 20:57:23.359963615 +0200
@@ -0,0 +1,541 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/condvar.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/mutex.h>
+
+#include <machine/autoconf.h>
+#include <machine/openfirm.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <arch/sparc64/dev/fire_i2creg.h>
+
+#define FIREI2C_DEBUG 0
+#if FIREI2C_DEBUG > 0
+#define DPRINTF printf
+#else
+#define DPRINTF if (0) printf
+#endif
+
+#define FIREI2C_SCL_FREQ	100000
+#define FIREI2C_OWN_ADDR	0x08
+
+#define FIREI2C_CLK_FULL	0
+#define FIREI2C_CLK_HALF	1
+#define FIREI2C_CLK_TT		2
+#define FIREI2C_CLK_LEN		3
+
+struct firei2c_softc {
+	device_t sc_dev;
+	bus_space_tag_t sc_bustag;
+	bus_space_handle_t sc_regh;
+	int sc_node;
+	void *sc_inth;
+	uint8_t sc_i2c_clks[FIREI2C_CLK_LEN];
+	struct i2c_controller sc_i2c;
+	kmutex_t sc_mutex;
+	kcondvar_t sc_cv;
+};
+
+static int firei2c_match(device_t, cfdata_t, void *);
+static void firei2c_attach(device_t, device_t, void *);
+static uint8_t firei2c_clock(int);
+static int firei2c_stop(struct firei2c_softc *);
+static int firei2c_wait(struct firei2c_softc *, int);
+int firei2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
+    size_t, void *, size_t, int);
+static int firei2c_write(struct firei2c_softc *, const uint8_t *, size_t,
+    uint8_t *, size_t, int, int);
+static int firei2c_read(struct firei2c_softc *, uint8_t *, size_t, int, int);
+int firei2c_intr(void *);
+static uint8_t firei2c_reg_read(struct firei2c_softc *, bus_size_t);
+static void firei2c_reg_write(struct firei2c_softc *, bus_size_t, uint8_t);
+
+CFATTACH_DECL_NEW(firei2c, sizeof(struct firei2c_softc),
+	firei2c_match, firei2c_attach, NULL, NULL);
+
+static int
+firei2c_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct mainbus_attach_args *ma = aux;
+	char *compat;
+
+	if (strcmp(ma->ma_name, "i2c"))
+		return 0;
+
+	compat = prom_getpropstring(ma->ma_node, "compatible");
+	if (!strcmp(compat, "fire-i2c"))
+		return 1;
+
+	return 0;
+}
+
+static void
+firei2c_attach(device_t parent, device_t self, void *aux)
+{
+	struct firei2c_softc *sc = device_private(self);
+	struct mainbus_attach_args *ma = aux;
+	int  sysclk;
+
+	sc->sc_bustag = ma->ma_bustag;
+	sc->sc_node = ma->ma_node;
+	sc->sc_dev = self;
+
+	if (ma->ma_nreg != 1) {
+		aprint_error(": register count error (%d != 1)\n",
+		    ma->ma_nreg);
+		return;
+	}
+	if (ma->ma_reg[0].ur_len < FIREI2C_SRST) {
+		aprint_error(": register length error (%ld < %d\n",
+		    ma->ma_reg[0].ur_len, FIREI2C_SRST);
+		return;
+	}
+
+	if (bus_space_map(sc->sc_bustag, ma->ma_reg[0].ur_paddr,
+	    ma->ma_reg[0].ur_len, 0, &sc->sc_regh)) {
+		aprint_error(": failed to map registers\n");
+		return;
+	}
+
+	sc->sc_inth = NULL;
+/* XXX need to set up interrupt mapping before we can enable them here
+   Both i2c interrupts must go to the same leaf.
+
+	sc->sc_inth = bus_intr_establish(sc->sc_bustag, *ma->ma_interrupts,
+	    IPL_VM, firei2c_intr, sc);
+
+	if (sc->sc_inth == NULL) {
+		aprint_error(": failed to establish interrupt\n");
+		return;
+	}
+*/
+
+	aprint_normal(": addr %" PRIx64 ": Fire/MI2C i2c controller\n",
+	    ma->ma_reg[0].ur_paddr);
+/* XXX interrupts
+	aprint_normal_dev(self, "interrupting at %x\n", *ma->ma_interrupts);
+*/
+
+	/* Calculate clock, software reset, set our addr, enable interrupts */
+	sysclk = prom_getpropint(findroot(), "clock-frequency", 0);
+	sc->sc_i2c_clks[FIREI2C_CLK_FULL] = firei2c_clock(sysclk);
+	sc->sc_i2c_clks[FIREI2C_CLK_HALF] = firei2c_clock(sysclk / 2);
+	sc->sc_i2c_clks[FIREI2C_CLK_TT] = firei2c_clock(sysclk / 32);
+	firei2c_reg_write(sc, FIREI2C_SRST, FIREI2C_SRST_RST);
+	delay(1000);
+	firei2c_reg_write(sc, FIREI2C_CCR, sc->sc_i2c_clks[FIREI2C_CLK_FULL]);
+	firei2c_reg_write(sc, FIREI2C_ADDR,
+	    FIREI2C_OWN_ADDR << FIREI2C_ADDR_SHIFT);
+
+	/* i2c setup */
+	iic_tag_init(&sc->sc_i2c);
+	sc->sc_i2c.ic_cookie = sc;
+	sc->sc_i2c.ic_exec = firei2c_exec;
+
+	/* Synchronisation between exec and intr */
+	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_VM);
+	cv_init(&sc->sc_cv, "firei2c");
+
+	iicbus_attach(sc->sc_dev, &sc->sc_i2c);
+	return;
+}
+
+/*
+ * Sampling frequency: input clock divided by 2^N (N is bits 0:2)
+ * SCL frequency: sampling frequency divided by (M + 1) * 10 (bits 3:6)
+ * i2c SCL is max 100kHz, so find the closest frequency at or below that.
+ *
+ * If the system (JBus) clock frequency changes, we must change our clock.
+ * If increasing: after the JBus change, if decreasing: before the change.
+ * This is to avoid running the i2c clock faster than the specification.
+ */
+static uint8_t
+firei2c_clock(int sysclk)
+{
+	int p, f, best_p, best_f;
+	uint32_t sample, scl;
+	uint32_t min = FIREI2C_SCL_FREQ;
+	uint8_t clkval;
+
+	best_p = 0;
+	best_f = 0;
+	for (p = 0; p <= FIREI2C_CCR_POW2_MAX; p++) {
+		if (p)
+			sample = sysclk / (2 << (p - 1));
+		else
+			sample = sysclk;
+		for (f = 0; f <= FIREI2C_CCR_FACT_MAX; f++) {
+			scl = sample / ((f + 1) * 10);
+			if (scl <= FIREI2C_SCL_FREQ) {
+				if (FIREI2C_SCL_FREQ - scl < min) {
+					best_p = p;
+					best_f = f;
+					min = FIREI2C_SCL_FREQ - scl;
+				}
+				break;	/* Already at or below SCL_FREQ */
+			}
+		}
+		if (!min)	/* Exactly SCL_FREQ */
+			break;
+	}
+	clkval = best_p + (best_f << FIREI2C_CCR_FACT_SHIFT);
+	return clkval;
+}
+
+/* No interrupts for stop, so wait for idle */
+static int
+firei2c_stop(struct firei2c_softc *sc)
+{
+	int i;
+	uint8_t ctrl, val;
+
+	ctrl = FIREI2C_CTRL_ENAB | FIREI2C_CTRL_STP;
+	firei2c_reg_write(sc, FIREI2C_CTRL, ctrl);
+	for (i = 0; i < 500; i++) {
+		val = firei2c_reg_read(sc, FIREI2C_STAT);
+		if (val == FIREI2C_STAT_IDLE)
+			return 0;
+		delay(1000);
+	}
+	/* Clear the interrupt flag if we timed out */
+	ctrl = FIREI2C_CTRL_ENAB;
+	firei2c_reg_write(sc, FIREI2C_CTRL, ctrl);
+	return 1;
+}
+
+
+static int
+firei2c_wait(struct firei2c_softc *sc, int flags)
+{
+	volatile uint8_t ctrl;
+	int i;
+
+	if (sc->sc_inth == NULL || flags & I2C_F_POLL) {
+		for (i = 0; i < 500; i++) {
+			ctrl = firei2c_reg_read(sc, FIREI2C_CTRL);
+			if (ctrl & FIREI2C_CTRL_IFLG)
+				return 0;
+			delay(1000);
+		}
+		return 1;
+	} else {
+		mutex_enter(&sc->sc_mutex);
+		if (cv_timedwait(&sc->sc_cv, &sc->sc_mutex, hz / 2)) {
+			mutex_exit(&sc->sc_mutex);
+			return 1;
+		}
+		mutex_exit(&sc->sc_mutex);
+		return 0;
+	}
+}
+
+/*
+ * Flow for a "simple" read/write:
+ *   1.  Send start
+ *   2.  Send addr + R or W
+ *   3.  Receive ACK
+ *   4.  (nothing)
+ *   5.  (nothing)
+ *   6R. Receive data			6W.  Send data
+ *   7R. Send ACK or NAK		7W.  Receive ACK or NAK
+ *       either receive more (6R) or	     either write more (6W) or
+ *   8.  Send stop
+ * 
+ * Flow for read/write following a "register" write:
+ *   1.  Send start
+ *   2.  Send addr + W
+ *   3.  Receive ACK
+ *   4.  Send register
+ *   5R. Send repeat start + addr + R	5W.  (nothing to do)
+ *   6R. Receive data			6W.  Send data
+ *   7R. Send ACK or NAK		7W.  Receive ACK or NAK
+ *       either receive more (6R) or	     either write more (6W) or
+ *   8.  Send stop
+ *
+ * After each step, we receive an interrupt notifying us of the result.
+ * We check the status, set up the next step, then clear the interrupt flag.
+ */
+int
+firei2c_exec(void *arg, i2c_op_t op, i2c_addr_t addr, const void *cmd,
+    size_t cmdlen, void *vbuf, size_t buflen, int flags)
+{
+	struct firei2c_softc *sc = arg;
+	uint8_t *buf = vbuf;
+	uint8_t ctrl, val, ack, nack;
+	int err;
+
+	DPRINTF("%s: exec op: %d addr: 0x%x cmdlen: %d buflen: %d flags 0x%x\n",
+	    device_xname(sc->sc_dev), op, addr,
+	    (int) cmdlen, (int) buflen, flags);
+
+	/*
+	 * Send start
+ 	 * If we are writing, write addr, cmd, buf.
+ 	 * If we are reading, either:
+ 	 *   write addr, cmd, repeat-start, then read addr to buf, or:
+ 	 *   read addr to buf.
+	 * Send stop
+ 	 */
+
+	/* Control defaults - bus enabled, interrupts enabled */
+	ctrl = FIREI2C_CTRL_ENAB;
+	if (sc->sc_inth != NULL && !(flags & I2C_F_POLL))
+		ctrl |= FIREI2C_CTRL_IEN;
+
+	val = firei2c_reg_read(sc, FIREI2C_STAT);
+	if (val != FIREI2C_STAT_IDLE) {
+		/* Try sending stop */
+		DPRINTF("%s: not idle, sending stop\n",
+		    device_xname(sc->sc_dev));
+		err = firei2c_stop(sc);
+		if (err) {
+			printf("%s: not idle (0x%02x)\n",
+			    device_xname(sc->sc_dev), val);
+			return 1;
+		}
+	}
+
+	/* Send start */
+	val = ctrl | FIREI2C_CTRL_STA;
+	firei2c_reg_write(sc, FIREI2C_CTRL, val);
+	err = firei2c_wait(sc, flags);
+	val = firei2c_reg_read(sc, FIREI2C_STAT);
+	if (err) {
+		printf("%s: start timeout 0x%x\n",
+		    device_xname(sc->sc_dev), val);
+		goto stop;
+	}
+	if (val != FIREI2C_STAT_STA) {
+		printf("%s: start error 0x%x\n",
+		    device_xname(sc->sc_dev), val);
+		err = 1;
+		goto stop;
+	}
+
+	/* Address + r/w in data, then clear interrupt flag in ctrl */
+	val = (addr & 0x7f) << FIREI2C_DATA_SHIFT;
+	if (I2C_OP_WRITE_P(op) || cmdlen > 0) {
+		ack = FIREI2C_STAT_AWR_ACK;
+		nack = FIREI2C_STAT_AWR_NAK;
+	} else {
+		val |= 0x01;
+		ack = FIREI2C_STAT_ARE_ACK;
+		nack = FIREI2C_STAT_ARE_NAK;
+	}
+	firei2c_reg_write(sc, FIREI2C_DATA, val);
+	firei2c_reg_write(sc, FIREI2C_CTRL, ctrl);
+	err = firei2c_wait(sc, flags);
+	val = firei2c_reg_read(sc, FIREI2C_STAT);
+	if (err) {
+		printf("%s: addr timeout 0x%x\n",
+		    device_xname(sc->sc_dev), val);
+		goto stop;
+	}
+	if (val != ack) {
+		if (val != nack)	/* Don't print for NACK */
+			printf("%s: addr error 0x%x\n",
+			    device_xname(sc->sc_dev), val);
+		err = 1;
+		goto stop;
+	}
+
+	err = 0;
+	if (I2C_OP_READ_P(op)) {
+		if (cmdlen > 0) {
+			/* Write register */
+			err = firei2c_write(sc, cmd, cmdlen,
+			    NULL, 0, ctrl, flags);
+			if (err)
+				goto stop;
+
+			/* Send repeat start */
+			val = ctrl | FIREI2C_CTRL_STA;
+			firei2c_reg_write(sc, FIREI2C_CTRL, val);
+			err = firei2c_wait(sc, flags);
+			val = firei2c_reg_read(sc, FIREI2C_STAT);
+			if (err) {
+				printf("%s: repeat start timeout 0x%x\n",
+				    device_xname(sc->sc_dev), val);
+				goto stop;
+			}
+			if (val != FIREI2C_STAT_REPSTA) {
+				printf("%s: repeat start error 0x%x\n",
+				    device_xname(sc->sc_dev), val);
+				err = 1;
+				goto stop;
+			}
+
+			/* Address + r in data, then clear intr flag in ctrl */
+			val = (addr & 0x7f) << FIREI2C_DATA_SHIFT;
+			val |= 0x01;
+			ack = FIREI2C_STAT_ARE_ACK;
+			nack = FIREI2C_STAT_ARE_NAK;
+			firei2c_reg_write(sc, FIREI2C_DATA, val);
+			firei2c_reg_write(sc, FIREI2C_CTRL, ctrl);
+			err = firei2c_wait(sc, flags);
+			val = firei2c_reg_read(sc, FIREI2C_STAT);
+			if (err) {
+				printf("%s: addr timeout 0x%x\n",
+				    device_xname(sc->sc_dev), val);
+				goto stop;
+			}
+			if (val != ack) {
+				if (val != nack)
+					printf("%s: addr error 0x%x\n",
+					    device_xname(sc->sc_dev), val);
+				err = 1;
+				goto stop;
+			}
+		}
+
+		/* Read data */
+		err = firei2c_read(sc, buf, buflen, ctrl, flags);
+	} else
+		/* Write data or register + data */
+		err = firei2c_write(sc, cmd, cmdlen, buf, buflen, ctrl, flags);
+
+stop:
+	err |= firei2c_stop(sc);
+
+	return err;
+}
+
+static int
+firei2c_write(struct firei2c_softc *sc, const uint8_t *cmd, size_t cmdlen,
+    uint8_t *buf, size_t buflen, int ctrl, int flags)
+{
+	uint8_t val;
+	int i, err;
+
+	for (i = 0; i < cmdlen + buflen; i++) {
+		/* Bytes in data, then clear interrupt flag in ctrl */
+		if (i < cmdlen)
+			val = cmd[i];
+		else
+			val = buf[i - cmdlen];
+		firei2c_reg_write(sc, FIREI2C_DATA, val);
+		firei2c_reg_write(sc, FIREI2C_CTRL, ctrl);
+		err = firei2c_wait(sc, flags);
+		val = firei2c_reg_read(sc, FIREI2C_STAT);
+		if (err) {
+			printf("%s: write timeout 0x%x\n",
+			    device_xname(sc->sc_dev), val);
+			return 1;
+		}
+		if (val != FIREI2C_STAT_DAT_ACK) {
+			if (val != FIREI2C_STAT_DAT_NAK)
+				printf("%s: write error 0x%x\n",
+				    device_xname(sc->sc_dev), val);
+			return 1;
+		}
+
+	}
+	return 0;
+}
+
+static int
+firei2c_read(struct firei2c_softc *sc, uint8_t *buf, size_t buflen,
+    int ctrl, int flags)
+{
+	uint8_t val, ack;
+	int i, err;
+
+	for (i = 0; i < buflen; i++) {
+		/* Send ACK on all but last byte */
+		if (i < buflen - 1) {
+			val = ctrl | FIREI2C_CTRL_AAK;
+			ack = FIREI2C_STAT_MDAT_ACK;
+		} else {
+			val = ctrl;
+			ack = FIREI2C_STAT_MDAT_NAK;
+		}
+		/* Clear interrupt flag in ctrl, bytes in data */
+		firei2c_reg_write(sc, FIREI2C_CTRL, val);
+		err = firei2c_wait(sc, flags);
+		val = firei2c_reg_read(sc, FIREI2C_STAT);
+		if (err) {
+			printf("%s: read timeout 0x%x\n",
+			    device_xname(sc->sc_dev), val);
+			return 1;
+		}
+		if (val != ack) {
+			printf("%s: read error 0x%x\n",
+			    device_xname(sc->sc_dev), val);
+			return 1;
+		}
+		buf[i] = firei2c_reg_read(sc, FIREI2C_DATA);
+	}
+	return 0;
+}
+
+int
+firei2c_intr(void *arg)
+{
+	struct firei2c_softc *sc = arg;
+	uint8_t ctrl;
+
+	mutex_enter(&sc->sc_mutex);
+	ctrl = firei2c_reg_read(sc, FIREI2C_CTRL);
+	if (ctrl & FIREI2C_CTRL_IFLG) {
+		/* Disable interrupts */
+		firei2c_reg_write(sc, FIREI2C_CTRL, ctrl & ~FIREI2C_CTRL_IEN);
+		cv_signal(&sc->sc_cv);
+		mutex_exit(&sc->sc_mutex);
+		return 1;	/* Handled */
+	}
+	mutex_exit(&sc->sc_mutex);
+	return 0;
+}
+
+static uint8_t
+firei2c_reg_read(struct firei2c_softc *sc, bus_size_t reg)
+{
+	uint32_t val;
+	bus_space_barrier(sc->sc_bustag, sc->sc_regh, reg, 8,
+	    BUS_SPACE_BARRIER_READ);
+	val = bus_space_read_8(sc->sc_bustag, sc->sc_regh, reg);
+	return val & 0xff;
+}
+
+static void
+firei2c_reg_write(struct firei2c_softc *sc, bus_size_t reg, uint8_t val)
+{
+	bus_space_write_8(sc->sc_bustag, sc->sc_regh, reg, val);
+	bus_space_barrier(sc->sc_bustag, sc->sc_regh, reg, 8,
+	    BUS_SPACE_BARRIER_WRITE);
+}
--- src/sys/arch/sparc64/dev/fire_i2creg.h.dist	2026-02-26 21:45:44.659987635 +0100
+++ src/sys/arch/sparc64/dev/fire_i2creg.h	2026-05-23 09:14:41.729988745 +0200
@@ -0,0 +1,116 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIRE_I2C_H_
+#define _FIRE_I2C_H_
+
+/*
+ * Register definitions for "Mentor Grapics MI2C / Fire I2C"
+ * Information taken from:
+ * - Inventra MI2C Product Specification
+ * - Fire Programmer's Reference Manual for Fire 2.1
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+/* Register offsets */
+#define FIREI2C_ADDR		0x00	/* Slave address */
+#define FIREI2C_XADDR		0x08	/* Extended slave address */
+#define FIREI2C_DATA		0x10	/* Data byte */
+#define FIREI2C_CTRL		0x18	/* Control register */
+#define FIREI2C_STAT		0x20	/* Status register (RO) */
+#define FIREI2C_CCR		0x28	/* Clock cntrol register (WO) */
+#define FIREI2C_SRST		0x30	/* Software reset */
+
+/* 0x00 - Slave (our) address */
+#define FIREI2C_ADDR_GC_ENBL	0x01	/* General call address enable */
+#define FIREI2C_ADDR_7		0xfe	/* 7-bit addr address mask */
+#define FIREI2C_ADDR_SHIFT	1
+#define FIREI2C_XADDR_9		0x06	/* 9-bit address mask (bits 8 + 9) */
+#define FIREI2C_XADDR_9_SHIFT	8
+
+/* 0x08 - Extended slave address */
+#define FIREI2C_XADDR_7		0xff	/* 9-bit address mask (bits 0 - 7) */
+
+/* 0x10 - Data byte - sent or received on the bus (including slave addr) */
+#define FIREI2C_DATA_SHIFT	1
+
+/* 0x18 - Control register */
+#define FIREI2C_CTRL_AAK	0x04	/* Assert acknowledge */
+#define FIREI2C_CTRL_IFLG	0x08	/* Interrupt flag */
+#define FIREI2C_CTRL_STP	0x10	/* Master mode stop */
+#define FIREI2C_CTRL_STA	0x20	/* Master mode start */
+#define FIREI2C_CTRL_ENAB	0x40	/* Bus enable */
+#define FIREI2C_CTRL_IEN	0x80	/* Interrupt enable */
+
+/* 0x20 - Status register (RO) */
+#define FIREI2C_STAT_BERR	0x00	/* Bus error */
+#define FIREI2C_STAT_STA	0x08	/* Start transmitted */
+#define FIREI2C_STAT_REPSTA	0x10	/* Repeated start transmitted */
+#define FIREI2C_STAT_AWR_ACK	0x18	/* Addr + Write sent -> ACK rcvd */
+#define FIREI2C_STAT_AWR_NAK	0x20	/* Addr + Write sent -> no ACK rcvd */
+#define FIREI2C_STAT_DAT_ACK	0x28	/* Data sent = ACK rcvd */
+#define FIREI2C_STAT_DAT_NAK	0x30	/* Data sent = no ACK rcvd */
+#define FIREI2C_STAT_ARB_LST	0x38	/* Arbitration lost */
+#define FIREI2C_STAT_ARE_ACK	0x40	/* Addr + Read sent -> ACK rcvd */
+#define FIREI2C_STAT_ARE_NAK	0x48	/* Addr + Read sent -> no ACK rcvd */
+#define FIREI2C_STAT_MDAT_ACK	0x50	/* Data rcvd master -> ACK sent */
+#define FIREI2C_STAT_MDAT_NAK	0x58	/* Data rcvd master -> not ACK sent */
+#define FIREI2C_STAT_SLW_ACK	0x60	/* Slave + Write rcvd -> ACK sent */
+#define FIREI2C_STAT_ARB_SLW	0x68	/* Arb. lost, slv+wr rcvd, ACK sent */
+#define FIREI2C_STAT_GC_ACK	0x70	/* Gen. call rcvd, ACK sent */
+#define FIREI2C_STAT_ARB_GC	0x78	/* Arb. lost, gen call rcvd, ACK sent */
+#define FIREI2C_STAT_SDAT_ACK	0x80	/* Data rcvd slave -> ACK sent */
+#define FIREI2C_STAT_SDAT_NAK	0x88	/* Data rcvd slave -> not ACK sent */
+#define FIREI2C_STAT_GDAT_ACK	0x90	/* Data rcvd gen call -> ACK sent */
+#define FIREI2C_STAT_GDAT_NAK	0x98	/* Data rcvd gen call -> not ACK sent */
+#define FIREI2C_STAT_STP_REP	0xa0	/* Stop or Rep Start rcvd slave */
+#define FIREI2C_STAT_SLR_ACK	0xa8	/* Slave + Read rcvd -> ACK sent */
+#define FIREI2C_STAT_ARB_SLR	0xb0	/* Arb. lost, slv+rd rcvd, ACK sent */
+#define FIREI2C_STAT_SLV_ACK	0xb8	/* Data sent slave -> ACK rcvd */
+#define FIREI2C_STAT_SLV_NAK	0xc0	/* Data sent slave -> ACK not rcvd */
+#define FIREI2C_STAT_SLV_LAS	0xc8	/* Last byte sent slave -> ACK rcvd */
+#define FIREI2C_STAT_2AW_ACK	0xd0	/* 2nd Addr + Write sent -> ACK rcvd */
+#define FIREI2C_STAT_2AW_NAK	0xd8	/* 2nd Addr + Wr sent -> no ACK rcvd */
+#define FIREI2C_STAT_IDLE	0xf8	/* No relevant status (default) */
+
+/* 0x28 - Clock cntrol register (WO) */
+#define FIREI2C_CCR_POW2	0x07	/* Clock divider 1 = 2^N (sample clk) */
+#define FIREI2C_CCR_FACT	0x78	/* Clock divider 2 = M (i2c clock) */
+#define FIREI2C_CCR_FACT_SHIFT	3
+#define FIREI2C_CCR_POW2_MAX	7
+#define FIREI2C_CCR_FACT_MAX	15
+
+/* 0x30 - Software reset - write any value to reset */
+#define FIREI2C_SRST_RST	0x01
+
+#endif /* _FIRE_I2C_H_ */
--- src/sys/dev/i2c/adt7462.c.dist	2026-02-10 22:19:38.439986424 +0100
+++ src/sys/dev/i2c/adt7462.c	2026-05-28 10:23:14.979986293 +0200
@@ -0,0 +1,1525 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/sysctl.h>
+
+#include <dev/sysmon/sysmonvar.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/adt7462reg.h>
+
+/* Temperature sensors descriptions (envstat) */
+static const char* temp_descs[] =
+    { "local", "remote 1", "remote 2", "remote 3" };
+
+/* PWM control channel names (sysctl) */
+static const char* pwm_ctrl_names[] =
+    { "local", "remote1", "remote2", "remote3",
+    "off", "local_remote3", "all", "manual" };
+/* Temperature sensors names (sysctl) */
+static const char* temp_names[] =
+    { "local", "remote1", "remote2", "remote3" };
+
+#define ADT7462_DESC_LEN	SYSCTL_NAMELEN
+
+/* Voltage/analog sensors registers */
+struct adt7462_volts_regs {
+	uint8_t v_reg, h_reg, l_reg;
+};
+static struct adt7462_volts_regs adt7462_volts_table[] = {
+	{ ADT7462_PIN23V_VAL, ADT7462_PIN23V_HIGH, ADT7462_PIN23V_LOW },
+	{ ADT7462_PIN24V_VAL, ADT7462_PIN24V_HIGH, ADT7462_PIN24V_LOW },
+	{ ADT7462_PIN25V_VAL, ADT7462_PIN25V_HIGH, ADT7462_PIN25V_LOW },
+	{ ADT7462_PIN26V_VAL, ADT7462_PIN26V_HIGH, ADT7462_PIN26V_LOW },
+	{ ADT7462_15V1_VAL, ADT7462_15V1_HIGH, ADT7462_15V1_LOW }, /* Pin 28 */
+	{ ADT7462_15V2_VAL, ADT7462_15V2_HIGH, ADT7462_15V2_LOW }, /* Pin 29 */
+	{ ADT7462_33V_VAL, ADT7462_33V_HIGH, ADT7462_33V_LOW }, /* Pin 13 */
+	{ ADT7462_12V1_VAL, ADT7462_12V1_HIGH, ADT7462_12V1_LOW }, /* Pin 7 */
+	{ ADT7462_12V2_VAL, ADT7462_12V2_HIGH, ADT7462_12V2_LOW }, /* Pin 8 */
+	{ ADT7462_12V3_VAL, ADT7462_12V3_HIGH, ADT7462_12V3_LOW }, /* Pin 22 */
+	{ ADT7462_PIN19V_VAL, ADT7462_PIN19V_HIGH, ADT7462_PIN19V_LOW },
+	{ ADT7462_PIN15V_VAL, ADT7462_PIN15V_HIGH, ADT7462_PIN15V_LOW },
+	{ ADT7462_5V_VAL, ADT7462_5V_HIGH, ADT7462_5V_LOW }, /* Pin 21 */
+};
+
+/* 1 LSB values for voltages (datasheet table 16) in mV */
+#define ADT7462_SCALE_12V	62500
+#define ADT7462_SCALE_5V	26000
+#define ADT7462_SCALE_VCCP	6250
+#define ADT7462_SCALE_VVID	12500	/* Vccp when VID's enabled */
+#define ADT7462_SCALE_3_3V	17200
+#define ADT7462_SCALE_VBAT	15600
+#define ADT7462_SCALE_2_5V	13000
+#define ADT7462_SCALE_1_8V	9400
+#define ADT7462_SCALE_1_5V	7800
+#define ADT7462_SCALE_1_25V	6500
+#define ADT7462_SCALE_1_2V	6250
+#define ADT7462_SCALE_0_9V	4690
+
+/* Trange values (16 entries) rounded to the nearest integer */
+static const int trange_vals[] =
+    { 2, 2, 3, 4, 5, 7, 8, 10,
+    13, 16, 20, 27, 32, 40, 53, 80 };
+#define ADT7462_TRANGE_LEN	(sizeof(trange_vals) / sizeof(trange_vals[0]))
+
+/* Maximum number of each type of sensor */
+#define ADT7462_MAX_FANS	8
+#define ADT7462_MAX_TEMPS	4
+#define ADT7462_MAX_VOLTS	13
+#define ADT7462_MAX_FAULTS	1
+#define ADT7462_MAX_SENSORS	\
+    (ADT7462_MAX_FANS + ADT7462_MAX_TEMPS + \
+    ADT7462_MAX_VOLTS + ADT7462_MAX_FAULTS)
+#define  ADT7462_NUM_PWM	4
+
+/* Fan/temp/volt/fault sensor offsets */
+#define ADT7462_FAN_NUM(x)	(x)
+#define ADT7462_TEMP_NUM(x)	(x + ADT7462_MAX_FANS)
+#define ADT7462_VOLT_NUM(x)	(x + ADT7462_MAX_FANS + ADT7462_MAX_TEMPS)
+#define ADT7462_FAULT_NUM(x)	\
+    (x + ADT7462_MAX_FANS + ADT7462_MAX_TEMPS + ADT7462_MAX_VOLTS)
+
+/* Fan conversions */
+#define VAL_TO_SPEED(msb, lsb)  \
+    (ADT7462_TACH_PERIOD / ((msb << 8) + lsb))
+#define SPEED_TO_MSB(spd)	\
+    (((ADT7462_TACH_PERIOD / spd) >> 8) & 0xff)
+
+/* Temperature conversions */
+#define VAL_TO_TEMP(msb, lsb)	\
+    (ADT7462_TEMP_BASE + msb * 1000000 + (lsb >> 6) * 250000)
+#define TEMP_TO_MSB(temp)	\
+    (((temp - ADT7462_TEMP_BASE) / 1000000) & 0xff)
+
+/* Voltage conversions */
+#define VAL_TO_VOLT(val, scale)	\
+    (val * scale)
+#define VOLT_TO_MSB(volt, scale)	\
+    ((volt / scale) & 0xff)
+
+struct adt7462_softc {
+	device_t sc_dev;
+	i2c_tag_t sc_tag;
+	int sc_address;
+	bool sc_monitor;
+
+	int sc_nfans, sc_ntemps, sc_nvolts;
+	int sc_env_map[ADT7462_MAX_SENSORS];  /* Envsys numbers to sensors */
+	int sc_vscale[ADT7462_MAX_VOLTS];  /* Scale for voltages */
+	struct sysmon_envsys *sc_sme;
+	envsys_data_t sc_sensor[ADT7462_MAX_SENSORS];
+	uint8_t sc_therm1[ADT7462_MAX_SENSORS];
+	uint8_t sc_therm2[ADT7462_MAX_SENSORS];
+	int sc_crit_therm[ADT7462_MAX_SENSORS];
+	uint8_t sc_highlim[ADT7462_MAX_SENSORS];
+	uint8_t sc_lowlim[ADT7462_MAX_SENSORS];
+	uint8_t sc_fan_conf;
+	char sc_sys_ctrl[ADT7462_NUM_PWM][ADT7462_DESC_LEN]; /* sysctl str */
+	struct sysctllog *sc_sysctl_log;
+};
+
+static int adt7462_match(device_t, cfdata_t, void *);
+static int adt7462_ident(i2c_tag_t, i2c_addr_t, int, uint8_t*);
+static void adt7462_attach(device_t, device_t, void *);
+static int adt7462_detach(device_t, int);
+bool adt7462_pmf_suspend(device_t, const pmf_qual_t *);
+bool adt7462_pmf_resume(device_t, const pmf_qual_t *);
+
+static int adt7462_start_monitor(struct adt7462_softc *, int);
+static int adt7462_stop_monitor(struct adt7462_softc *);
+static int adt7462_setup_fans(struct adt7462_softc *, uint8_t *);
+static int adt7462_setup_temps(struct adt7462_softc *, uint8_t *);
+static int adt7462_setup_volts(struct adt7462_softc *, uint8_t *);
+static int adt7462_setup_faults(struct adt7462_softc *);
+static int adt7462_setup_sysctl(struct adt7462_softc *, uint8_t *);
+
+void adt7462_refresh(struct sysmon_envsys *, envsys_data_t *);
+static void adt7462_read_fan_val(struct adt7462_softc *, envsys_data_t *);
+static void adt7462_read_temp_val(struct adt7462_softc *, envsys_data_t *);
+static void adt7462_read_volt_val(struct adt7462_softc *, envsys_data_t *);
+static void adt7462_read_fault_val(struct adt7462_softc *, envsys_data_t *);
+
+void adt7462_get_limits(struct sysmon_envsys *, envsys_data_t *,
+    sysmon_envsys_lim_t *, uint32_t *);
+void adt7462_set_limits(struct sysmon_envsys *, envsys_data_t *,
+    sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_get_fan_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_get_temp_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_get_volt_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_set_fan_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_set_temp_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+static void adt7462_set_volt_limits(struct adt7462_softc *,
+    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
+
+static int adt7462_pwm_duty(SYSCTLFN_ARGS);
+static int adt7462_trange(SYSCTLFN_ARGS);
+static int adt7462_tmin(SYSCTLFN_ARGS);
+static int adt7462_op_point(SYSCTLFN_ARGS);
+
+static int adt7462_read_reg(i2c_tag_t, i2c_addr_t, uint8_t, uint8_t *);
+static int adt7462_write_reg(i2c_tag_t, i2c_addr_t, uint8_t, uint8_t);
+
+CFATTACH_DECL_NEW(adt7462sm, sizeof(struct adt7462_softc),
+	adt7462_match, adt7462_attach, adt7462_detach, NULL);
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "i2c-adt7462" },
+	DEVICE_COMPAT_EOL
+};
+
+static int
+adt7462_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct i2c_attach_args *ia = aux;
+	int match_result;
+	uint8_t rev;
+
+	if (iic_use_direct_match(ia, cf, compat_data, &match_result))
+		return match_result;
+
+	if ((ia->ia_addr == ADT7462_ADDR1 || ia->ia_addr == ADT7462_ADDR2)
+	    && adt7462_ident(ia->ia_tag, ia->ia_addr, 1, &rev))
+		return I2C_MATCH_ADDRESS_AND_PROBE;
+
+	return 0;
+}
+
+static int
+adt7462_ident(i2c_tag_t tag, i2c_addr_t addr, int probe_only, uint8_t *rev)
+{
+	uint8_t reg, val;
+	int err;
+
+	/* Device, company and revision ID */
+	reg = ADT7462_DEV_ID;
+	err = adt7462_read_reg(tag, addr, reg, &val);
+	if (err || val != ADT7462_DEV_ID_VAL) {
+		if (!probe_only)
+			aprint_verbose("adt7462_ident: "
+			    "device ID invalid or missing\n");
+		return 0;
+	}
+	reg = ADT7462_COMP_ID;
+	err = adt7462_read_reg(tag, addr, reg, &val);
+	if (err || val != ADT7462_COMP_ID_VAL) {
+		if (!probe_only)
+			aprint_verbose("adt7462_ident: "
+			    "company ID invalid or missing\n");
+		return 0;
+	}
+	reg = ADT7462_REV_ID;
+	err = adt7462_read_reg(tag, addr, reg, rev);
+	if (err || *rev != ADT7462_REV_ID_VAL) {
+		if (!probe_only)
+			aprint_verbose("adt7462_ident: "
+			    "revision invalid or missing\n");
+		return 0;
+	}
+	return 1;
+}
+
+static void
+adt7462_attach(device_t parent, device_t self, void *aux)
+{
+	struct adt7462_softc *sc = device_private(self);
+	struct i2c_attach_args *ia = aux;
+	prop_dictionary_t props = device_properties(self);
+	uint8_t reg, rev, val, pin_cfg[4];
+
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_address = ia->ia_addr;
+	sc->sc_dev = self;
+
+	/* Property override for the number of fans */
+	if (prop_dictionary_get_uint8(props, "fan_conf", &sc->sc_fan_conf) == 0)
+		sc->sc_fan_conf = 0xff;	/* 4 + 4 fans */
+
+	(void) adt7462_ident(sc->sc_tag, sc->sc_address, 0, &rev);
+	aprint_normal(": ADT7462 system monitor: rev. 0x%x\n", rev);
+
+	if (adt7462_start_monitor(sc, 1))
+		return;
+
+	/* Read the pin config registers for fan/temp/volt setup. */
+	reg = ADT7462_PIN_CONF1;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read pin conf1\n");
+		return;
+	}
+	pin_cfg[0] = val;
+	reg = ADT7462_PIN_CONF2;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read pin conf2\n");
+		return;
+	}
+	pin_cfg[1] = val;
+	reg = ADT7462_PIN_CONF3;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read pin conf3\n");
+		return;
+	}
+	pin_cfg[2] = val;
+	reg = ADT7462_PIN_CONF4;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read pin conf4\n");
+		return;
+	}
+	pin_cfg[3] = val;
+
+	sc->sc_sme = sysmon_envsys_create();
+
+	sc->sc_nfans = 0;
+	sc->sc_ntemps = 0;
+	sc->sc_nvolts = 0;
+	if (adt7462_setup_fans(sc, pin_cfg))
+		goto bad;
+	if (adt7462_setup_temps(sc, pin_cfg))
+		goto bad;
+	if (adt7462_setup_volts(sc, pin_cfg))
+		goto bad;
+	if (adt7462_setup_faults(sc))
+		goto bad;
+	aprint_normal_dev(self, "%d fans, %d temperatures, %d voltages\n",
+	    sc->sc_nfans, sc->sc_ntemps, sc->sc_nvolts);
+
+	sc->sc_sme->sme_name = device_xname(self);
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_refresh = adt7462_refresh;
+	sc->sc_sme->sme_get_limits = adt7462_get_limits;
+	sc->sc_sme->sme_set_limits = adt7462_set_limits;
+	if (sysmon_envsys_register(sc->sc_sme)) {
+		aprint_error_dev(self,
+		    "unable to register with sysmon\n");
+		goto bad;
+	}
+
+	if (!pmf_device_register(self,
+	    adt7462_pmf_suspend, adt7462_pmf_resume)) {
+		aprint_error_dev(self, "couldn't establish power handler\n");
+		goto bad2;
+	}
+
+	if (adt7462_setup_sysctl(sc, pin_cfg))
+		goto bad2;
+
+	return;
+
+bad2:
+	sysmon_envsys_unregister(sc->sc_sme);
+	sc->sc_sme = NULL;
+bad:
+	if (sc->sc_sme != NULL) {
+		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
+	return;
+}
+
+/* Stop (suspend/detach) and restart (resume) monitoring, if we started it. */
+bool
+adt7462_pmf_suspend(device_t dev, const pmf_qual_t *qual)
+{
+	struct adt7462_softc *sc = device_private(dev);
+
+	if (sc->sc_monitor == 1) {
+		if (adt7462_stop_monitor(sc))
+			return false;
+	}
+	return true;
+}
+
+bool
+adt7462_pmf_resume(device_t dev, const pmf_qual_t *qual)
+{
+	struct adt7462_softc *sc = device_private(dev);
+
+	if (sc->sc_monitor == 1) {
+		if (adt7462_start_monitor(sc, 0))
+			return false;
+	}
+	return true;
+}
+
+static int
+adt7462_detach(device_t self, int flags)
+{
+	struct adt7462_softc *sc = device_private(self);
+
+	pmf_device_deregister(self);
+
+	if (sc->sc_sme != NULL)
+		sysmon_envsys_unregister(sc->sc_sme);
+
+	if (sc->sc_monitor == 1) {
+		if (adt7462_stop_monitor(sc))
+			return 1;
+	}
+	return 0;
+}
+
+static int
+adt7462_start_monitor(struct adt7462_softc *sc, int print)
+{
+	uint8_t reg, val;
+
+	/*
+	 * Start monitoring if not already monitoring.
+	 * Wait 1.0s for the fan readings to stabilise.
+	 */
+	reg = ADT7462_CONF1;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read conf1\n");
+		return 1;
+	}
+	if (!(val & ADT7462_CONF1_MONITOR)) {
+		sc->sc_monitor = 1;
+		val |= ADT7462_CONF1_MONITOR;
+		if (adt7462_write_reg(sc->sc_tag, sc->sc_address,
+		    reg, val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    ": unable to write conf1\n");
+			return 1;
+		}
+		if (print)
+			aprint_normal_dev(sc->sc_dev,
+			    ": starting monitoring, "
+			    "waiting 1.0s for readings\n");
+		delay(1000000);
+	} else
+		sc->sc_monitor = 0;
+	return 0;
+}
+
+static int
+adt7462_stop_monitor(struct adt7462_softc *sc)
+{
+	uint8_t reg, val;
+
+	reg = ADT7462_CONF1;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to read conf1\n");
+		return 1;
+	}
+
+	val &= ~ADT7462_CONF1_MONITOR;
+	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
+		aprint_error_dev(sc->sc_dev, ": unable to write conf1\n");
+		return 1;
+	}
+	return 0;
+}
+
+static int
+adt7462_setup_fans(struct adt7462_softc *sc, uint8_t *pin_cfg)
+{
+	int i, map, snum;
+	uint8_t reg, val, fans;
+
+	/* Check tach enable register to see which tachs are enabled. */
+	reg = ADT7462_TACH_EN;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to read tach enable\n");
+		return 1;
+	}
+	fans = val & sc->sc_fan_conf;
+
+	/* Don't check the fan present register - it might not be set up. */
+
+	for (i = 0; i < ADT7462_MAX_FANS; i++) {
+		/* Check tach mask and pin1/pin2 configurations. */
+		if (!ADT7462_FAN_TACH_EN(fans, i) ||
+		    !ADT7462_PCR1_TACH(pin_cfg[0], i) ||
+		    !ADT7462_PCR2_TACH(pin_cfg[1], i))
+			continue;
+
+		snum = ADT7462_FAN_NUM(i);
+
+		/* Store initial limit */
+		reg = ADT7462_TACH_LIMIT(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read fan %d limit\n", i);
+			return 1;
+		}
+		sc->sc_highlim[snum] = val;
+
+		/* Set up sysmon sensor */
+		sc->sc_sensor[snum].units = ENVSYS_SFANRPM;
+		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
+		sc->sc_sensor[snum].flags = ENVSYS_FMONLIMITS;
+		snprintf(sc->sc_sensor[snum].desc,
+		    sizeof(sc->sc_sensor[snum].desc), "fan %d", snum);
+		if (sysmon_envsys_sensor_attach(
+		    sc->sc_sme, &sc->sc_sensor[snum])) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to attach fan %d at sysmon\n", i);
+			return 1;
+		}
+		map = sc->sc_sensor[snum].sensor;
+		sc->sc_env_map[map] = i;
+		sc->sc_nfans++;
+	}
+	return 0;
+}
+
+static int
+adt7462_setup_temps(struct adt7462_softc *sc, uint8_t *pin_cfg)
+{
+	int i, map, snum;
+	uint8_t reg, val, val2;
+
+	for (i = 0; i < ADT7462_MAX_TEMPS; i++) {
+		/* Check pin1 configurations. */
+		if (!ADT7462_PCR1_TEMP(pin_cfg[0], i))
+			continue;
+
+		snum = ADT7462_TEMP_NUM(i);
+
+		/* Store initial limits */
+		reg = ADT7462_TEMP_THERM1(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read temp %d therm1 limit\n", i);
+			return 1;
+		}
+		sc->sc_therm1[snum] = val;
+		reg = ADT7462_TEMP_THERM2(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val2) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read temp %d therm2 limit\n", i);
+			return 1;
+		}
+		sc->sc_therm2[snum] = val2;
+		/* Store lowest therm value as critmax. */
+		if (val <= val2)
+			sc->sc_crit_therm[snum] = 1;
+		else
+			sc->sc_crit_therm[snum] = 2;
+
+		reg = ADT7462_TEMP_HIGH(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read temp %d high limit\n", i);
+			return 1;
+		}
+		sc->sc_highlim[snum] = val;
+
+		reg = ADT7462_TEMP_LOW(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read temp %d low limit\n", i);
+			return 1;
+		}
+		sc->sc_lowlim[snum] = val;
+
+		/* Set up sysmon sensor */
+		strlcpy(sc->sc_sensor[snum].desc, temp_descs[i],
+			sizeof(sc->sc_sensor[snum].desc));
+
+		sc->sc_sensor[snum].units = ENVSYS_STEMP;
+		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
+		sc->sc_sensor[snum].flags =
+		    ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
+		if (sysmon_envsys_sensor_attach(
+		    sc->sc_sme, &sc->sc_sensor[snum])) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to attach temp %d at sysmon\n", i);
+			return 1;
+		}
+		map = sc->sc_sensor[snum].sensor;
+		sc->sc_env_map[map] = i;
+		sc->sc_ntemps++;
+	}
+	return 0;
+}
+
+static int
+adt7462_setup_volts(struct adt7462_softc *sc, uint8_t *pin_cfg)
+{
+	int i, map, snum;
+	uint8_t reg, val;
+	char desc[ADT7462_DESC_LEN];
+
+	for (i = 0; i < ADT7462_MAX_VOLTS; i++) {
+		snum = ADT7462_VOLT_NUM(i);
+
+		/* Invidual configuration for each sensor */
+		switch (i) {
+		case 0:		/* Pin 23 (always measures volts) */
+			if (ADT7462_PCR2_P23_25V(pin_cfg[1])) {
+				strcpy(desc, "V2.5 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
+			} else if (ADT7462_PCR2_P23_18V(pin_cfg[1])) {
+				strcpy(desc, "V1.8 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
+			} else if (ADT7462_PCR2_P23_15V(pin_cfg[1])) {
+				strcpy(desc, "V1.5 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
+			} else {
+				strcpy(desc, "Vccp 1");
+				if (ADT7462_PCR1_VIDS(pin_cfg[0]))
+					sc->sc_vscale[i] = ADT7462_SCALE_VVID;
+				else
+					sc->sc_vscale[i] = ADT7462_SCALE_VCCP;
+			}
+			break;
+		case 1:		/* Pin 24 (always measures volts) */
+			if (ADT7462_PCR3_P24_25V(pin_cfg[2])) {
+				strcpy(desc, "V2.5 2");
+				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
+			} else if (ADT7462_PCR3_P24_18V(pin_cfg[2])) {
+				strcpy(desc, "V1.8 2");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
+			} else if (ADT7462_PCR3_P24_15V(pin_cfg[2])) {
+				strcpy(desc, "V1.5 2");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
+			} else {
+				strcpy(desc, "Vccp 2");
+				if (ADT7462_PCR1_VIDS(pin_cfg[0]))
+					sc->sc_vscale[i] = ADT7462_SCALE_VVID;
+				else
+					sc->sc_vscale[i] = ADT7462_SCALE_VCCP;
+			}
+			break;
+		case 2:		/* Pin 25 (check pin config 3) */
+			if (ADT7462_PCR3_P25_33V(pin_cfg[2])) {
+				strcpy(desc, "V3.3 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_3_3V;
+			} else if (ADT7462_PCR3_P25_12V(pin_cfg[2])) {
+				strcpy(desc, "V1.2 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_2V;
+			} else
+				continue;	/* Not voltage */
+			break;
+		case 3:		/* Pin 26 (check pin config 3) */
+			if (ADT7462_PCR3_P26_VBAT(pin_cfg[2])) {
+				strcpy(desc, "Vbatt");
+				sc->sc_vscale[i] = ADT7462_SCALE_VBAT;
+			} else if (ADT7462_PCR3_P26_12V(pin_cfg[2])) {
+				strcpy(desc, "V1.2 2");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_2V;
+			} else
+				continue;	/* Not voltage */
+			break;
+		case 4:		/* Pin 28 (check pin config 1 and 4) */
+			if (ADT7462_PCR1_VIDS(pin_cfg[0]))
+				continue;
+			if (!ADT7462_PCR4_P28_15V(pin_cfg[3]))
+				continue;
+			strcpy(desc, "V1.5 3");
+			sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
+			break;
+		case 5:		/* Pin 29 (check pin config 1 and 4) */
+			if (ADT7462_PCR1_VIDS(pin_cfg[0]))
+				continue;
+			if (!ADT7462_PCR4_P29_15V(pin_cfg[3]))
+				continue;
+			strcpy(desc, "V1.5 4");
+			sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
+			break;
+		case 6:		/* Pin 13 (check pin config 2) */
+			if (ADT7462_PCR2_P13_PWM4(pin_cfg[1]))
+				continue;
+			strcpy(desc, "V3.3 2");
+			sc->sc_vscale[i] = ADT7462_SCALE_3_3V;
+			break;
+		case 7:		/* Pin 7 (check pin config 1) */
+			if (!ADT7462_PCR1_PIN7_V(pin_cfg[0]))
+				continue;
+			strcpy(desc, "V12 1");
+			sc->sc_vscale[i] = ADT7462_SCALE_12V;
+			break;
+		case 8:		/* Pin 8 (check pin config 2) */
+			if (ADT7462_PCR2_P8_TACH6(pin_cfg[1]))
+				continue;
+			strcpy(desc, "V12 2");
+			sc->sc_vscale[i] = ADT7462_SCALE_12V;
+			break;
+		case 9:		/* Pin 22 (check pin config 2) */
+			if (ADT7462_PCR2_P22_TACH8(pin_cfg[1]))
+				continue;
+			strcpy(desc, "V12 3");
+			sc->sc_vscale[i] = ADT7462_SCALE_12V;
+			break;
+		case 10:	/* Pin 19 (check pin config 1 and 2) */
+			if (!ADT7462_PCR1_PIN19_V(pin_cfg[0]))
+				continue;
+			if (ADT7462_PCR2_P19_09V(pin_cfg[1])) {
+				strcpy(desc, "V0.9 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_0_9V;
+			} else {
+				strcpy(desc, "V1.25 1");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_25V;
+			}
+			break;
+		case 11:	/* Pin 15 (check pin config 1 and 2) */
+			if (!ADT7462_PCR1_PIN15_V(pin_cfg[0]))
+				continue;
+			if (ADT7462_PCR2_P15_18V(pin_cfg[1])) {
+				strcpy(desc, "V1.8 3");
+				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
+			} else {
+				strcpy(desc, "V2.5 3");
+				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
+			}
+			break;
+		case 12:	/* Pin 21 (check pin config 2) */
+			if (ADT7462_PCR2_P21_TACH7(pin_cfg[1]))
+				continue;
+			strcpy(desc, "V5 1");
+			sc->sc_vscale[i] = ADT7462_SCALE_5V;
+			break;
+		}
+
+		/* Store initial limits */
+		reg = adt7462_volts_table[i].h_reg;
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read volt %d high limit\n", i);
+			return 1;
+		}
+		sc->sc_highlim[snum] = val;
+
+		reg = adt7462_volts_table[i].l_reg;
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read volt %d low limit\n", i);
+			return 1;
+		}
+		sc->sc_lowlim[snum] = val;
+
+		/* Set up sysmon sensor */
+		sc->sc_sensor[snum].units = ENVSYS_SVOLTS_DC;
+		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
+		sc->sc_sensor[snum].flags = ENVSYS_FMONLIMITS;
+		strlcpy(sc->sc_sensor[snum].desc, desc,
+		    sizeof(sc->sc_sensor[snum].desc));
+		if (sysmon_envsys_sensor_attach(
+		    sc->sc_sme, &sc->sc_sensor[snum])) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to attach volts %d at sysmon\n", i);
+			return 1;
+		}
+		map = sc->sc_sensor[snum].sensor;
+		sc->sc_env_map[map] = i;
+		sc->sc_nvolts++;
+	}
+	return 0;
+}
+
+static int
+adt7462_setup_faults(struct adt7462_softc *sc)
+{
+	int map, snum;
+
+	snum = ADT7462_FAULT_NUM(0);
+
+	/* Set up sysmon sensor */
+	strlcpy(sc->sc_sensor[snum].desc, "fan fault",
+	    sizeof(sc->sc_sensor[snum].desc));
+	sc->sc_sensor[snum].units = ENVSYS_INTEGER;
+	sc->sc_sensor[snum].state = ENVSYS_SINVALID;
+	sc->sc_sensor[snum].flags = ENVSYS_FMONCRITICAL;
+	if (sysmon_envsys_sensor_attach(
+	    sc->sc_sme, &sc->sc_sensor[snum])) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to attach fan fault at sysmon\n");
+		return 1;
+	}
+	map = sc->sc_sensor[snum].sensor;
+	sc->sc_env_map[map] = 0;
+	return 0;
+}
+
+static int
+adt7462_setup_sysctl(struct adt7462_softc *sc, uint8_t *pin_cfg)
+{
+	int i, sysnum, temp_is_pwm[ADT7462_MAX_TEMPS];
+	uint8_t reg, val, val2, rw;
+	char name[ADT7462_DESC_LEN], desc[ADT7462_DESC_LEN];
+	const struct sysctlnode *root, *branch, *node;
+
+	/* Default temperatures to manual */
+	for (i = 0; i < ADT7462_MAX_TEMPS; i++)
+		temp_is_pwm[i] = 0;
+
+	/* Root node */
+	root = NULL;
+	sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &root,
+	    CTLFLAG_READWRITE, CTLTYPE_NODE,
+	    device_xname(sc->sc_dev), NULL,
+	    NULL, 0, NULL, 0,
+	    CTL_HW, CTL_CREATE, CTL_EOL);
+	if (root == NULL) {
+		aprint_error_dev(sc->sc_dev, "unable to add sysctl root\n");
+		return 1;
+	}
+
+	/* PWM settings */
+	for (i = 0; i < ADT7462_NUM_PWM; i++) {
+		/* PWM root */
+		snprintf(name, sizeof(name), "pwm%d", i + 1);
+		snprintf(desc, sizeof(desc), "PWM output %d", i + 1);
+		branch = NULL;
+		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &branch,
+		    CTLFLAG_READWRITE, CTLTYPE_NODE,
+		    name, SYSCTL_DESCR(desc),
+		    NULL, 0, NULL, 0,
+		    CTL_HW, root->sysctl_num, CTL_CREATE, CTL_EOL);
+		if (branch == NULL) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to add sysctl pwm %d\n", i + 1);
+			return 1;
+		}
+		/* Encode the PWM id in the sysctl_num */
+		sysnum = 256;
+
+		reg = ADT7462_PWM_CFG(i);
+		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+		    reg, &val) != 0) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to read pwm %d configuration\n", i + 1);
+			return 1;
+		}
+		val &= ADT7462_PWM_BHVR_MASK;
+
+		/* PWM duty cycle (RW) if control channel is manual */
+		if (val == ADT7462_PWM_BHVR_MAN) {
+			node = NULL;
+			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
+			    CTLFLAG_READWRITE, CTLTYPE_INT,
+			    "duty_cycle", SYSCTL_DESCR("PWM duty cycle"),
+			    adt7462_pwm_duty, 1, (void *)sc, 0,
+			    CTL_HW, root->sysctl_num, branch->sysctl_num,
+			    sysnum + i, CTL_EOL);
+			if (node == NULL) {
+				aprint_error_dev(sc->sc_dev,
+				    "unable to add sysctl pwm duty %d\n",
+				    i + 1);
+				return 1;
+			}
+		}
+		sysnum *= 2;
+
+		/* Mark temperatures as auto/dynamic */
+		switch (val) {
+		case ADT7462_PWM_BHVR_LOCAL:
+			temp_is_pwm[0] = 1;
+			break;
+		case ADT7462_PWM_BHVR_REM1:
+			temp_is_pwm[1] = 1;
+			break;
+		case ADT7462_PWM_BHVR_REM2:
+			temp_is_pwm[2] = 1;
+			break;
+		case ADT7462_PWM_BHVR_REM3:
+			temp_is_pwm[3] = 1;
+			break;
+		case ADT7462_PWM_BHVR_LR3:
+			temp_is_pwm[0] = 1;
+			temp_is_pwm[3] = 1;
+			break;
+		case ADT7462_PWM_BHVR_ALL:
+			temp_is_pwm[0] = 1;
+			temp_is_pwm[1] = 1;
+			temp_is_pwm[2] = 1;
+			temp_is_pwm[3] = 1;
+			break;
+		}
+
+		/* Base control channel name */
+		val2 = (val & ADT7462_PWM_BHVR_MASK) >> ADT7462_PWM_BHVR_SHFT;
+		strlcpy(sc->sc_sys_ctrl[i], pwm_ctrl_names[val2],
+		    sizeof(sc->sc_sys_ctrl[i]));
+
+		/* Remote 1 and 2 can have dynamic Tmin */
+		if (val == ADT7462_PWM_BHVR_REM1 ||
+		    val == ADT7462_PWM_BHVR_REM2) {
+			reg = ADT7462_TMIN_CAL1;
+			if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
+			    reg, &val2) != 0) {
+				aprint_error_dev(sc->sc_dev,
+				    "unable to read tmin control\n");
+				return 1;
+			}
+			if (val == ADT7462_PWM_BHVR_REM1 &&
+			    (val2 & ADT7462_REM1_EN)) {
+				temp_is_pwm[1] = 2;
+				strlcat(sc->sc_sys_ctrl[i], "_dynamic",
+				    sizeof(sc->sc_sys_ctrl[i]));
+			}
+			if (val == ADT7462_PWM_BHVR_REM2 &&
+			    (val2 & ADT7462_REM2_EN)) {
+				temp_is_pwm[2] = 2;
+				strlcat(sc->sc_sys_ctrl[i], "_dynamic",
+				    sizeof(sc->sc_sys_ctrl[i]));
+			}
+		}
+
+		/* Control description (RO) */
+		node = NULL;
+		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
+		    CTLFLAG_READONLY, CTLTYPE_STRING,
+		    "channel", SYSCTL_DESCR("PWM control channel"),
+		    NULL, 0, sc->sc_sys_ctrl[i], 0,
+		    CTL_HW, root->sysctl_num, branch->sysctl_num,
+		    sysnum + i, CTL_EOL);
+		if (node == NULL) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to add sysctl pwm channel %d\n", i + 1);
+			return 1;
+		}
+	}
+
+	/* Temperature sensor settings */
+	for (i = 0; i < ADT7462_MAX_TEMPS; i++) {
+		/* Check pin1 configurations. */
+		if (!ADT7462_PCR1_TEMP(pin_cfg[0], i))
+			continue;
+
+		/* Temperature root */
+		snprintf(desc, sizeof(desc), "Temperature sensor %s",
+		    temp_descs[i]);
+		branch = NULL;
+		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &branch,
+		    CTLFLAG_READWRITE, CTLTYPE_NODE,
+		    temp_names[i], SYSCTL_DESCR(desc),
+		    NULL, 0, NULL, 0,
+		    CTL_HW, root->sysctl_num, CTL_CREATE, CTL_EOL);
+		if (branch == NULL) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to add sysctl temp %s\n", temp_names[i]);
+			return 1;
+		}
+		/* Encode the PWM id in the sysctl_num */
+		sysnum = 256;
+
+		/* Automatic = Tmin (RW) / Dynamic = Tmin (RO) */
+		if (temp_is_pwm[i] > 0) {
+			if (temp_is_pwm[i] == 1)
+				rw = CTLFLAG_READWRITE;
+			else
+				rw = CTLFLAG_READONLY;
+			node = NULL;
+			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
+			    rw, CTLTYPE_INT,
+			    "tmin", SYSCTL_DESCR("Temperature minimum"),
+			    adt7462_tmin, 0, (void *)sc, 0,
+			    CTL_HW, root->sysctl_num, branch->sysctl_num,
+			    sysnum + i, CTL_EOL);
+			if (node == NULL) {
+				aprint_error_dev(sc->sc_dev,
+				    "unable to add sysctl %s tmin\n",
+				    temp_names[i]);
+				return 1;
+			}
+		}
+		sysnum *= 2;
+
+		/* Automatic/Dynamic = Trange (RW) */
+		if (temp_is_pwm[i]) {
+			node = NULL;
+			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
+			    CTLFLAG_READWRITE, CTLTYPE_INT,
+			    "trange", SYSCTL_DESCR("Temperature range"),
+			    adt7462_trange, 0, (void *)sc, 0,
+			    CTL_HW, root->sysctl_num, branch->sysctl_num,
+			    sysnum + i, CTL_EOL);
+			if (node == NULL) {
+				aprint_error_dev(sc->sc_dev,
+				    "unable to add sysctl %s trange\n",
+				    temp_names[i]);
+				return 1;
+			}
+		}
+		sysnum *= 2;
+
+		/* Dynamic Tmin = operating point (RW) */
+		if (temp_is_pwm[i] == 2) {
+			node = NULL;
+			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
+			    CTLFLAG_READWRITE, CTLTYPE_INT,
+			    "oppoint", SYSCTL_DESCR("Operating point"),
+			    adt7462_op_point, 0, (void *)sc, 0,
+			    CTL_HW, root->sysctl_num, branch->sysctl_num,
+			    sysnum + i, CTL_EOL);
+			if (node == NULL) {
+				aprint_error_dev(sc->sc_dev,
+				    "unable to add sysctl %s op point\n",
+				    temp_names[i]);
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+adt7462_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
+{
+	struct adt7462_softc *sc = sme->sme_cookie;
+
+	if (edata->sensor < sc->sc_nfans)
+		adt7462_read_fan_val(sc, edata);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
+		adt7462_read_temp_val(sc, edata);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
+		adt7462_read_volt_val(sc, edata);
+	else
+		adt7462_read_fault_val(sc, edata);
+}
+
+static void
+adt7462_read_fan_val(struct adt7462_softc *sc, envsys_data_t *edata)
+{
+	int fan = sc->sc_env_map[edata->sensor];
+	uint8_t	reg, lsb, msb;
+
+	/* Read LSB then MSB to ensure correct reading */
+	reg = ADT7462_TACH_VAL_LSB(fan);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &lsb) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+	reg += 1;	/* MSB register is always LSB register + 1 */
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &msb) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+	if ((msb == 0xff && lsb == 0xff) || (msb == 0x00 && lsb == 0x00))
+		/* Fan missing or stopped */
+		edata->value_cur = 0;
+	else
+		edata->value_cur = VAL_TO_SPEED(msb, lsb);
+	edata->state = ENVSYS_SVALID;
+}
+
+static void
+adt7462_read_temp_val(struct adt7462_softc *sc, envsys_data_t *edata)
+{
+	int temp = sc->sc_env_map[edata->sensor];
+	uint8_t	reg, lsb, msb;
+
+	/* Read LSB then MSB to ensure correct reading */
+	reg = ADT7462_TEMP_LSB(temp);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &lsb) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+	reg += 1;	/* MSB register is always LSB register + 1 */
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &msb) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+
+	edata->value_cur = VAL_TO_TEMP(msb, lsb);
+	edata->state = ENVSYS_SVALID;
+}
+
+static void
+adt7462_read_volt_val(struct adt7462_softc *sc, envsys_data_t *edata)
+{
+	int volt = sc->sc_env_map[edata->sensor];
+	uint8_t	reg, val;
+
+	reg = adt7462_volts_table[volt].v_reg;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+	edata->value_cur = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
+	edata->state = ENVSYS_SVALID;
+}
+
+static void
+adt7462_read_fault_val(struct adt7462_softc *sc, envsys_data_t *edata)
+{
+	uint8_t	reg, val;
+	int32_t which, total;
+	int i, j;
+
+	reg = ADT7462_FAN_STAT_H;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		edata->state = ENVSYS_SINVALID;
+		return;
+	}
+	val &= sc->sc_fan_conf;
+
+	/* Change our status based on zero or non-zero value */
+	if (val != 0)
+		edata->state = ENVSYS_SCRITICAL;
+	else
+		edata->state = ENVSYS_SVALID;
+
+	/* Create an 8-digit integer for fan positions (0 OK, 1 fault) */
+	total = 0;
+	for (i = 0; i < 8; i++)
+		if (val & (1 << i)) {
+			which = 1;
+			for (j = 0; j < i; j++)
+				which *= 10;
+			total += which;
+		}
+	if (total != edata->value_cur)
+		aprint_normal_dev(sc->sc_dev,
+		    "fan fault status change: %08d -> %08d\n",
+		    edata->value_cur, total);
+	edata->value_cur = total;
+}
+
+void
+adt7462_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	struct adt7462_softc *sc = sme->sme_cookie;
+
+	if (edata->sensor < sc->sc_nfans)
+		adt7462_get_fan_limits(sc, edata, limits, props);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
+		adt7462_get_temp_limits(sc, edata, limits, props);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
+		adt7462_get_volt_limits(sc, edata, limits, props);
+}
+
+static void
+adt7462_get_fan_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int fan = sc->sc_env_map[edata->sensor];
+	uint8_t	reg, val;
+
+	/* The chip measures intervals, so the limit is for low speed. */
+	*props &= ~PROP_WARNMIN;
+
+	reg = ADT7462_TACH_LIMIT(fan);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	if (val == 0x00)	/* No limit */
+		return;
+	limits->sel_warnmin = VAL_TO_SPEED(val, 0);
+	*props |= PROP_WARNMIN;
+}
+
+static void
+adt7462_get_temp_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int temp, snum;
+	uint8_t	reg, val;
+
+	temp = sc->sc_env_map[edata->sensor];
+	snum = ADT7462_TEMP_NUM(temp);
+
+	*props &= ~(PROP_CRITMAX | PROP_WARNMAX | PROP_WARNMIN);
+
+	if (sc->sc_crit_therm[snum] == 1)
+		reg = ADT7462_TEMP_THERM1(temp);
+	else
+		reg = ADT7462_TEMP_THERM2(temp);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	limits->sel_critmax = VAL_TO_TEMP(val, 0);
+	*props |= PROP_CRITMAX;
+
+	reg = ADT7462_TEMP_HIGH(temp);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	limits->sel_warnmax = VAL_TO_TEMP(val, 0);
+	*props |= PROP_WARNMAX;
+
+	reg = ADT7462_TEMP_LOW(temp);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	limits->sel_warnmin = VAL_TO_TEMP(val, 0);
+	*props |= PROP_WARNMIN;
+}
+
+static void
+adt7462_get_volt_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int volt = sc->sc_env_map[edata->sensor];
+	uint8_t	reg, val;
+
+	*props &= ~(PROP_WARNMAX | PROP_WARNMIN);
+
+	reg = adt7462_volts_table[volt].h_reg;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	limits->sel_warnmax = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
+	*props |= PROP_WARNMAX;
+
+	reg = adt7462_volts_table[volt].l_reg;
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
+		return;
+	limits->sel_warnmin = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
+	*props |= PROP_WARNMIN;
+}
+
+void
+adt7462_set_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	struct adt7462_softc *sc = sme->sme_cookie;
+
+	if (edata->sensor < sc->sc_nfans)
+		adt7462_set_fan_limits(sc, edata, limits, props);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
+		adt7462_set_temp_limits(sc, edata, limits, props);
+	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
+		adt7462_set_volt_limits(sc, edata, limits, props);
+}
+
+static void
+adt7462_set_fan_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int fan, snum;
+	uint8_t	reg, val;
+
+	fan = sc->sc_env_map[edata->sensor];
+	snum = ADT7462_FAN_NUM(fan);
+
+	if (limits == NULL || *props & PROP_WARNMIN) {
+		if (limits == NULL)	/* Restore defaults */
+			val = sc->sc_highlim[snum];
+		else {
+			if (limits->sel_warnmin == 0)
+				val = 0xff;
+			else
+				val = SPEED_TO_MSB(limits->sel_warnmin);
+		}
+		reg = ADT7462_TACH_LIMIT(fan);
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+}
+
+static void
+adt7462_set_temp_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int temp, snum;
+	uint8_t	reg, val;
+
+	temp = sc->sc_env_map[edata->sensor];
+	snum = ADT7462_TEMP_NUM(temp);
+
+	if (limits == NULL || *props & PROP_CRITMAX) {
+		if (limits == NULL) {	/* Restore defaults */
+			if (sc->sc_crit_therm[snum] == 1)
+				val = sc->sc_therm1[snum];
+			else
+				val = sc->sc_therm2[snum];
+		} else {
+			val = TEMP_TO_MSB(limits->sel_critmax);
+		}
+		/* Don't change order of therm limits */
+		if (sc->sc_crit_therm[snum] == 1) {
+			reg = ADT7462_TEMP_THERM1(temp);
+			if (val > sc->sc_therm2[snum])
+				val = sc->sc_therm2[snum];
+			sc->sc_therm1[snum] = val;
+		} else {
+			reg = ADT7462_TEMP_THERM2(temp);
+			if (val > sc->sc_therm1[snum])
+				val = sc->sc_therm1[snum];
+			sc->sc_therm2[snum] = val;
+		}
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+
+	if (limits == NULL || *props & PROP_WARNMAX) {
+		if (limits == NULL)	/* Restore defaults */
+			val = sc->sc_highlim[snum];
+		else {
+			val = TEMP_TO_MSB(limits->sel_warnmax);
+		}
+		reg = ADT7462_TEMP_HIGH(temp);
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+
+	if (limits == NULL || *props & PROP_WARNMIN) {
+		if (limits == NULL)	/* Restore defaults */
+			val = sc->sc_lowlim[snum];
+		else {
+			val = TEMP_TO_MSB(limits->sel_warnmin);
+		}
+		reg = ADT7462_TEMP_LOW(temp);
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+}
+
+static void
+adt7462_set_volt_limits(struct adt7462_softc *sc, envsys_data_t *edata,
+	sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	int volt, snum;
+	uint8_t	reg, val;
+
+	volt = sc->sc_env_map[edata->sensor];
+	snum = ADT7462_VOLT_NUM(volt);
+
+	if (limits == NULL || *props & PROP_WARNMAX) {
+		if (limits == NULL)	/* Restore defaults */
+			val = sc->sc_highlim[snum];
+		else {
+			val = VOLT_TO_MSB(limits->sel_warnmax,
+			    sc->sc_vscale[volt]);
+		}
+		reg = adt7462_volts_table[volt].h_reg;
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+
+	if (limits == NULL || *props & PROP_WARNMIN) {
+		if (limits == NULL)	/* Restore defaults */
+			val = sc->sc_lowlim[snum];
+		else {
+			val = VOLT_TO_MSB(limits->sel_warnmin,
+			    sc->sc_vscale[volt]);
+		}
+		reg = adt7462_volts_table[volt].l_reg;
+		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
+	}
+}
+
+static int
+adt7462_pwm_duty(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node = *rnode;
+	struct adt7462_softc *sc = node.sysctl_data;
+	uint8_t reg, val;
+	int sysval, err;
+
+	/* Register from sysctl number */
+	val = node.sysctl_num & 0xff;
+	reg = ADT7462_PWM_DUTY(val);
+
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read pwm duty cycle\n");
+		return EAGAIN;
+	}
+	sysval = val * 100 / 255;
+	node.sysctl_data = &sysval;
+	err = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (err || newp == NULL)
+		return err;
+
+	/* Write a new value */
+	sysval = *(int *)node.sysctl_data;
+	if (sysval < 0 || sysval > 100)
+		return EINVAL;
+	val = (sysval * 255 / 100) & 0xff;
+	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to write pwm duty cycle\n");
+		return EAGAIN;
+	}
+	return 0;
+}
+
+static int
+adt7462_trange(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node = *rnode;
+	struct adt7462_softc *sc = node.sysctl_data;
+	uint8_t reg, val, old, new;
+	int sysval, err, i;
+
+	/* Register from sysctl number */
+	val = node.sysctl_num & 0xff;
+	reg = ADT7462_HYST_TRANGE(val);
+
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to read trange\n");
+		return EAGAIN;
+	}
+	old = (val & ADT7462_HTR_RNGE_MASK) >> ADT7462_HTR_RNGE_SHFT;
+	sysval = trange_vals[old];
+	node.sysctl_data = &sysval;
+	err = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (err || newp == NULL)
+		return err;
+
+	/* Write a new value */
+	sysval = *(int *)node.sysctl_data;
+	/* Convert new value to trange value (and add in lower 4 bits) */
+	if (sysval < trange_vals[0] ||
+	    sysval > trange_vals[ADT7462_TRANGE_LEN - 1])
+		return EINVAL;
+	new = 12;	/* Default Trange */
+	for (i = 0; i < ADT7462_TRANGE_LEN; i++)
+		if (sysval >= trange_vals[i])
+			new = i;
+	new <<= ADT7462_HTR_RNGE_SHFT;
+	new |= (val & ADT7462_HTR_HYST_MASK);
+	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, new) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to write trange\n");
+		return EAGAIN;
+	}
+	return 0;
+}
+
+static int
+adt7462_tmin(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node = *rnode;
+	struct adt7462_softc *sc = node.sysctl_data;
+	uint8_t reg, val;
+	int temp, sysval, err;
+
+	/* Register from sysctl number */
+	temp = node.sysctl_num & 0xff;
+	reg = ADT7462_TMIN(temp);
+
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to read tmin\n");
+		return EAGAIN;
+	}
+	sysval = val - ADT7462_TEMP_OFFSET;
+	node.sysctl_data = &sysval;
+	err = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (err || newp == NULL)
+		return err;
+
+	/* Write a new value */
+	sysval = *(int *)node.sysctl_data;
+	if (sysval < 0)
+		return EINVAL;
+	val = (sysval + ADT7462_TEMP_OFFSET) & 0xff;
+	/* Tmin needs to be below the therm limits */
+	if (sc->sc_crit_therm[temp] == 1) {
+		if (val > sc->sc_therm1[temp] - 1)
+			return EINVAL;
+	} else {
+		if (val > sc->sc_therm2[temp] - 1)
+			return EINVAL;
+	}
+	/* If we have RW tmin, we don't have op. point, so don't check it */
+	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to write tmin\n");
+		return EAGAIN;
+	}
+	return 0;
+}
+
+static int
+adt7462_op_point(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node = *rnode;
+	struct adt7462_softc *sc = node.sysctl_data;
+	uint8_t reg, val, mreg, mval;
+	int temp, sysval, err;
+
+	/* Register from sysctl number */
+	temp = node.sysctl_num & 0xff;
+	reg = ADT7462_OP_POINT(temp);
+
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to read op point\n");
+		return EAGAIN;
+	}
+	sysval = val - ADT7462_TEMP_OFFSET;
+	node.sysctl_data = &sysval;
+	err = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (err || newp == NULL)
+		return err;
+
+	/* Write a new value */
+	sysval = *(int *)node.sysctl_data;
+	if (sysval < 1)
+		return EINVAL;
+	val = (sysval + ADT7462_TEMP_OFFSET) & 0xff;
+	/* Operating point should be below the therm limits */
+	if (sc->sc_crit_therm[temp] == 1) {
+		if (val > sc->sc_therm1[temp] - 1)
+			return EINVAL;
+	} else {
+		if (val > sc->sc_therm2[temp] - 1)
+			return EINVAL;
+	}
+	/* Operating point should be above Tmin */
+	mreg = ADT7462_TMIN(temp);
+	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, mreg, &mval) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to read tmin\n");
+		return EAGAIN;
+	}
+	if (val < mval + 1)
+		return EINVAL;
+	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
+		aprint_error_dev(sc->sc_dev, "unable to write op point\n");
+		return EAGAIN;
+	}
+	return 0;
+}
+
+static int
+adt7462_read_reg(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t *val)
+{
+	int err = 0;
+
+	if ((err = iic_acquire_bus(tag, 0)) != 0)
+		return err;
+	err = iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &reg, 1, val, 1, 0);
+	iic_release_bus(tag, 0);
+	return err;
+}
+
+static int
+adt7462_write_reg(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t val)
+{
+	int err = 0;
+
+	if ((err = iic_acquire_bus(tag, 0)) != 0)
+		return err;
+	err = iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &reg, 1, &val, 1, 0);
+	iic_release_bus(tag, 0);
+	return err;
+}
--- src/sys/dev/i2c/adt7462reg.h.dist	2026-02-10 22:19:43.319986421 +0100
+++ src/sys/dev/i2c/adt7462reg.h	2026-02-24 12:15:28.119982280 +0100
@@ -0,0 +1,709 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DEV_I2C_ADT7462REG_H_
+#define _DEV_I2C_ADT7462REG_H_
+
+/*
+ * Register definitions for "ADT7462 Flexible Temperature, Voltage Monitor,
+ * and System Fan Controller".
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#define ADT7462_ADDR1		0x58
+#define ADT7462_ADDR2		0x5c
+
+#define ADT7462_CONF0		0x00
+#define ADT7462_CONF1		0x01
+#define ADT7462_CONF2		0x02
+#define ADT7462_CONF3		0x03
+#define ADT7462_TACH_EN		0x07
+#define ADT7462_TACH_CFG	0x08
+#define ADT7462_GPIO1_CFG	0x09
+#define ADT7462_GPIO2_CFG	0x0a
+#define ADT7462_TMIN_CAL1	0x0b
+#define ADT7462_TMIN_CAL2	0x0c
+#define ADT7462_THERM_CONF	0x0d
+#define ADT7462_CONF_THERM1	0x0e
+#define ADT7462_CONF_THERM2	0x0f
+#define ADT7462_PIN_CONF1	0x10
+#define ADT7462_PIN_CONF2	0x11
+#define ADT7462_PIN_CONF3	0x12
+#define ADT7462_PIN_CONF4	0x13
+#define ADT7462_EASY_CONF	0x14
+#define ADT7462_EDO_ENABLE	0x16
+#define ADT7462_ATTEN1_EN	0x18
+#define ADT7462_ATTEN2_EN	0x19
+#define ADT7462_ACCOUSTICS1	0x1a
+#define ADT7462_ACCOUSTICS2	0x1b
+#define ADT7462_FAN_TEST	0x1c
+#define ADT7462_FANS_PRESENT	0x1d
+#define ADT7462_FAN_TEST_EN	0x1e
+#define ADT7462_PWM1_CFG	0x21
+#define ADT7462_PWM2_CFG	0x22
+#define ADT7462_PWM3_CFG	0x23
+#define ADT7462_PWM4_CFG	0x24
+#define ADT7462_PWM12_FREQ	0x25
+#define ADT7462_PWM34_FREQ	0x26
+#define ADT7462_PWM1_MIN	0x28	/* Minimum PWM1 duty cycle */
+#define ADT7462_PWM2_MIN	0x29	/* Minimum PWM2 duty cycle */
+#define ADT7462_PWM3_MIN	0x2a	/* Minimum PWM3 duty cycle */
+#define ADT7462_PWM4_MIN	0x2b	/* Minimum PWM4 duty cycle */
+#define ADT7462_PWM1234_MAX	0x2c	/* Maximum all PWM duty cycle */
+#define ADT7462_THERM_MASK1	0x30
+#define ADT7462_THERM_MASK2	0x31
+#define ADT7462_VOLT_MASK1	0x32
+#define ADT7462_VOLT_MASK2	0x33
+#define ADT7462_FAN_MASK	0x34
+#define ADT7462_DIG_MASK	0x35
+#define ADT7462_GPIO_MASK	0x36
+#define ADT7462_EDO_MASK1	0x37
+#define ADT7462_EDO_MASK2	0x38
+#define ADT7462_DEV_ID		0x3d
+#define ADT7462_COMP_ID		0x3e
+#define ADT7462_REV_ID		0x3f
+#define ADT7462_LOCAL_LOW	0x44
+#define ADT7462_REM1_LOW	0x45	/* Register contents depend on */
+#define ADT7462_PIN15V_LOW	0x45	/* pin configuration. */
+#define ADT7462_REM2_LOW	0x46
+#define ADT7462_REM3_LOW	0x47
+#define ADT7462_PIN19V_LOW	0x47
+#define ADT7462_LOCAL_HIGH	0x48
+#define ADT7462_REM1_HIGH	0x49
+#define ADT7462_PIN15V_HIGH	0x49
+#define ADT7462_REM2_HIGH	0x4a
+#define ADT7462_REM3_HIGH	0x4b
+#define ADT7462_PIN19V_HIGH	0x4b
+#define ADT7462_LOCAL_THERM1	0x4c
+#define ADT7462_15V2_HIGH	0x4c
+#define ADT7462_REM1_THERM1	0x4d
+#define ADT7462_REM2_THERM1	0x4e
+#define ADT7462_REM3_THERM1	0x4f
+#define ADT7462_LOCAL_THERM2	0x50
+#define ADT7462_15V1_HIGH	0x50
+#define ADT7462_REM1_THERM2	0x51
+#define ADT7462_REM2_THERM2	0x52
+#define ADT7462_REM3_THERM2	0x53
+#define ADT7462_LOCREM1_HYST	0x54
+#define ADT7462_REM23_HYST	0x55
+#define ADT7462_LOCAL_OFF	0x56
+#define ADT7462_REM1_OFF	0x57
+#define ADT7462_REM2_OFF	0x58
+#define ADT7462_REM3_OFF	0x59
+#define ADT7462_REM1_OPP	0x5a
+#define ADT7462_REM2_OPP	0x5b
+#define ADT7462_LOCAL_MIN	0x5c
+#define ADT7462_REM1_MIN	0x5d
+#define ADT7462_REM2_MIN	0x5e
+#define ADT7462_REM3_MIN	0x5f
+#define ADT7462_LOCAL_RANGE	0x60
+#define ADT7462_REM1_RANGE	0x61
+#define ADT7462_REM2_RANGE	0x62
+#define ADT7462_REM3_RANGE	0x63
+#define ADT7462_OPP_HYST	0x64
+#define ADT7462_33V_HIGH	0x68
+#define ADT7462_PIN23V_HIGH	0x69
+#define ADT7462_PIN24V_HIGH	0x6a
+#define ADT7462_PIN25V_HIGH	0x6b
+#define ADT7462_PIN26V_HIGH	0x6c
+#define ADT7462_12V1_LOW	0x6d
+#define ADT7462_12V2_LOW	0x6e
+#define ADT7462_12V3_LOW	0x6f
+#define ADT7462_33V_LOW		0x70
+#define ADT7462_5V_LOW		0x71
+#define ADT7462_PIN23V_LOW	0x72
+#define ADT7462_PIN24V_LOW	0x73
+#define ADT7462_PIN25V_LOW	0x74
+#define ADT7462_PIN26V_LOW	0x75
+#define ADT7462_15V1_LOW	0x76
+#define ADT7462_15V2_LOW	0x77
+#define ADT7462_TACH1_LIMIT	0x78
+#define ADT7462_VID_LIMIT	0x78
+#define ADT7462_TACH2_LIMIT	0x79
+#define ADT7462_TACH3_LIMIT	0x7a
+#define ADT7462_TACH4_LIMIT	0x7b
+#define ADT7462_TACH5_LIMIT	0x7c
+#define ADT7462_12V1_HIGH	0x7c
+#define ADT7462_TACH6_LIMIT	0x7d
+#define ADT7462_12V2_HIGH	0x7d
+#define ADT7462_TACH7_LIMIT	0x7e
+#define ADT7462_5V_HIGH		0x7e
+#define ADT7462_TACH8_LIMIT	0x7f
+#define ADT7462_12V3_HIGH	0x7f
+#define ADT7462_THERM1_TIMELIM	0x80
+#define ADT7462_THERM2_TIMELIM	0x81
+#define ADT7462_LOCAL_VAL_LSB	0x88
+#define ADT7462_LOCAL_VAL_MSB	0x89
+#define ADT7462_REM1_VAL_LSB	0x8a
+#define ADT7462_REM1_VAL_MSB	0x8b
+#define ADT7462_PIN15V_VAL	0x8b
+#define ADT7462_REM2_VAL_LSB	0x8c
+#define ADT7462_REM2_VAL_MSB	0x8d
+#define ADT7462_REM3_VAL_LSB	0x8e
+#define ADT7462_REM3_VAL_MSB	0x8f
+#define ADT7462_PIN19V_VAL	0x8f
+#define ADT7462_PIN23V_VAL	0x90
+#define ADT7462_PIN24V_VAL	0x91
+#define ADT7462_PIN25V_VAL	0x92
+#define ADT7462_PIN26V_VAL	0x93
+#define ADT7462_15V1_VAL	0x94
+#define ADT7462_15V2_VAL	0x95
+#define ADT7462_33V_VAL		0x96
+#define ADT7462_VID_VAL		0x97
+#define ADT7462_TACH1_VAL_LSB	0x98
+#define ADT7462_TACH1_VAL_MSB	0x99
+#define ADT7462_TACH2_VAL_LSB	0x9a
+#define ADT7462_TACH2_VAL_MSB	0x9b
+#define ADT7462_TACH3_VAL_LSB	0x9c
+#define ADT7462_TACH3_VAL_MSB	0x9d
+#define ADT7462_TACH4_VAL_LSB	0x9e
+#define ADT7462_TACH4_VAL_MSB	0x9f
+#define ADT7462_TACH5_VAL_LSB	0xa2
+#define ADT7462_TACH5_VAL_MSB	0xa3
+#define ADT7462_12V1_VAL	0xa3
+#define ADT7462_TACH6_VAL_LSB	0xa4
+#define ADT7462_TACH6_VAL_MSB	0xa5
+#define ADT7462_12V2_VAL	0xa5
+#define ADT7462_TACH7_VAL_LSB	0xa6
+#define ADT7462_TACH7_VAL_MSB	0xa7
+#define ADT7462_5V_VAL		0xa7
+#define ADT7462_TACH8_VAL_LSB	0xa8
+#define ADT7462_TACH8_VAL_MSB	0xa9
+#define ADT7462_12V3_VAL	0xa9
+#define ADT7462_PWM1_DUTY	0xaa
+#define ADT7462_PWM2_DUTY	0xab
+#define ADT7462_PWM3_DUTY	0xac
+#define ADT7462_PWM4_DUTY	0xad
+#define ADT7462_THERM1_ON_TIM	0xae
+#define ADT7462_THERM2_ON_TIM	0xaf
+#define ADT7462_TEMP1_STAT_H	0xb8
+#define ADT7462_TEMP2_STAT_H	0xb9
+#define ADT7462_TEMP3_STAT_H	0xba
+#define ADT7462_VOLT1_STAT_H	0xbb
+#define ADT7462_VOLT2_STAT_H	0xbc
+#define ADT7462_FAN_STAT_H	0xbd
+#define ADT7462_DIG_STAT_H	0xbe
+#define ADT7462_GPIO_STAT_H	0xbf
+#define ADT7462_TEMP1_STAT_B	0xc0
+#define ADT7462_TEMP2_STAT_B	0xc1
+#define ADT7462_VOLT1_STAT_B	0xc3
+#define ADT7462_VOLT2_STAT_B	0xc4
+#define ADT7462_FAN_STAT_B	0xc5
+#define ADT7462_DIG_STAT_B	0xc6
+
+/* 0x00: Configuration register 0 */
+#define ADT7462_CONF0_BLK_MASK	0x3f	/* # registers for block read */
+#define ADT7462_CONF0_VID_SPEC	0x40	/* 0 = VR10, 1 = VR11 */
+#define ADT7462_CONF0_RESET	0x80	/* Reset all unlocked registers */
+
+/* 0x01: Configuration register 1 */
+#define ADT7462_CONF1_MONITOR	0x01	/* Start monitoring temps and volts */
+#define ADT7462_CONF1_ALERT	0x08	/* 0 = SMB, 1 = comparator */
+#define ADT7462_CONF1_FAST_SPIN	0x10	/* 1 = no fast spin-up */
+#define ADT7462_CONF1_COMPLETE	0x20	/* Setup complete, start monitoring */
+#define ADT7462_CONF1_LOCK	0x40	/* Lock limit registers */
+#define ADT7462_CONF1_READY	0x80	/* Ready to start monitoring */
+
+/* 0x02: Configuration register 2 */
+#define ADT7462_CONF2_FAN_HF	0x01	/* Fast fan measurements */
+#define ADT7462_CONF2_PWM_HF	0x04	/* High frequency PWM measurements */
+#define ADT7462_CONF2_VRD1	0x08	/* Full speed fans on VRD1 */
+#define ADT7462_CONF2_VRD2	0x10	/* Full speed fans on VRD2 */
+#define ADT7462_CONF2_FFULL	0x20	/* Full speed fans */
+#define ADT7462_CONF2_TACH	0xc0	/* Tach pulse to measure */
+
+/* 0x03: Configuration register 3 */
+#define ADT7462_CONF3_GPIO_EN	0x01	/* Enable GPIO's */
+#define ADT7462_CONF3_SCL_TO	0x02	/* Enable SCL timeout */
+#define ADT7462_CONF3_SDA_TO	0x04	/* Enable SDA timeout */
+#define ADT7462_CONF3_VID_LVL	0x08	/* Set low VID threshold */
+#define ADT7462_CONF3_THERM_LVL	0x10	/* Set low therm threshold */
+#define ADT7462_CONF3_CI_RESET	0x20	/* Reset chassis intrusion circuit */
+#define ADT7462_CONF3_XOR_TEST	0x40	/* Enable XOR tree test */
+#define ADT7462_CONF3_VCORE_LOW	0x80	/* Set V_core_low */
+
+/* 0x07: Tach enable register */
+#define ADT7462_TACH1_ENABLE	0x01	/* Enable tach1 measurement */
+#define ADT7462_TACH2_ENABLE	0x02	/* Enable tach2 measurement */
+#define ADT7462_TACH3_ENABLE	0x04	/* Enable tach3 measurement */
+#define ADT7462_TACH4_ENABLE	0x08	/* Enable tach4 measurement */
+#define ADT7462_TACH5_ENABLE	0x10	/* Enable tach5 measurement */
+#define ADT7462_TACH6_ENABLE	0x20	/* Enable tach6 measurement */
+#define ADT7462_TACH7_ENABLE	0x40	/* Enable tach7 measurement */
+#define ADT7462_TACH8_ENABLE	0x80	/* Enable tach8 measurement */
+
+#define ADT7462_FAN_TACH_EN(val, x)	((val & (1 << x)) != 0)
+
+/* 0x08: Tach configuration register */
+#define ADT7462_TACH15_CONT	0x01	/* Continuous tach1+5 measurement */
+#define ADT7462_TACH26_CONT	0x02	/* Continuous tach2+6 measurement */
+#define ADT7462_TACH37_CONT	0x04	/* Continuous tach3+7 measurement */
+#define ADT7462_TACH48_CONT	0x08	/* Continuous tach4+8 measurement */
+
+/* 0x09: GPIO configuration register 1 */
+#define ADT7462_GPIO1_POL	0x01	/* GPIO1 polarity (0 low, 1 high) */
+#define ADT7462_GPIO1_DIR	0x02	/* GPIO1 direction (0 in, 1 out) */
+#define ADT7462_GPIO2_POL	0x04	/* GPIO2 polarity (0 low, 1 high) */
+#define ADT7462_GPIO2_DIR	0x08	/* GPIO2 direction (0 in, 1 out) */
+#define ADT7462_GPIO3_POL	0x10	/* GPIO3 polarity (0 low, 1 high) */
+#define ADT7462_GPIO3_DIR	0x20	/* GPIO3 direction (0 in, 1 out) */
+#define ADT7462_GPIO4_POL	0x40	/* GPIO4 polarity (0 low, 1 high) */
+#define ADT7462_GPIO4_DIR	0x80	/* GPIO4 direction (0 in, 1 out) */
+
+/* 0x0a: GPIO configuration register 2 */
+#define ADT7462_GPIO5_POL	0x01	/* GPIO5 polarity (0 low, 1 high) */
+#define ADT7462_GPIO5_DIR	0x02	/* GPIO5 direction (0 in, 1 out) */
+#define ADT7462_GPIO6_POL	0x04	/* GPIO6 polarity (0 low, 1 high) */
+#define ADT7462_GPIO6_DIR	0x08	/* GPIO6 direction (0 in, 1 out) */
+#define ADT7462_GPIO7_POL	0x10	/* GPIO7 polarity (0 low, 1 high) */
+#define ADT7462_GPIO7_DIR	0x20	/* GPIO7 direction (0 in, 1 out) */
+#define ADT7462_GPIO8_POL	0x40	/* GPIO8 polarity (0 low, 1 high) */
+#define ADT7462_GPIO8_DIR	0x80	/* GPIO8 direction (0 in, 1 out) */
+
+/* 0x0b: Dynamix Tmin control register 1 */
+#define ADT7462_REM1_EN		0x01	/* Enable dynamic rem 1 Tmin control */
+#define ADT7462_REM2_EN		0x02	/* Enable dynamic rem 2 Tmin control */
+#define ADT7462_P1_R1		0x04	/* Copy rem 1 val to op if therm1 */
+#define ADT7462_P1_R2		0x08	/* Copy rem 2 val to op if therm1 */
+#define ADT7462_P2_R1		0x10	/* Copy rem 1 val to op if therm2 */
+#define ADT7462_P2_R2		0x20	/* Copy rem 2 val to op if therm2 */
+
+/* 0x0c: Dynamic Tmin control register 2 */
+#define ADT7462_REM1_CYCLE	0x07	/* Rem 1 cycle mask */
+#define ADT7462_REM2_CYCLE	0x38	/* Rem 2 cycle mask */
+#define ADT7462_CTRL_LOOP	0x40	/* Control loop select */
+
+/* 0x0d: Therm configuration register */
+#define ADT7462_BOOST1		0x01	/* Max fan PWM if therm1 */
+#define ADT7462_BOOST2		0x02	/* Max fan PWM if therm2 */
+#define ADT7462_THERM1_TIMER	0x1c	/* Therm1 timer window mask */
+#define ADT7462_THERM2_TIMER	0xe0	/* Therm2 timer window mask */
+
+/* 0x0e: Therm1 configuration register */
+#define ADT7462_THERM1_TIM_EN	0x01	/* Enable therm1 timer circuit */
+#define ADT7462_THERM1_LOCAL	0x02	/* Therm1 assert on local */
+#define ADT7462_THERM1_REM1	0x04	/* Therm1 assert on remote1 */
+#define ADT7462_THERM1_REM2	0x08	/* Therm1 assert on remote2 */
+#define ADT7462_THERM1_REM3	0x10	/* Therm1 assert on remote3 */
+
+/* 0x0f: Therm2 configuration register */
+#define ADT7462_THERM2_TIM_EN	0x01	/* Enable therm2 timer circuit */
+#define ADT7462_THERM2_LOCAL	0x02	/* Therm2 assert on local */
+#define ADT7462_THERM2_REM1	0x04	/* Therm2 assert on remote1 */
+#define ADT7462_THERM2_REM2	0x08	/* Therm2 assert on remote2 */
+#define ADT7462_THERM2_REM3	0x10	/* Therm2 assert on remote3 */
+
+/* 0x10: Pin configuration register 1 */
+#define ADT7462_PIN7_CONF	0x01	/* 0 = +12v1, 1 = Tach5 */
+#define ADT7462_PIN4_CONF	0x02	/* 0 = GPIO4, 1 = Tach4 */
+#define ADT7462_PIN3_CONF	0x04	/* 0 = GPIO3, 1 = Tach3 */
+#define ADT7462_PIN2_CONF	0x08	/* 0 = GPIO2, 1 = Tach2 */
+#define ADT7462_PIN1_CONF	0x10	/* 0 = GPIO1, 1 = Tach1 */
+#define ADT7462_DIODE3_CONF	0x20	/* 0 = volt/SCSI, 1 = D3+/D3- */
+#define ADT7462_DIODE1_CONF	0x40	/* 0 = volt/SCSI, 1 = D1+/D1- */
+#define ADT7462_VID_EN		0x80	/* 1 = En. VID's pins 1-4 28 31 32 */
+
+#define ADT7462_PCR1_TACH(val, x)	(x > 3 || \
+    (!(val & ADT7462_VID_EN) && (val & (1 << x)) != 0))
+#define ADT7462_PCR1_TEMP(val, x)	(x== 0 || x == 2 || \
+    (x == 3 && (val & ADT7462_DIODE3_CONF)) || \
+    (x == 1 && (val & ADT7462_DIODE1_CONF)))
+#define ADT7462_PCR1_PIN7_V(val)	(!(val & ADT7462_PIN7_CONF))
+#define ADT7462_PCR1_PIN19_V(val)	(!(val & ADT7462_DIODE3_CONF))
+#define ADT7462_PCR1_PIN15_V(val)	(!(val & ADT7462_DIODE1_CONF))
+#define ADT7462_PCR1_VIDS(val)		((val & ADT7462_VID_EN) != 0)
+
+/* 0x11: Pin configuration register 2 */
+#define ADT7462_PIN23_CONF	0x03	/* 00 = Vcc, 2.5v, 1.8v, 11 = 1.5v */
+#define ADT7462_PIN22_CONF	0x04	/* 0 = 12v3, 1 = Tach8 */
+#define ADT7462_PIN21_CONF	0x08	/* 0 = 5v, 1 = Tach7 */
+#define ADT7462_PIN19_CONF	0x10	/* 0 = 1.25v, 1 = 0.9v */
+#define ADT7462_PIN15_CONF	0x20	/* 0 = 2.5v, 1 = 1.8v */
+#define ADT7462_PIN13_CONF	0x40	/* 0 = 3.3v, 1 = PWM4 */
+#define ADT7462_PIN8_CONF	0x80	/* 0 = 12v2, 1 = Tach6 */
+
+#define ADT7462_PCR2_P23_25V(val)	((val & ADT7462_PIN23_CONF) == 0x01)
+#define ADT7462_PCR2_P23_18V(val)	((val & ADT7462_PIN23_CONF) == 0x02)
+#define ADT7462_PCR2_P23_15V(val)	((val & ADT7462_PIN23_CONF) == 0x03)
+#define ADT7462_PCR2_P22_TACH8(val)	((val & ADT7462_PIN22_CONF) == 0x04)
+#define ADT7462_PCR2_P21_TACH7(val)	((val & ADT7462_PIN21_CONF) == 0x08)
+#define ADT7462_PCR2_P19_09V(val)	((val & ADT7462_PIN19_CONF) == 0x10)
+#define ADT7462_PCR2_P15_18V(val)	((val & ADT7462_PIN15_CONF) == 0x20)
+#define ADT7462_PCR2_P13_PWM4(val)	((val & ADT7462_PIN13_CONF) == 0x40)
+#define ADT7462_PCR2_P8_TACH6(val)	((val & ADT7462_PIN8_CONF) == 0x80)
+#define ADT7462_PCR2_TACH(val, x)	(x < 5 || \
+    (x == 5 && ADT7462_PCR2_P8_TACH6(val)) || \
+    (x == 6 && ADT7462_PCR2_P21_TACH7(val)) || \
+    (x == 7 && ADT7462_PCR2_P22_TACH8(val)))
+
+/* 0x12: Pin configuration register 3 */
+#define ADT7462_PIN27_CONF	0x02	/* 0 = fan2max, 1 = chassis intrus */
+#define ADT7462_PIN26_CONF	0x0c	/* 00 = Vbatt, 1.2v2, 10/11=vr_hot2 */
+#define ADT7462_PIN25_CONF	0x30	/* 00 = 3.3v, 1.2v1, 10/11=vr_hot1 */
+#define ADT7462_PIN24_CONF	0xc0	/* 00 = Vccp, 2.5v, 1.8v, 11 = 1.5v */
+
+#define ADT7462_PCR3_P24_25V(val)	((val & ADT7462_PIN24_CONF) == 0x40)
+#define ADT7462_PCR3_P24_18V(val)	((val & ADT7462_PIN24_CONF) == 0x80)
+#define ADT7462_PCR3_P24_15V(val)	((val & ADT7462_PIN24_CONF) == 0xc0)
+#define ADT7462_PCR3_P25_33V(val)	((val & ADT7462_PIN25_CONF) == 0x00)
+#define ADT7462_PCR3_P25_12V(val)	((val & ADT7462_PIN25_CONF) == 0x10)
+#define ADT7462_PCR3_P26_VBAT(val)	((val & ADT7462_PIN26_CONF) == 0x00)
+#define ADT7462_PCR3_P26_12V(val)	((val & ADT7462_PIN26_CONF) == 0x04)
+
+/* 0x13: Pin configuration register 4 */
+#define ADT7462_PIN32_CONF	0x04	/* 0 = GPIO6, 1 = PWM2 */
+#define ADT7462_PIN31_CONF	0x08	/* 0 = GPIO5, 1 = PWM1 */
+#define ADT7462_PIN29_CONF	0x30	/* 00 = GPIO8, 1.5v, 10/11 = therm2 */
+#define ADT7462_PIN28_CONF	0xc0	/* 00 = GPIO7, 1.5v, 10/11 = therm1 */
+
+#define ADT7462_PCR4_P29_15V(val)	((val & ADT7462_PIN29_CONF) == 0x10)
+#define ADT7462_PCR4_P28_15V(val)	((val & ADT7462_PIN28_CONF) == 0x10)
+
+/* 0x14: Easy configuration options */
+#define ADT7462_EASY1		0x01	/* Enable easy option 1 */
+#define ADT7462_EASY2		0x02	/* Enable easy option 2 */
+#define ADT7462_EASY3		0x04	/* Enable easy option 3 */
+#define ADT7462_EASY4		0x08	/* Enable easy option 4 */
+
+/* 0x16: EDO/Single channel enable */
+#define ADT7462_EDO_EN1		0x01	/* Enable EDO on GPIO5 */
+#define ADT7462_EDO_EN2		0x02	/* Enable EDO on GPIO6 */
+#define ADT7462_1CHAN_MODE	0x04	/* Select single-channel mode */
+#define ADT7462_CHAN_SEL	0xf8	/* Select channel mask */
+
+/* 0x18: Voltage attenuator configuration 1 */
+#define ADT7462_ATT_PIN7	0x02	/* Enable attenuator on pin7 */
+#define ADT7462_ATT_PIN8	0x04	/* Enable attenuator on pin8 */
+#define ADT7462_ATT_PIN13	0x08	/* Enable attenuator on pin13 */
+#define ADT7462_ATT_PIN15	0x10	/* Enable attenuator on pin15 */
+#define ADT7462_ATT_PIN19	0x20	/* Enable attenuator on pin19 */
+#define ADT7462_ATT_PIN21	0x40	/* Enable attenuator on pin21 */
+#define ADT7462_ATT_PIN22	0x80	/* Enable attenuator on pin22 */
+
+/* 0x19: Voltage attenuator configuration 2 */
+#define ADT7462_ATT_PIN23	0x01	/* Enable attenuator on pin23 */
+#define ADT7462_ATT_PIN24	0x02	/* Enable attenuator on pin24 */
+#define ADT7462_ATT_PIN25	0x04	/* Enable attenuator on pin25 */
+#define ADT7462_ATT_PIN28	0x10	/* Enable attenuator on pin28 */
+#define ADT7462_ATT_PIN29	0x20	/* Enable attenuator on pin29 */
+
+/* 0x1a: Enhanced acoustics register 1 */
+#define ADT7462_AC_EN1		0x01	/* En. enhanced acoustics for PWM1 */
+#define ADT7462_AC_EN2		0x02	/* En. enhanced acoustics for PWM2 */
+#define ADT7462_AC_RATE1	0x1c	/* Ramp rate for PWM1 */
+#define ADT7462_AC_RATE2	0xe0	/* Ramp rate for PWM2 */
+
+/* 0x1b: Enhanced acoustics register 2 */
+#define ADT7462_AC_EN3		0x01	/* En. enhanced acoustics for PWM3 */
+#define ADT7462_AC_EN4		0x02	/* En. enhanced acoustics for PWM4 */
+#define ADT7462_AC_RATE3	0x1c	/* Ramp rate for PWM3 */
+#define ADT7462_AC_RATE4	0xe0	/* Ramp rate for PWM4 */
+
+/* 0x1c: Fan freewheeling test */
+#define ADT7462_FAN_FREE1_END	0x01	/* Freewheeling for fan1 complete */
+#define ADT7462_FAN_FREE2_END	0x02	/* Freewheeling for fan2 complete */
+#define ADT7462_FAN_FREE3_END	0x04	/* Freewheeling for fan3 complete */
+#define ADT7462_FAN_FREE4_END	0x08	/* Freewheeling for fan4 complete */
+#define ADT7462_FAN_FREE5_END	0x10	/* Freewheeling for fan5 complete */
+#define ADT7462_FAN_FREE6_END	0x20	/* Freewheeling for fan6 complete */
+#define ADT7462_FAN_FREE7_END	0x40	/* Freewheeling for fan7 complete */
+#define ADT7462_FAN_FREE8_END	0x80	/* Freewheeling for fan8 complete */
+
+/* 0x1d: Fans present */
+#define ADT7462_FAN1_PRESENT	0x01	/* Fan1 present */
+#define ADT7462_FAN2_PRESENT	0x02	/* Fan2 present */
+#define ADT7462_FAN3_PRESENT	0x04	/* Fan3 present */
+#define ADT7462_FAN4_PRESENT	0x08	/* Fan4 present */
+#define ADT7462_FAN5_PRESENT	0x10	/* Fan5 present */
+#define ADT7462_FAN6_PRESENT	0x20	/* Fan6 present */
+#define ADT7462_FAN7_PRESENT	0x40	/* Fan7 present */
+#define ADT7462_FAN8_PRESENT	0x80	/* Fan8 present */
+
+/* 0x1e: Fan freewheeling test */
+#define ADT7462_FAN_FREE1	0x01	/* Start freewheeling test for fan1 */
+#define ADT7462_FAN_FREE2	0x02	/* Start freewheeling test for fan2 */
+#define ADT7462_FAN_FREE3	0x04	/* Start freewheeling test for fan3 */
+#define ADT7462_FAN_FREE4	0x08	/* Start freewheeling test for fan4 */
+#define ADT7462_FAN_FREE5	0x10	/* Start freewheeling test for fan5 */
+#define ADT7462_FAN_FREE6	0x20	/* Start freewheeling test for fan6 */
+#define ADT7462_FAN_FREE7	0x40	/* Start freewheeling test for fan7 */
+#define ADT7462_FAN_FREE8	0x80	/* Start freewheeling test for fan8 */
+
+/* 0x21 - 0x24 : PWM configuration registers */
+#define ADT7462_PWM_CFG(x)	(0x21 + x)
+#define ADT7462_PWM_SPIN_TIMEO	0x07	/* Fan startup and test timeout */
+#define ADT7462_PWM_SLOW	0x08	/* Slow en. acoustics mode start*/
+#define ADT7462_PWM_INV		0x10	/* PWM output 0 = low, 1 = high */
+#define ADT7462_PWM_BHVR_MASK	0xe0	/* PWM control channel mask */
+#define ADT7462_PWM_BHVR_SHFT	5	/* PWM control channel shift */
+#define ADT7462_PWM_BHVR_LOCAL	0x00	/* PWM control = local */
+#define ADT7462_PWM_BHVR_REM1	0x20	/* PWM control = remote 1 */
+#define ADT7462_PWM_BHVR_REM2	0x40	/* PWM control = remote 2 */
+#define ADT7462_PWM_BHVR_REM3	0x60	/* PWM control = remote 3 */
+#define ADT7462_PWM_BHVR_OFF	0x80	/* PWM control = off */
+#define ADT7462_PWM_BHVR_LR3	0xa0	/* PWM control = local + rem 3 */
+#define ADT7462_PWM_BHVR_ALL	0xc0	/* PWM control = local + rem 1/2/3 */
+#define ADT7462_PWM_BHVR_MAN	0xe0	/* PWM control = manual */
+
+/* 0x25: PWM1, PWM2 frequency */
+#define ADT7462_PWM1_TMIN	0x01	/* PWM1 is off or min at Tmin */
+#define ADT7462_PWM2_TMIN	0x02	/* PWM2 is off or min at Tmin */
+#define ADT7462_PWM1_LFREQ	0x1c	/* PWM1 low frequency mask */
+#define ADT7462_PWM2_LFREQ	0xe0	/* PWM2 low frequency mask */
+
+/* 0x26: PWM3, PWM4 frequency */
+#define ADT7462_PWM3_TMIN	0x01	/* PWM3 is off or min at Tmin */
+#define ADT7462_PWM4_TMIN	0x02	/* PWM4 is off or min at Tmin */
+#define ADT7462_PWM3_LFREQ	0x1c	/* PWM3 low frequency mask */
+#define ADT7462_PWM4_LFREQ	0xe0	/* PWM4 low frequency mask */
+
+/* 0x30: Thermal mask register 1 */
+#define ADT7462_MASK_LOCAL	0x02	/* Mask local temp. out of limit */
+#define ADT7462_MASK_REMOTE1	0x04	/* Mask remote1 temp. out of limit */
+#define ADT7462_MASK_REMOTE2	0x08	/* Mask remote2 temp. out of limit */
+#define ADT7462_MASK_REMOTE3	0x10	/* Mask remote3 temp. out of limit */
+#define ADT7462_MASK_DIODE1	0x20	/* Mask remote1 temp. error */
+#define ADT7462_MASK_DIODE2	0x40	/* Mask remote2 temp. error */
+#define ADT7462_MASK_DIODE3	0x80	/* Mask remote3 temp. error */
+
+/* 0x31: Thermal mask register 2 */
+#define ADT7462_MASK_TH1_PCT	0x01	/* Mask therm1 percent alert */
+#define ADT7462_MASK_TH1_ASRT	0x02	/* Mask therm1 assert */
+#define ADT7462_MASK_TH1_STAT	0x04	/* Mask therm1 state alert */
+#define ADT7462_MASK_TH2_PCT	0x08	/* Mask therm2 percent alert */
+#define ADT7462_MASK_TH2_ASRT	0x10	/* Mask therm2 assert */
+#define ADT7462_MASK_TH2_STAT	0x20	/* Mask therm2 state alert */
+#define ADT7462_MASK_VRD1	0x40	/* Mask VRD1 assert */
+#define ADT7462_MASK_VRD2	0x80	/* Mask VRD2 assert */
+
+/* 0x32: Voltage mask register 1 */
+#define ADT7462_MASK_12V1	0x01	/* Mask 12V1 alert */
+#define ADT7462_MASK_12V2	0x02	/* Mask 12V2 alert */
+#define ADT7462_MASK_12V3	0x04	/* Mask 12V3 alert */
+#define ADT7462_MASK_33V	0x08	/* Mask 3.3V alert */
+#define ADT7462_MASK_PIN15V	0x10	/* Mask pin15v alert */
+#define ADT7462_MASK_PIN19V	0x20	/* Mask pin19v alert */
+#define ADT7462_MASK_5V		0x40	/* Mask 5V alert */
+#define ADT7462_MASK_PIN23V	0x80	/* Mask pin23v alert */
+
+/* 0x33: Voltage mask register 2 */
+#define ADT7462_MASK_PIN24V	0x08	/* Mask pin24v alert */
+#define ADT7462_MASK_PIN25V	0x10	/* Mask pin25v alert */
+#define ADT7462_MASK_PIN26V	0x20	/* Mask pin26v alert */
+#define ADT7462_MASK_15V2	0x40	/* Mask 1.5V2 alert */
+#define ADT7462_MASK_15V1	0x80	/* Mask 1.5V1 alert */
+
+/* 0x34: Fan mask register */
+#define ADT7462_MASK_FAN1_FAULT	0x01	/* Mask fan1 fault */
+#define ADT7462_MASK_FAN2_FAULT	0x02	/* Mask fan2 fault */
+#define ADT7462_MASK_FAN3_FAULT	0x04	/* Mask fan3 fault */
+#define ADT7462_MASK_FAN4_FAULT	0x08	/* Mask fan4 fault */
+#define ADT7462_MASK_FAN5_FAULT	0x10	/* Mask fan5 fault */
+#define ADT7462_MASK_FAN6_FAULT	0x20	/* Mask fan6 fault */
+#define ADT7462_MASK_FAN7_FAULT	0x40	/* Mask fan7 fault */
+#define ADT7462_MASK_FAN8_FAULT	0x80	/* Mask fan8 fault */
+
+/* 0x35: Digital mask register */
+#define ADT7462_MASK_FAN2MAX	0x08	/* Mask fan2max alert */
+#define ADT7462_MASK_SCSI1	0x10	/* Mask SCSI1 alert */
+#define ADT7462_MASK_SCSI2	0x20	/* Mask SCSI2 alert */
+#define ADT7462_MASK_VID	0x40	/* Mask VID comparison alert */
+#define ADT7462_MASK_CHASSIS	0x80	/* Mask chassis intrusion alert */
+
+/* 0x36: GPIO mask register */
+#define ADT7462_MASK_GPIO1_FLT	0x01	/* Mask GPIO1 fault */
+#define ADT7462_MASK_GPIO2_FLT	0x02	/* Mask GPIO2 fault */
+#define ADT7462_MASK_GPIO3_FLT	0x04	/* Mask GPIO3 fault */
+#define ADT7462_MASK_GPIO4_FLT	0x08	/* Mask GPIO4 fault */
+#define ADT7462_MASK_GPIO5_FLT	0x10	/* Mask GPIO5 fault */
+#define ADT7462_MASK_GPIO6_FLT	0x20	/* Mask GPIO6 fault */
+#define ADT7462_MASK_GPIO7_FLT	0x40	/* Mask GPIO7 fault */
+#define ADT7462_MASK_GPIO8_FLT	0x80	/* Mask GPIO8 fault */
+
+/* 0x36: EDO mask register 1 */
+#define ADT7462_EDO1_GPIO1	0x01	/* Mask GPIO1 for EDO1 */
+#define ADT7462_EDO1_GPIO2	0x02	/* Mask GPIO2 for EDO1 */
+#define ADT7462_EDO1_GPIO3	0x04	/* Mask GPIO3 for EDO1 */
+#define ADT7462_EDO1_GPIO4	0x08	/* Mask GPIO4 for EDO1 */
+#define ADT7462_EDO1_FAN	0x20	/* Mask fan fault for EDO1 */
+#define ADT7462_EDO1_TEMP	0x40	/* Mask therm for EDO1 */
+#define ADT7462_EDO2_VOLT	0x80	/* Mask volt limit for EDO1 */
+
+/* 0x36: EDO mask register 2 */
+#define ADT7462_EDO2_GPIO1	0x01	/* Mask GPIO1 for EDO2 */
+#define ADT7462_EDO2_GPIO2	0x02	/* Mask GPIO2 for EDO2 */
+#define ADT7462_EDO2_GPIO3	0x04	/* Mask GPIO3 for EDO2 */
+#define ADT7462_EDO2_GPIO4	0x08	/* Mask GPIO4 for EDO2 */
+#define ADT7462_EDO2_FAN	0x20	/* Mask fan fault for EDO2 */
+#define ADT7462_EDO2_TEMP	0x40	/* Mask therm for EDO2 */
+#define ADT7462_EDO2_VOLT	0x80	/* Mask volt limit for EDO2 */
+
+/* 0x3d: Device ID register */
+#define ADT7462_DEV_ID_VAL	0x62	/* ADT7462 device ID */
+
+/* 0x3e: Company ID register */
+#define ADT7462_COMP_ID_VAL	0x41	/* ADT7462 company ID */
+
+/* 0x3f: Revision ID register */
+#define ADT7462_REV_ID_VAL	0x04	/* ADT7462 revision ID */
+
+/* 0x44 - 0x53: Temperature limit registers: -64'C + value */
+#define ADT7462_TEMP_BASE	209150000
+#define ADT7462_TEMP_OFFSET	64
+#define ADT7462_TEMP_LOW(x)	(0x44 + x)
+#define ADT7462_TEMP_HIGH(x)	(0x48 + x)
+#define ADT7462_TEMP_THERM1(x)	(0x4c + x)
+#define ADT7462_TEMP_THERM2(x)	(0x50 + x)
+
+/* 0x54 - Local/remote 1 temperature hysteresis */
+#define ADT7462_REMOTE1_TH_HYST	0x0f	/* Remote 1 therm hyst. (0 - 15) */
+#define ADT7462_LOCAL_TH_HYST	0xf0	/* Local therm hyst. (0 - 15) */
+
+/* 0x55 - Remote 2/remote 3 temperature hysteresis */
+#define ADT7462_REMOTE3_TH_HYST	0x0f	/* Remote 3 therm hyst. (0 - 15) */
+#define ADT7462_REMOTE2_TH_HYST	0xf0	/* Remote 2 therm hyst. (0 - 15) */
+
+/* 0x56 - 0x59: Offset registers: resolution = 0.5'C */
+
+/* 0x5a - 0x5b: Operating point registers */
+#define ADT7462_OP_POINT(x)	(0x59 + x)	/* Only remote 1 and 2 */
+
+/* 0x5c - 0x5f: Tmin registers */
+#define ADT7462_TMIN(x)		(0x5c + x)
+
+/* 0x60 - 0x63: Trange/Hysteresis registers */
+#define ADT7462_HYST_TRANGE(x)	(0x60 + x)
+#define ADT7462_HTR_HYST_MASK	0x0f	/* Auto fan loop hyst. (0-15) */
+#define ADT7462_HTR_RNGE_MASK	0xf0	/* Trange value (2 - 80) */
+#define ADT7462_HTR_RNGE_SHFT	4	/* Trange shift */
+
+/* 0x64: Operating point hysteresis */
+#define ADT7462_OPT_HYST	0xf0	/* Tmin loop hyst. (0 - 15) */
+
+/* 0x68 - 0x77: Voltage limit registers */
+
+/* 0x78 - 0x7f: Tach limit registers */
+#define ADT7462_TACH_LIMIT(x)	(0x78 + x)
+
+/* 0x80 - 0x81: Therm timer limit registers */
+
+/* 0x88 - 0x8f: Temperature value registers */
+#define ADT7462_TEMP_LSB(x)	(0x88 + 2 * x)
+
+/* 0x90 - 0x96: Voltage value registers */
+
+/* 0x97: VID value register */
+
+/* 0x98 - 0x9f, 0xa2 - 0xa9: Tach value registers */
+#define ADT7462_TACH_VAL_LSB(x)	(x < 4 ? 0x98 + 2 * x : 0x9a + 2 * x)
+#define ADT7462_TACH_PERIOD	(90000 * 60)
+
+/* 0xaa - 0xad: PWM current duty cycle registers */
+#define ADT7462_PWM_DUTY(x)	(0xaa + x)
+
+/* 0xae - 0xaf: Therm time value registers */
+
+/* 0xb8, 0xc0: Host/BMC thermal status register 1 */
+#define ADT7462_LOCAL_TRIP	0x02
+#define ADT7462_REMOTE1_TRIP	0x04
+#define ADT7462_REMOTE2_TRIP	0x08
+#define ADT7462_REMOTE3_TRIP	0x10
+#define ADT7462_DIODE1_ERR	0x20
+#define ADT7462_DIODE2_ERR	0x40
+#define ADT7462_DIODE3_ERR	0x80
+
+/* 0xb9, 0xc1: Host/BMC thermal status register 2 */
+#define ADT7462_THERM1_PCT	0x01
+#define ADT7462_THERM1_ASSERT	0x02
+#define ADT7462_THERM1_STATE	0x04
+#define ADT7462_THERM2_PCT	0x08
+#define ADT7462_THERM2_ASSERT	0x10
+#define ADT7462_THERM2_STATE	0x20
+#define ADT7462_VRD1_ASSERT	0x40
+#define ADT7462_VRD2_ASSERT	0x80
+
+/* 0xba: Host thermal status register 3 */
+#define ADT7462_THERM1_L_EXCD	0x01
+#define ADT7462_THERM1_R1_EXCD	0x02
+#define ADT7462_THERM1_R2_EXCD	0x04
+#define ADT7462_THERM1_R3_EXCD	0x08
+#define ADT7462_THERM2_L_EXCD	0x10
+#define ADT7462_THERM2_R1_EXCD	0x20
+#define ADT7462_THERM2_R2_EXCD	0x40
+#define ADT7462_THERM2_R3_EXCD	0x80
+
+/* 0xbb, 0xc3: Host/BMC voltage status register 1 */
+#define ADT7462_12V1_TRIP	0x01
+#define ADT7462_12V2_TRIP	0x02
+#define ADT7462_12V3_TRIP	0x04
+#define ADT7462_33V_TRIP	0x08
+#define ADT7462_PIN15V_TRIP	0x10
+#define ADT7462_PIN19V_TRIP	0x20
+#define ADT7462_5V_TRIP		0x40
+#define ADT7462_PIN23V_TRIP	0x80
+
+/* 0xbc, 0xc4: Host/BMC voltage status register 2 */
+#define ADT7462_PIN24V_TRIP	0x04
+#define ADT7462_PIN25V_TRIP	0x10
+#define ADT7462_PIN26V_TRIP	0x20
+#define ADT7462_15V2_TRIP	0x40
+#define ADT7462_15V1_TRIP	0x80
+
+/* 0xbd, 0xc5: Host/BMC fan status register */
+#define ADT7462_FAN1_FAULT	0x01
+#define ADT7462_FAN2_FAULT	0x02
+#define ADT7462_FAN3_FAULT	0x04
+#define ADT7462_FAN4_FAULT	0x08
+#define ADT7462_FAN5_FAULT	0x10
+#define ADT7462_FAN6_FAULT	0x20
+#define ADT7462_FAN7_FAULT	0x40
+#define ADT7462_FAN8_FAULT	0x80
+
+/* 0xbe, 0xc6: Host/BMC digital status register */
+#define ADT7462_FAN2MAX_ASSERT	0x08
+#define ADT7462_SCSI1_ASSERT	0x10
+#define ADT7462_SCSI2_ASSERT	0x20
+#define ADT7462_VID_COMP_FLT	0x40
+#define ADT7462_CHASSIS_ASSERT	0x80
+
+/* 0xbe, 0xc6: Host/BMC GPIO status register */
+#define ADT7462_GPIO1_ASSERT	0x01
+#define ADT7462_GPIO2_ASSERT	0x02
+#define ADT7462_GPIO3_ASSERT	0x04
+#define ADT7462_GPIO4_ASSERT	0x08
+#define ADT7462_GPIO5_ASSERT	0x10
+#define ADT7462_GPIO6_ASSERT	0x20
+#define ADT7462_GPIO7_ASSERT	0x40
+#define ADT7462_GPIO8_ASSERT	0x80
+
+#endif /* _DEV_I2C_ADT7462REG_H_ */
--- src/sys/dev/i2c/lm95221.c.dist	2026-02-10 22:20:01.329986410 +0100
+++ src/sys/dev/i2c/lm95221.c	2026-02-20 08:07:11.269991159 +0100
@@ -0,0 +1,279 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2020 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+
+#include <dev/sysmon/sysmonvar.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/lm95221reg.h>
+
+#define LM95221_LOCAL	0
+#define LM95221_REMOTE1	1
+#define LM95221_REMOTE2	2
+#define LM95221_MISSING 0x01
+#define LM95221_SIGNED	0x02
+
+struct lm95221ts_softc {
+	device_t sc_dev;
+	i2c_tag_t sc_tag;
+	int sc_address;
+
+	struct sysmon_envsys *sc_sme;
+	envsys_data_t sc_sensor[LM95221_NSENSORS];
+	int sc_map[LM95221_NSENSORS];		/* Sysmon numbers to sensors */
+	uint8_t	sc_reg_m[LM95221_NSENSORS];	/* MSB register */
+	uint8_t	sc_reg_l[LM95221_NSENSORS];	/* LSB register */
+	uint8_t sc_flags[LM95221_NSENSORS];	/* Flags for remotes */
+};
+
+static int lm95221ts_match(device_t, cfdata_t, void *);
+static void lm95221ts_attach(device_t, device_t, void *);
+static int lm95221ts_detach(device_t, int);
+void lm95221ts_refresh(struct sysmon_envsys *, envsys_data_t *);
+static int lm95221ts_read_temp(struct lm95221ts_softc *, uint8_t, uint8_t,
+    uint8_t, uint32_t *);
+
+CFATTACH_DECL_NEW(lm95221ts, sizeof(struct lm95221ts_softc),
+	lm95221ts_match, lm95221ts_attach, lm95221ts_detach, NULL);
+
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "i2c-lm95221" },
+	DEVICE_COMPAT_EOL
+};
+
+static int
+lm95221ts_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct i2c_attach_args *ia = aux;
+	int match_result;
+
+	if (iic_use_direct_match(ia, cf, compat_data, &match_result))
+		return match_result;
+
+	/*
+	 * Indirect config - assume config is correct!
+	 */
+	if (ia->ia_addr == LM95221_ADDR)
+		return I2C_MATCH_ADDRESS_ONLY;
+
+	return 0;
+}
+
+static void
+lm95221ts_attach(device_t parent, device_t self, void *aux)
+{
+	struct lm95221ts_softc *sc = device_private(self);
+	struct i2c_attach_args *ia = aux;
+	uint8_t reg, val;
+	int i, map;
+
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_address = ia->ia_addr;
+	sc->sc_dev = self;
+
+	aprint_normal(": LM95221 temperature sensor\n");
+
+	/* Read status, start conversion */
+	for (i = 0; i < LM95221_NSENSORS; i++)
+		sc->sc_flags[i] = 0;
+
+	if (iic_acquire_bus(sc->sc_tag, 0)) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to acquire i2c bus\n");
+		return;
+	}
+
+	reg = LM95221_STATUS;
+	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg, 1, &val, 1, 0)) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read status register\n");
+		return;
+	}
+	if (val & LM95221_STAT_NO_REMOTE1)
+		sc->sc_flags[LM95221_REMOTE1] |= LM95221_MISSING;
+	if (val & LM95221_STAT_NO_REMOTE2)
+		sc->sc_flags[LM95221_REMOTE2] |= LM95221_MISSING;
+
+	reg = LM95221_CONFIG;
+	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg, 1, &val, 1, 0)) {
+		iic_release_bus(sc->sc_tag, 0);
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read config register\n");
+		return;
+	}
+	if (val & LM95221_CONF_REMOTE1)
+		sc->sc_flags[LM95221_REMOTE1] |= LM95221_SIGNED;
+	if (val & LM95221_CONF_REMOTE2)
+		sc->sc_flags[LM95221_REMOTE2] |= LM95221_SIGNED;
+
+	if (val & LM95221_CONF_STANDBY) {
+		val &= ~(LM95221_CONF_STANDBY);
+		if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_address, &reg, 1, &val, 1, 0)) {
+			aprint_error_dev(sc->sc_dev,
+			    "unable to write config register\n");
+			iic_release_bus(sc->sc_tag, 0);
+			return;
+		}
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+
+	sc->sc_sme = sysmon_envsys_create();
+	sc->sc_sme->sme_name = device_xname(self);
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_refresh = lm95221ts_refresh;
+
+	strlcpy(sc->sc_sensor[LM95221_LOCAL].desc, "local",
+	    sizeof(sc->sc_sensor[LM95221_LOCAL].desc));
+	sc->sc_reg_m[LM95221_LOCAL] = LM95221_LOCAL_MSB;
+	sc->sc_reg_l[LM95221_LOCAL] = LM95221_LOCAL_LSB;
+
+	strlcpy(sc->sc_sensor[LM95221_REMOTE1].desc, "remote 1",
+	    sizeof(sc->sc_sensor[LM95221_REMOTE1].desc));
+	sc->sc_reg_m[LM95221_REMOTE1] = LM95221_REMOTE1_MSB;
+	sc->sc_reg_l[LM95221_REMOTE1] = LM95221_REMOTE1_LSB;
+
+	strlcpy(sc->sc_sensor[LM95221_REMOTE2].desc, "remote 2",
+	    sizeof(sc->sc_sensor[LM95221_REMOTE2].desc));
+	sc->sc_reg_m[LM95221_REMOTE2] = LM95221_REMOTE2_MSB;
+	sc->sc_reg_l[LM95221_REMOTE2] = LM95221_REMOTE2_LSB;
+
+	for (i = 0; i < LM95221_NSENSORS; i++) {
+		if (i == LM95221_REMOTE1 &&
+		    (sc->sc_flags[LM95221_REMOTE1] & LM95221_MISSING))
+			continue;
+		if (i == LM95221_REMOTE2 &&
+		    (sc->sc_flags[LM95221_REMOTE2] & LM95221_MISSING))
+			continue;
+
+		sc->sc_sensor[i].units = ENVSYS_STEMP;
+		sc->sc_sensor[i].state = ENVSYS_SINVALID;
+		sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
+
+		if (sysmon_envsys_sensor_attach(sc->sc_sme,
+		    &sc->sc_sensor[i])) {
+			sysmon_envsys_destroy(sc->sc_sme);
+			sc->sc_sme = NULL;
+			aprint_error_dev(sc->sc_dev,
+			    "unable to attach sensor %d to sysmon\n", i);
+			return;
+		}
+		map = sc->sc_sensor[i].sensor;
+		sc->sc_map[map] = i;
+	}
+
+	if (sysmon_envsys_register(sc->sc_sme)) {
+		aprint_error_dev(self, "unable to register with sysmon\n");
+		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
+		return;
+	}
+}
+
+static int
+lm95221ts_detach(device_t self, int flags)
+{
+	struct lm95221ts_softc *sc = device_private(self);
+
+	if (sc->sc_sme != NULL) {
+		sysmon_envsys_unregister(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
+
+	return 0;
+}
+
+void
+lm95221ts_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
+{
+	struct lm95221ts_softc *sc = sme->sme_cookie;
+	uint32_t val;
+	uint8_t reg_m, reg_l, flags;
+	int map;
+
+	map = sc->sc_map[edata->sensor];
+	reg_m = sc->sc_reg_m[map];
+	reg_l = sc->sc_reg_l[map];
+	flags = sc->sc_flags[map];
+
+	if (iic_acquire_bus(sc->sc_tag, 0))
+		return;
+
+	if (lm95221ts_read_temp(sc, reg_m, reg_l, flags, &val) == 0) {
+		edata->value_cur = val;
+		edata->state = ENVSYS_SVALID;
+	} else {
+		edata->state = ENVSYS_SINVALID;
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+}
+
+static int
+lm95221ts_read_temp(struct lm95221ts_softc *sc, uint8_t reg_m, uint8_t reg_l,
+    uint8_t flags, uint32_t *valp)
+{
+	uint8_t buf_m, buf_l;
+	int err;
+
+	err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg_m, 1, &buf_m, 1, 0);
+	if (err)
+		return err;
+	err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg_l, 1, &buf_l, 1, 0);
+	if (err)
+		return err;
+
+	/*
+	 * Convert the 11 high bits from the 2 bytes to a temperature in uK.
+	 * Maybe sign-extend the MSB, and add in the LSB
+	 */
+	if (flags & LM95221_SIGNED)
+		*valp = buf_m;
+	else
+		*valp = (int8_t) buf_m;
+	*valp = (*valp << 3) + ((buf_l >> 5) & 0x07);
+	*valp = *valp * 125000 + 273150000;
+
+	return 0;
+}
--- src/sys/dev/i2c/lm95221reg.h.dist	2026-02-10 22:20:01.329986410 +0100
+++ src/sys/dev/i2c/lm95221reg.h	2026-02-03 07:36:19.779992263 +0100
@@ -0,0 +1,102 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DEV_I2C_LM95221REG_H_
+#define _DEV_I2C_LM95221REG_H_
+
+/* LM95221 temperature sensor register definitions */
+
+#define	LM95221_ADDR		0x2b
+
+/*
+ * Temperature on the LM95221 is represented by a 10-bit (local) and 11-bit
+ * (remote) 2's complement word or 11-bit (remote) unsigned binary word
+ * with an LSB of 0.125C.  The data is left-justified in 2 * 8-bit registers.
+ *
+ * 11-bit 2's complement:
+ *
+ *	+125C	0111 1101 0000 0000	0x7d00
+ *	+25C	0001 1001 0000 0000	0x1900
+ *	+1C	0000 0001 0000 0000	0x0100
+ *      +0.125C	0000 0000 0010 0000	0x0020
+ *	 0C	0000 0000 0000 0000	0x0000
+ *      -0.125C	1111 1111 1110 0000	0xffe0
+ *	-1C	1111 1111 0000 0000	0xff00
+ *	-25C	1110 0111 0000 0000	0xe700
+ *	-55C	1100 1001 0000 0000	0xc900
+ *
+ * 11-bit unsigned binary:
+ *
+ *    +255.875C	1111 1111 1110 0000	0xffe0
+ *	+255C	1111 1111 0000 0000	0xff90
+ *	+201C	1100 1001 0000 0000	0xc900
+ *	+125C	0111 1101 0000 0000	0x7d00
+ *	+25C	0001 1001 0000 0000	0x1900
+ *	+1C	0000 0001 0000 0000	0x0100
+ *      +0.125C	0000 0000 0010 0000	0x0020
+ *	 0C	0000 0000 0000 0000	0x0000
+ *
+ * 10-bit 2's complement is similar to 11-bit 2's complement
+ * (but with 0.25C resolution)
+ */
+
+/* Command bits 0-2 select the register */
+#define	LM95221_STATUS		0x02	/* Status */
+#define	LM95221_CONFIG		0x03	/* Configuration */
+#define	LM95221_1SHOT		0x0f	/* One conversion when in standby */
+#define	LM95221_LOCAL_MSB	0x10	/* Reading MSB locks LSB 0x20 */
+#define	LM95221_REMOTE1_MSB	0x11	/* Reading MSB locks LSB 0x21 */
+#define	LM95221_REMOTE2_MSB	0x12	/* Reading MSB locks LSB 0x22 */
+#define	LM95221_LOCAL_LSB	0x20	/* Reading LSB unlocks it */
+#define	LM95221_REMOTE1_LSB	0x21	/* Reading LSB unlocks it */
+#define	LM95221_REMOTE2_LSB	0x22	/* Reading LSB unlocks it */
+#define	LM95221_MANUF		0xfe	/* Manufacturer ID (0x01) */
+#define	LM95221_REVISION	0xff	/* Manufacturer ID (0x61) */
+
+/* Status register */
+#define LM95221_STAT_NO_REMOTE1	0x01	/* Remote 1 diode missing */
+#define LM95221_STAT_NO_REMOTE2	0x02	/* Remote 1 diode missing */
+#define LM95221_STAT_BUSY	0x80	/* Currently converting */
+
+/* Configuration register */
+#define LM95221_CONF_REMOTE1	0x02	/* Remote 1 signed format */
+#define LM95221_CONF_REMOTE2	0x04	/* Remote 2 signed format */
+#define LM95221_CONF_CONVRATE	0x30	/* Conversion rate */
+#define LM95221_CONF_STANDBY	0x40	/* Disable conversions */
+#define LM95221_CONVRATE_CONT	0x00	/* Continuous every 66ms */
+#define LM95221_CONVRATE_200	0x10	/* Every 200ms */
+#define LM95221_CONVRATE_1000	0x20	/* Every 1s */
+#define LM95221_CONVRATE_3000	0x30	/* Every 3s */
+
+/* Temperature registers */
+#define LM95221_NSENSORS	3
+
+#endif /* _DEV_I2C_LM95221REG_H_ */
--- src/sys/dev/i2c/nxp75a.c.dist	2026-02-10 22:20:01.319986410 +0100
+++ src/sys/dev/i2c/nxp75a.c	2026-05-24 21:37:53.799962166 +0200
@@ -0,0 +1,304 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+
+#include <dev/sysmon/sysmonvar.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/nxp75areg.h>
+
+struct nxp75a_softc {
+	device_t sc_dev;
+	i2c_tag_t sc_tag;
+	int sc_address;
+
+	struct sysmon_envsys *sc_sme;
+	envsys_data_t sc_sensor;
+	uint32_t sc_otemp, sc_hyst;
+};
+
+static int  nxp75a_match(device_t, cfdata_t, void *);
+static void nxp75a_attach(device_t, device_t, void *);
+static int nxp75a_detach(device_t, int);
+void	nxp75a_refresh(struct sysmon_envsys *, envsys_data_t *);
+void    nxp75a_get_limits(struct sysmon_envsys *, envsys_data_t *,
+		    sysmon_envsys_lim_t *, uint32_t *);
+void    nxp75a_set_limits(struct sysmon_envsys *, envsys_data_t *,
+		    sysmon_envsys_lim_t *, uint32_t *);
+static int nxp75a_read_temp(struct nxp75a_softc *, uint8_t, uint32_t *);
+static int nxp75a_write_temp(struct nxp75a_softc *, uint8_t, uint32_t);
+
+CFATTACH_DECL_NEW(nxp75a, sizeof(struct nxp75a_softc),
+	nxp75a_match, nxp75a_attach, nxp75a_detach, NULL);
+
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "i2c-lm75a" },
+	DEVICE_COMPAT_EOL
+};
+
+static int
+nxp75a_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct i2c_attach_args *ia = aux;
+	int match_result;
+
+	if (iic_use_direct_match(ia, cf, compat_data, &match_result))
+		return match_result;
+
+	/*
+	 * Indirect config - assume config is correct!
+	 */
+	if ((ia->ia_addr & NXP75A_ADDRMASK) == NXP75A_ADDR)
+		return I2C_MATCH_ADDRESS_ONLY;
+
+	return 0;
+}
+
+static void
+nxp75a_attach(device_t parent, device_t self, void *aux)
+{
+	struct nxp75a_softc *sc = device_private(self);
+	struct i2c_attach_args *ia = aux;
+	uint8_t reg, val;
+	uint32_t tval;
+
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_address = ia->ia_addr;
+	sc->sc_dev = self;
+
+	aprint_normal(": NXP LM75A temperature sensor\n");
+
+	if (iic_acquire_bus(sc->sc_tag, 0)) {
+		aprint_error_dev(self,
+		    "unable to acquire I2C bus\n");
+		return;
+	}
+
+	/* Read and save overtemp (critical limit) and hysteresis */
+	reg = NXP75A_OVERTEMP;
+	if (nxp75a_read_temp(sc, reg, &tval)) {
+		iic_release_bus(sc->sc_tag, 0);
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read overtemp register\n");
+		return;
+	}
+	sc->sc_otemp = tval;
+	reg = NXP75A_THYST;
+	if (nxp75a_read_temp(sc, reg, &tval)) {
+		iic_release_bus(sc->sc_tag, 0);
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read hysteresis register\n");
+		return;
+	}
+	sc->sc_hyst = tval;
+
+	/* Read config, start conversion */
+	reg = NXP75A_CONFIG;
+	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg, 1, &val, 1, 0)) {
+		iic_release_bus(sc->sc_tag, 0);
+		aprint_error_dev(sc->sc_dev,
+		    "unable to read config register\n");
+		return;
+	}
+
+	if (val & NXP75A_CONF_SHUTDOWN) {
+		val &= ~(NXP75A_CONF_SHUTDOWN);
+		if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_address, &reg, 1, &val, 1, 0)) {
+			iic_release_bus(sc->sc_tag, 0);
+			aprint_error_dev(sc->sc_dev,
+			    "unable to write config register\n");
+			return;
+		}
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+
+	sc->sc_sme = sysmon_envsys_create();
+	sc->sc_sme->sme_name = device_xname(self);
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_refresh = nxp75a_refresh;
+	sc->sc_sme->sme_get_limits = nxp75a_get_limits;
+	sc->sc_sme->sme_set_limits = nxp75a_set_limits;
+
+	strlcpy(sc->sc_sensor.desc, "temperature", sizeof(sc->sc_sensor.desc));
+	sc->sc_sensor.units = ENVSYS_STEMP;
+	sc->sc_sensor.state = ENVSYS_SINVALID;
+	sc->sc_sensor.flags = ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
+
+	if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor)) {
+		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
+		aprint_error_dev(sc->sc_dev,
+		    "unable to attach sensor to sysmon\n");
+		return;
+	}
+
+	if (sysmon_envsys_register(sc->sc_sme)) {
+		aprint_error_dev(self, "unable to register with sysmon\n");
+		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
+		return;
+	}
+}
+
+static int
+nxp75a_detach(device_t self, int flags)
+{
+	struct nxp75a_softc *sc = device_private(self);
+
+	if (sc->sc_sme != NULL) {
+		sysmon_envsys_unregister(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
+
+	return 0;
+}
+
+void
+nxp75a_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
+{
+	struct nxp75a_softc *sc = sme->sme_cookie;
+	uint8_t reg;
+	uint32_t val;
+
+	if (iic_acquire_bus(sc->sc_tag, 0))
+		return;
+
+	reg = NXP75A_TEMP;
+	if (nxp75a_read_temp(sc, reg, &val) == 0) {
+		edata->value_cur = val;
+		edata->state = ENVSYS_SVALID;
+	} else {
+		edata->state = ENVSYS_SINVALID;
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+}
+
+void
+nxp75a_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
+    sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	struct nxp75a_softc *sc = sme->sme_cookie;
+	uint8_t reg;
+	uint32_t val;
+
+	*props &= ~(PROP_CRITMAX | PROP_WARNMAX | PROP_WARNMIN);
+
+	if (iic_acquire_bus(sc->sc_tag, 0))
+		return;
+
+	reg = NXP75A_OVERTEMP;
+	if (nxp75a_read_temp(sc, reg, &val) == 0) {
+		limits->sel_critmax = val;
+		*props |= PROP_CRITMAX;
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+}
+
+void
+nxp75a_set_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
+    sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	struct nxp75a_softc *sc = sme->sme_cookie;
+	uint8_t reg;
+	uint32_t otemp, hyst;
+
+	if (iic_acquire_bus(sc->sc_tag, 0))
+		return;
+
+	if (limits == NULL || *props & PROP_CRITMAX) {
+		if (limits == NULL) {	/* Restore defaults */
+			otemp = sc->sc_otemp;
+			hyst = sc->sc_hyst;
+		} else {
+			otemp = limits->sel_critmax;
+			/* Make sure that hyst is less than overtemp */
+			hyst = otemp - (sc->sc_otemp - sc->sc_hyst);
+		}
+		reg = NXP75A_OVERTEMP;
+		if (nxp75a_write_temp(sc, reg, otemp) == 0) {
+			reg = NXP75A_THYST;
+			nxp75a_write_temp(sc, reg, hyst);
+		}
+	}
+			
+	iic_release_bus(sc->sc_tag, 0);
+}
+
+static int
+nxp75a_read_temp(struct nxp75a_softc *sc, uint8_t reg, uint32_t *valp)
+{
+	uint8_t buf[NXP75A_TEMP_LEN];
+	int err;
+
+	err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+	    sc->sc_address, &reg, 1, buf, NXP75A_TEMP_LEN, 0);
+	if (err)
+		return err;
+
+	/*
+	 * Convert the 11 high bits from the 2 bytes to a temperature in uK.
+	 * Sign-extend the MSB and add in the LSB
+	 */
+	*valp = (int8_t) buf[0];
+	*valp = (*valp << 3) + ((buf[1] >> 5) & 0x07);
+	*valp = *valp * 125000 + 273150000;
+
+	return 0;
+}
+
+static int
+nxp75a_write_temp(struct nxp75a_softc *sc, uint8_t reg, uint32_t val)
+{
+	uint8_t buf[NXP75A_TEMP_LEN];
+	uint32_t temp;
+
+	/* Convert the temperature in uK to 11 high bits in 2 bytes.*/
+	temp = (int) (val - 273150000) / 125000 ;
+	buf[0] = (temp >> 3) & 0xff;
+	buf[1] = (temp << 5) & 0xe0;
+
+	return iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+	    sc->sc_address, &reg, 1, buf, NXP75A_TEMP_LEN, 0);
+}
+
--- src/sys/dev/i2c/nxp75areg.h.dist	2026-02-10 22:20:01.329986410 +0100
+++ src/sys/dev/i2c/nxp75areg.h	2026-02-16 16:18:22.169999344 +0100
@@ -0,0 +1,82 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Julian Coleman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DEV_I2C_NXP75AREG_H_
+#define _DEV_I2C_NXP75AREG_H_
+
+/*
+ * *NXP* LM75A temperature sensor register definitions.
+ * Pin compatible but not the same as the *TI* LM75A.
+ */
+
+#define	NXP75A_ADDRMASK		0x3f8
+#define	NXP75A_ADDR		0x48
+
+/*
+ * Temperature on the NXP LM75A is represented by a 11-bit two's complement
+ * word with the LSB equal to 0.125C.  From the data sheet:
+ *
+ *	+127C	011 1111 1000	0x3f8
+ *    +126.875C	011 1111 0111	0x3f7
+ *    +126.125C	011 1111 0001	0x3f1
+ *	+125C	011 1110 1000	0x3e8
+ *	+25C	000 1100 1000	0x9c8
+ *      +0.125C	000 0000 0001	0x001
+ *	 0C	000 0000 0000	0x000
+ *      -0.125C	111 1111 1111	0x7ff
+ *	-25C	111 0011 1000	0x738
+ *     -54.875C	110 0100 1001	0x649
+ *	-55C	110 0100 1000	0x648
+ *
+ * The bits are left-shifted to take the high 11 bits of the 2 registers.
+ */
+
+/* Command bits 0-2 select the register */
+#define	NXP75A_TEMP		0x00
+#define	NXP75A_CONFIG		0x01
+#define	NXP75A_THYST		0x02
+#define	NXP75A_OVERTEMP		0x03
+
+/* Configuration register */
+#define NXP75A_CONF_SHUTDOWN	(1 << 0)	/* Shutdown mode */
+#define NXP75A_CONF_OS_MODE_INT	(1 << 1)	/* OS interrupt mode*/
+#define NXP75A_CONF_OS_MODE_CMP	(1 << 0)	/* OS comparator mode*/
+#define NXP75A_CONF_OS_POL_HIGH	(2 << 1)	/* OS polarity high */
+#define NXP75A_CONF_OS_POL_LOW	(2 << 0)	/* OS polarity low */
+#define NXP75A_CONF_OS_FLTQ_1	(0 << 3)	/* OS fault queue len 1 */
+#define NXP75A_CONF_OS_FLTQ_2	(1 << 3)	/* OS fault queue len 2 */
+#define NXP75A_CONF_OS_FLTQ_4	(2 << 3)	/* OS fault queue len 4 */
+#define NXP75A_CONF_OS_FLTQ_6	(3 << 3)	/* OS fault queue len 6 */
+
+#define NXP75A_TEMP_LEN		2	/* 2 bytes for temperatures */
+#define NXP75A_CONF_LEN		1	/* 1 byte for configuration */
+
+#endif /* _DEV_I2C_NXP75AREG_H_ */
--- src/sys/dev/i2c/files.i2c.dist	2026-02-10 22:19:26.249986431 +0100
+++ src/sys/dev/i2c/files.i2c	2026-05-26 16:38:18.349972880 +0200
@@ -464,3 +464,18 @@
 # NXP SC16IS7xx UART bridge
 attach sc16is7xx at iic with sc16is7xxi2c
 file dev/i2c/sc16is7xxi2c.c			sc16is7xxi2c
+
+# Texas Instruments LM95221 temperature sensor
+device	lm95221ts: sysmon_envsys
+attach	lm95221ts at iic
+file	dev/i2c/lm95221.c			lm95221ts
+
+# NXP LM75A temperature sensor
+device	nxp75a: sysmon_envsys
+attach	nxp75a at iic
+file	dev/i2c/nxp75a.c			nxp75a
+
+# ADM7462 system monitor
+device	adt7462sm: sysmon_envsys
+attach	adt7462sm at iic
+file	dev/i2c/adt7462.c			adt7462sm
--- src/sys/dev/pci/alipm.c.dist	2026-01-23 16:28:47.229998971 +0100
+++ src/sys/dev/pci/alipm.c	2026-05-28 19:45:06.489966199 +0200
@@ -144,35 +144,38 @@
 		/* Map I/O space */
 		iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_BASE);
 		sc->sc_iot = pa->pa_iot;
-		if (iobase == 0 ||
-		    bus_space_map(sc->sc_iot, iobase >> 16,
+		if (iobase == 0) {
+			aprint_error(": I/O base not set\n");
+			return;
+		}
+		if (bus_space_map(sc->sc_iot, iobase >> 16,
 		    iosize, 0, &sc->sc_ioh)) {
-			aprint_error_dev(sc->sc_dev, "can't map I/O space\n");
+			aprint_error("can't map I/O space\n");
 			return;
 		}
 
 		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_CONF);
 		if ((reg & ALIPM_CONF_SMBEN) == 0) {
-			aprint_error_dev(sc->sc_dev, "SMBus disabled\n");
+			aprint_error("SMBus disabled\n");
 			goto fail;
 		}
 
 		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_SMB_HOSTC);
 		if ((reg & ALIPM_SMB_HOSTC_HSTEN) == 0) {
-			aprint_error_dev(sc->sc_dev, "SMBus host disabled\n");
+			aprint_error("SMBus host disabled\n");
 			goto fail;
 		}
 	} else {
 		/* Map I/O space */
 		if (pci_mapreg_map(pa, ALIPM_SMB_BASE, PCI_MAPREG_TYPE_IO, 0,
 		    &sc->sc_iot, &sc->sc_ioh, NULL, &iosize)) {
-			aprint_error_dev(sc->sc_dev, "can't map I/O space\n");
+			aprint_error("can't map I/O space\n");
 			return;
 		}
 
 		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_SMB_HOSTX);
 		if ((reg & ALIPM_SMB_HOSTC_HSTEN) == 0) {
-			aprint_error_dev(sc->sc_dev, "SMBus host disabled\n");
+			aprint_error("SMBus host disabled\n");
 			goto fail;
 		}
 	}
--- src/sys/dev/pci/radeonfb.c.dist	2026-05-26 15:53:49.079974471 +0200
+++ src/sys/dev/pci/radeonfb.c	2026-05-26 15:52:52.889974505 +0200
@@ -463,6 +463,7 @@
 	pcireg_t		screg;
 	int			i, j, fg, bg, ul, flags;
 	uint32_t		v;
+	char			memstr[16] = "";
 
 	sc->sc_dev = dev;
 	sc->sc_id = pa->pa_id;
@@ -654,8 +655,10 @@
 	radeonfb_set_fbloc(sc);
 
 	/* 64 MB should be enough -- more just wastes map entries */
-	if (sc->sc_memsz > (64 << 20))
+	if (sc->sc_memsz > (64 << 20)) {
+		snprintf(memstr, 16, " %d MB,", (int)sc->sc_memsz >> 20);
 		sc->sc_memsz = (64 << 20);
+	}
 
 	for (i = 0; radeonfb_limits[i].size; i++) {
 		if (sc->sc_memsz >= radeonfb_limits[i].size) {
@@ -806,8 +809,8 @@
 		goto error;
 	}
 
-	aprint_normal("%s: %d MB aperture at 0x%08x, "
-	    "%d KB registers at 0x%08x\n", XNAME(sc),
+	aprint_normal("%s:%s %d MB aperture at 0x%08x, "
+	    "%d KB registers at 0x%08x\n", XNAME(sc), memstr,
 	    (int)sc->sc_memsz >> 20, (unsigned)sc->sc_memaddr,
 	    (int)sc->sc_regsz >> 10, (unsigned)sc->sc_regaddr);
 
--- src/share/man/man4/adt7462sm.4.dist	2026-02-19 19:33:51.879992352 +0100
+++ src/share/man/man4/adt7462sm.4	2026-05-27 20:25:27.807823290 +0200
@@ -0,0 +1,245 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2026 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Julian Coleman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd February 19, 2026
+.Dt ADT7462SM 4
+.Os
+.Sh NAME
+.Nm adt7462sm
+.Nd On Semiconductor (Analog Devices) ADT7462 Flexible Temperature,
+Voltage Monitor, and System Fan Controller
+.Sh SYNOPSIS
+.Cd "adt7462sm* at iic? addr?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the On Semiconductor ADT7462 system monitor.
+The chip possesses 8 fan speed sensors, 4 temperature sensors,
+and 13 voltage sensors.
+The number of each sensor type configured by the driver depends on the
+chip configuration.
+.Pp
+The values of the sensors are made available through the
+.Xr envstat 8
+interface.
+.Bl -column ".Li fan fault" "mV DC" -offset indent
+.It Sy Sensor     Ta Sy Units  Ta Sy Description
+.It Li fan Ar N      Ta RPM    Ta Fan 0\^\(en7
+.It Li local         Ta C      Ta Local temperature
+.It Li remote Ar N   Ta C      Ta Remote temperature 1\(en3
+.It Li V2.5 Ar N     Ta mV DC  Ta 2.5V voltage 1\(en3
+.It Li V1.8 Ar N     Ta mV DC  Ta 1.8V voltage 1\(en3
+.It Li V1.5 Ar N     Ta mV DC  Ta 1.5V voltage 1\(en4
+.It Li V3.3 Ar N     Ta mV DC  Ta 3.3V voltage 1\(en2
+.It Li V1.2 Ar N     Ta mV DC  Ta 1.2V voltage 1\(en2
+.It Li Vbatt         Ta mV DC  Ta Battery voltage
+.It Li V12 Ar N      Ta mV DC  Ta 12V voltage 1\(en3
+.It Li V0.9          Ta mV DC  Ta 0.9V voltage
+.It Li V1.25         Ta mV DC  Ta 1.25V voltage
+.It Li V5            Ta mV DC  Ta 5V voltage
+.It Li fan fault     Ta none   Ta Fan fault display
+.El
+.Pp
+Configurable limits for the sensors are also available via
+.Xr envstat 8 .
+Initial limit values are read from the chip.
+Each temperature sensor has therm, high and low limits, which are
+mapped to
+.Fa critmax ,
+.Fa warnmax
+and
+.Fa warnmin ,
+respectively.
+Voltage sensors have high
+.Pq Fa warnmax
+and low
+.Pq Fa warnmin
+limits.
+Fan sensors have a high limit mapped to
+.Fa warnmin ,
+because the fan measurements are revolution intervals, so higher
+numbers correlate to lower fan speeds.
+Note, that there are two therm limits per temperature sensor
+and the one with the lowest value is used as
+.Fa critmax .
+The fan fault sensor has a value of
+.Em 0
+in normal operation.
+If a fault is detected, the fan fault value will represent the numbers
+of the failed fans,
+.Em 1
+for fan 0,
+.Em 10
+for fan 1, and so on, to
+.Em 10000000
+for fan 8.
+.Pp
+The chip also supports Pulse-Width Modulated (PWM) fan speed control and the
+.Nm
+driver
+makes PWM and temperature settings available via
+.Xr sysctl 8 .
+For each PWM channel, the controlling temperature channel is shown.
+For example:
+.Bd -literal -offset indent
+hw.adt7462sm0.pwm1.channel = remote1_dynamic
+hw.adt7462sm0.pwm2.channel = remote2_dynamic
+hw.adt7462sm0.pwm3.channel = remote3
+hw.adt7462sm0.pwm4.channel = manual
+.Ed
+.Pp
+If the channel is shown as
+.Dq manual ,
+then the PWM duty cycle can be altered through the corresponding
+.Fa duty_cycle
+variable.  For example:
+.Bd -literal -offset indent
+hw.adt7462sm0.pwm4.duty_cycle = 12
+.Ed
+.Pp
+The
+.Fa duty cycle
+can be set to any value between 0% (off) and 100% (full) although the
+minimum non-zero value for a fan is usually in the 20% to 33% range.
+.Pp
+If the channel is shown as
+.Dq remoteN ,
+then the.
+.Fa tmin
+and
+.Fa trange
+variables for that
+.Dq remoteN
+can be used to adjust the PWM value and fan speed.
+For example:
+.Bd -literal -offset indent
+hw.adt7462sm0.remote3.tmin = 40
+hw.adt7462sm0.remote3.trange = 32
+.Ed
+.Pp
+The
+.Fa tmin
+value is the temperature in degC at which the fan starts and the
+.Fa trange
+value represents the temperature range over which the fan speed
+increases until it reaches full speed.
+Both of these values can be altered, although
+.Fa tmin
+must be lower than the corresponding
+.Fa critmax
+value from
+.Xr envstat
+and
+.Fa trange
+must be between 2 and 80.
+.Pp
+If the channel is shown as
+.Dq remoteN_dynamic ,
+then the
+.Fa tmin
+value is read-only,
+and the
+.Fa trange
+and
+.Fa oppoint
+variables for that
+.Dq remoteN
+are used to adjust the PWM value and fan speed.
+For example:
+.Bd -literal -offset indent
+hw.adt7462sm0.remote1.tmin = 40
+hw.adt7462sm0.remote1.trange = 32
+hw.adt7462sm0.remote1.oppoint = 60
+.Ed
+.Pp
+The
+.Fa tmin
+value is still the temperature in degC at which the fan starts, but is
+dynamically calculated by the chip based on the
+.Fa oppoint
+(operating point)
+and
+.Fa trange
+values.
+The operating point is a target temperature and the chip will attempt
+to maintain that temperature or lower.
+It does this by adjusting the
+.Fa tmin
+value based on the
+.Fa trange ,
+thus causing the PWM values and fan speed to vary based on the difference
+between the current and operating point temperatures.
+Both the
+.Fa oppoint
+and
+.Fa trange
+values can be altered, although
+.Fa oppoint
+must be higher than
+.Fa tmin .
+.Pp
+If the channel is shown as
+.Dq local_remote3 ,
+then
+.Fa tmin
+and
+.Fa trange
+for both the
+.Dq local
+and the
+.Dq remote3
+temperature sensors affect the PWM value and fan speed.
+.Pp
+If the channel is shown as
+.Dq off ,
+then it can not be altered.
+.Sh SEE ALSO
+.Xr iic 4 ,
+.Xr intro 4 ,
+.Xr envstat 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 11.1 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Julian Coleman Aq Mt jcoleman@NetBSD.org .
+.Sh BUGS
+It's not possible to determine if either a sensor is not connected,
+or the monitored device is producing no output.
+Therefore, unconnected sensors will show outputs of 0.
+.Pp
+The
+.Nm
+driver does not support interrupt output.
--- src/share/man/man4/lm95221ts.4.dist	2026-02-13 21:47:41.837831341 +0100
+++ src/share/man/man4/lm95221ts.4	2026-05-26 12:52:40.009980950 +0200
@@ -0,0 +1,63 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2026 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Julian Coleman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd February 13, 2026
+.Dt LM95221TS 4
+.Os
+.Sh NAME
+.Nm lm95221ts
+.Nd LM95221 Dual Remote Diode Digital Temperature Sensor
+.Sh SYNOPSIS
+.Cd "lm95221ts* at iic? addr?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the Texas Instruments LM95221
+temperature sensor, which supports a local and two remote temperature
+sensors.
+The local sensor has a resolution of 0.25 degC whilst the remote
+sensors have a resolution of 0.125 degC.
+The temperature values are available through the 
+.Xr envstat 8
+interface.
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr intro 4 ,
+.Xr envstat 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 11.1 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Julian Coleman Aq Mt jcoleman@netbsd.org .
--- src/share/man/man4/nxp75a.4.dist	2026-02-13 21:47:02.827831364 +0100
+++ src/share/man/man4/nxp75a.4	2026-05-26 12:52:45.829980946 +0200
@@ -0,0 +1,69 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2026 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Julian Coleman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd February 13, 2026
+.Dt NXP75A 4
+.Os
+.Sh NAME
+.Nm nxp75a
+.Nd NXP LM75A Digital temperature sensor and thermal watchdog
+.Sh SYNOPSIS
+.Cd "nxp75a* at iic? addr?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the NXP LM75A temperature sensor,
+with a temperature resolution of 0.125 degC and a single
+programable high limit.
+Both the temperature value and the limit are available through the
+.Xr envstat 8
+interface.
+.Pp
+Note, that although this chip is described as a
+.Qq pin-for-pin replacement for industry standard LM75
+it is not software compatible with it.
+The LM75 and similar chips are supported via the
+.Xr lmtemp 4
+driver.
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr intro 4 ,
+.Xr lmtemp 4 ,
+.Xr envstat 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 11.1 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Julian Coleman Aq Mt jcoleman@netbsd.org .
--- src/share/man/man4/pcagpio.4.dist	2026-02-13 21:10:09.207832683 +0100
+++ src/share/man/man4/pcagpio.4	2026-02-13 21:44:37.017831451 +0100
@@ -0,0 +1,64 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2020 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Julian Coleman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd November 15, 2020
+.Dt PCAGPIO 4
+.Os
+.Sh NAME
+.Nm pcagpio
+.Nd Philips Semiconductors PCA9555 and PCA9556 GPIO controller
+.Sh SYNOPSIS
+.Cd "pcagpio* at iic? addr?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the 8-pin PCA9555 and 16-pin PCA9556
+GPIO controllers.
+System properties are used to configure the pins as LED's or sensors
+which are then exposed via the
+.Xr led
+or
+.Xr envsys
+frameworks.
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr intro 4 ,
+.Xr led 4 ,
+.Xr envstat 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Michael Lorenz Aq Mt macallan@netbsd.org .
--- src/share/man/man4/pcf8574io.4.dist	2026-02-13 21:10:25.747832674 +0100
+++ src/share/man/man4/pcf8574io.4	2026-02-13 21:45:27.037831421 +0100
@@ -0,0 +1,67 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2020 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Julian Coleman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd February 13, 2026
+.Dt PCF8754IO 4
+.Os
+.Sh NAME
+.Nm pcf8574io
+.Nd Philips Semiconductors PCF8574 and PCF857A GPIO Controllers
+.Sh SYNOPSIS
+.Cd "pcf8574io* at iic? addr?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the PCF8574 and PCF857A
+GPIO controllers.
+These differ only in their address on the i2c bus.
+System properties are used to configure the pins as LED's or sensors
+which are then exposed via the
+.Xr led
+or
+.Xr envsys
+frameworks.
+If any pins are configured as alert sensors, then a console message
+will be printed if the pin status changes.
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr intro 4 ,
+.Xr led 4 ,
+.Xr envstat 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Julian Coleman Aq Mt jcoleman@netbsd.org .
--- src/share/man/man4/Makefile.dist	2026-05-26 16:14:04.029973747 +0200
+++ src/share/man/man4/Makefile	2026-05-27 10:31:47.257844521 +0200
@@ -3,7 +3,8 @@
 
 MAN=	aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
 	adbbt.4 adbkbd.4 adbms.4 \
-	adc.4 adm1026hm.4 admtemp.4 adv.4 adw.4 age.4 agp.4 agr.4 ahb.4 ahc.4 \
+	adc.4 adm1026hm.4 admtemp.4 adt7462sm.4 \
+	adv.4 adw.4 age.4 agp.4 agr.4 ahb.4 ahc.4 \
 	ahcisata.4 ahd.4 aht20temp.4 aibs.4 alc.4 ale.4 alipm.4 altmem.4 altq.4 \
 	am2315temp.4 amdgpio.4 amdpm.4 amdtemp.4 amhphy.4 amr.4 aps.4 asus.4 \
 	an.4 aq.4 arcmsr.4 arcofi.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \
@@ -39,7 +40,7 @@
 	ixg.4 ixl.4 ixpide.4 ixv.4 \
 	jme.4 jmide.4 jmphy.4 joy.4 \
 	kcov.4 kloader.4 kse.4 ksyms.4 kttcp.4 \
-	l2tp.4 lagg.4 lc.4 ld.4 lii.4 lo.4 lua.4 lxtphy.4 \
+	l2tp.4 lagg.4 lc.4 ld.4 lii.4 lm95221ts.4 lo.4 lua.4 lxtphy.4 \
 	machfb.4 \
 	mainbus.4 makphy.4 malo.4 mbe.4 mca.4 mcclock.4 mcommphy.4 \
 	mcx.4 md.4 mfb.4 mfi.4 mfii.4 mhzc.4 \
@@ -47,10 +48,11 @@
 	mpu.4 mtd.4 mtio.4 msm6242b.4 multicast.4 mvsata.4 \
 	nadb.4 ne.4 neo.4 netintro.4 nfe.4 nfsmb.4 njata.4 njs.4 npflog.4 \
 	nsclpcsio.4 nside.4 nsphy.4 nsphyter.4 ntwoc.4 null.4 \
-	nvme.4 nvmm.4 \
+	nvme.4 nvmm.4 nxp75a.4 \
 	oak.4 oosiop.4 opl.4 options.4 optiide.4 osiop.4 otus.4 \
-	pad.4 pas.4 pcdisplay.4 pcf8563rtc.4 pchtemp.4 pciide.4 pckbc.4 pckbd.4 \
-	pcn.4 pcppi.4 pcscp.4 pcweasel.4 pdcide.4 pdcsata.4 piixide.4 piixpcib.4 \
+	pad.4 pas.4 pcagpio.4 pcdisplay.4 pcf8563rtc.4 pcf8574io.4 pchtemp.4 \
+	pciide.4 pckbc.4 pckbd.4  pcn.4 pcppi.4 pcscp.4 pcweasel.4 \
+	pdcide.4 pdcsata.4 piixide.4 piixpcib.4 \
 	piixpm.4 pim.4 plip.4 pm3fb.4 pms.4 pmu.4 pnaphy.4 ppbus.4 ppp.4 \
 	pppoe.4 \
 	pseye.4 ptcd.4 ptm.4 pty.4 puc.4 pud.4 puffs.4 pwdog.4 px.4 pxagpio.4 \
--- src/distrib/sets/lists/man/mi.dist	2026-05-26 16:24:48.289973363 +0200
+++ src/distrib/sets/lists/man/mi	2026-05-26 21:27:13.649962548 +0200
@@ -856,6 +856,7 @@
 ./usr/share/man/cat4/adm1027.0			man-sys-catman		.cat
 ./usr/share/man/cat4/adm1030.0			man-sys-catman		.cat
 ./usr/share/man/cat4/admtemp.0			man-sys-catman		.cat
+./usr/share/man/cat4/adt7462sm.0		man-sys-catman		.cat
 ./usr/share/man/cat4/adt7463.0			man-sys-catman		.cat
 ./usr/share/man/cat4/adt7466.0			man-sys-catman		.cat
 ./usr/share/man/cat4/adt7467.0			man-sys-catman		.cat
@@ -1536,6 +1537,7 @@
 ./usr/share/man/cat4/lii.0			man-sys-catman		.cat
 ./usr/share/man/cat4/lkm.0			man-obsolete		obsolete
 ./usr/share/man/cat4/lm.0			man-sys-catman		.cat
+./usr/share/man/cat4/lm95221ts.0		man-sys-catman		.cat
 ./usr/share/man/cat4/lmc.0			man-obsolete		obsolete
 ./usr/share/man/cat4/lmenv.0			man-sys-catman		.cat
 ./usr/share/man/cat4/lmtemp.0			man-sys-catman		.cat
@@ -1672,6 +1674,7 @@
 ./usr/share/man/cat4/null.0			man-sys-catman		.cat
 ./usr/share/man/cat4/nvme.0			man-sys-catman		.cat
 ./usr/share/man/cat4/nvmm.0			man-sys-catman		.cat
+./usr/share/man/cat4/nxp75a.0			man-sys-catman		.cat
 ./usr/share/man/cat4/nxt2k.0			man-sys-catman		.cat
 ./usr/share/man/cat4/oak.0			man-sys-catman		.cat
 ./usr/share/man/cat4/oboe.0			man-sys-catman		.cat
@@ -1694,9 +1697,11 @@
 ./usr/share/man/cat4/pc532/ncr.0		man-obsolete		obsolete
 ./usr/share/man/cat4/pc532/plip.0		man-obsolete		obsolete
 ./usr/share/man/cat4/pc532/scn.0		man-obsolete		obsolete
+./usr/share/man/cat4/pcagpio.0			man-sys-catman		.cat
 ./usr/share/man/cat4/pcdisplay.0		man-sys-catman		.cat
 ./usr/share/man/cat4/pceb.0			man-sys-catman		.cat
 ./usr/share/man/cat4/pcf8563rtc.0		man-sys-catman		.cat
+./usr/share/man/cat4/pcf8574io.0		man-sys-catman		.cat
 ./usr/share/man/cat4/pchb.0			man-sys-catman		.cat
 ./usr/share/man/cat4/pchtemp.0			man-sys-catman		.cat
 ./usr/share/man/cat4/pci.0			man-sys-catman		.cat
@@ -4449,6 +4454,7 @@
 ./usr/share/man/man4/adm1027.4			man-sys-man		.man
 ./usr/share/man/man4/adm1030.4			man-sys-man		.man
 ./usr/share/man/man4/admtemp.4			man-sys-man		.man
+./usr/share/man/man4/adt7462sm.4		man-sys-man		.man
 ./usr/share/man/man4/adt7463.4			man-sys-man		.man
 ./usr/share/man/man4/adt7466.4			man-sys-man		.man
 ./usr/share/man/man4/adt7467.4			man-sys-man		.man
@@ -5129,6 +5135,7 @@
 ./usr/share/man/man4/lii.4			man-sys-man		.man
 ./usr/share/man/man4/lkm.4			man-obsolete		obsolete
 ./usr/share/man/man4/lm.4			man-sys-man		.man
+./usr/share/man/man4/lm95221ts.4		man-sys-man		.man
 ./usr/share/man/man4/lmc.4			man-obsolete		obsolete
 ./usr/share/man/man4/lmenv.4			man-sys-man		.man
 ./usr/share/man/man4/lmtemp.4			man-sys-man		.man
@@ -5265,6 +5272,7 @@
 ./usr/share/man/man4/null.4			man-sys-man		.man
 ./usr/share/man/man4/nvme.4			man-sys-man		.man
 ./usr/share/man/man4/nvmm.4			man-sys-man		.man
+./usr/share/man/man4/nxp75a.4			man-sys-man		.man
 ./usr/share/man/man4/nxt2k.4			man-sys-man		.man
 ./usr/share/man/man4/oak.4			man-sys-man		.man
 ./usr/share/man/man4/oboe.4			man-sys-man		.man
@@ -5287,9 +5295,11 @@
 ./usr/share/man/man4/pc532/ncr.4		man-obsolete		obsolete
 ./usr/share/man/man4/pc532/plip.4		man-obsolete		obsolete
 ./usr/share/man/man4/pc532/scn.4		man-obsolete		obsolete
+./usr/share/man/man4/pcagpio.4			man-sys-man		.man
 ./usr/share/man/man4/pcdisplay.4		man-sys-man		.man
 ./usr/share/man/man4/pceb.4			man-sys-man		.man
 ./usr/share/man/man4/pcf8563rtc.4		man-sys-man		.man
+./usr/share/man/man4/pcf8574io.4		man-sys-man		.man
 ./usr/share/man/man4/pchb.4			man-sys-man		.man
 ./usr/share/man/man4/pchtemp.4			man-sys-man		.man
 ./usr/share/man/man4/pci.4			man-sys-man		.man
--- src/distrib/sets/lists/manhtml/mi.dist	2026-05-26 16:24:48.429973363 +0200
+++ src/distrib/sets/lists/manhtml/mi	2026-05-26 16:32:43.349973080 +0200
@@ -775,6 +775,7 @@
 ./usr/share/man/html4/adm1027.html		man-sys-htmlman		html
 ./usr/share/man/html4/adm1030.html		man-sys-htmlman		html
 ./usr/share/man/html4/admtemp.html		man-sys-htmlman		html
+./usr/share/man/html4/adt7462sm.html		man-sys-htmlman		html
 ./usr/share/man/html4/adt7463.html		man-sys-htmlman		html
 ./usr/share/man/html4/adt7466.html		man-sys-htmlman		html
 ./usr/share/man/html4/adt7467.html		man-sys-htmlman		html
@@ -1382,6 +1383,7 @@
 ./usr/share/man/html4/lii.html			man-sys-htmlman		html
 ./usr/share/man/html4/lkm.html			man-obsolete		obsolete
 ./usr/share/man/html4/lm.html			man-sys-htmlman		html
+./usr/share/man/html4/lm95221ts.html		man-sys-htmlman		html
 ./usr/share/man/html4/lmc.html			man-obsolete		obsolete
 ./usr/share/man/html4/lmenv.html		man-sys-htmlman		html
 ./usr/share/man/html4/lmtemp.html		man-sys-htmlman		html
@@ -1509,6 +1511,7 @@
 ./usr/share/man/html4/null.html			man-sys-htmlman		html
 ./usr/share/man/html4/nvme.html			man-sys-htmlman		html
 ./usr/share/man/html4/nvmm.html			man-sys-htmlman		html
+./usr/share/man/html4/nxp75a.html		man-sys-htmlman		html
 ./usr/share/man/html4/nxt2k.html		man-sys-htmlman		html
 ./usr/share/man/html4/oak.html			man-sys-htmlman		html
 ./usr/share/man/html4/oboe.html			man-sys-htmlman		html
@@ -1524,9 +1527,11 @@
 ./usr/share/man/html4/owtemp.html		man-sys-htmlman		html
 ./usr/share/man/html4/pad.html			man-sys-htmlman		html
 ./usr/share/man/html4/pas.html			man-sys-htmlman		html
+./usr/share/man/html4/pcagpio.html		man-sys-htmlman		html
 ./usr/share/man/html4/pcdisplay.html		man-sys-htmlman		html
 ./usr/share/man/html4/pceb.html			man-sys-htmlman		html
 ./usr/share/man/html4/pcf8563rtc.html		man-sys-htmlman		html
+./usr/share/man/html4/pcf8574io.html		man-sys-htmlman		html
 ./usr/share/man/html4/pchb.html			man-sys-htmlman		html
 ./usr/share/man/html4/pchtemp.html		man-sys-htmlman		html
 ./usr/share/man/html4/pci.html			man-sys-htmlman		html
