git: cd3cc6e910c0 - main - i2c/sensors: Add driver for W83793 hardware monitor

From: Justin Hibbits <jhibbits_at_FreeBSD.org>
Date: Wed, 20 May 2026 02:23:38 UTC
The branch main has been updated by jhibbits:

URL: https://cgit.FreeBSD.org/src/commit/?id=cd3cc6e910c0f739925c57e42fae6781d693db02

commit cd3cc6e910c0f739925c57e42fae6781d693db02
Author:     Justin Hibbits <jhibbits@FreeBSD.org>
AuthorDate: 2026-05-20 02:04:30 +0000
Commit:     Justin Hibbits <jhibbits@FreeBSD.org>
CommitDate: 2026-05-20 02:05:00 +0000

    i2c/sensors: Add driver for W83793 hardware monitor
    
    The Winbond/Nuvoton W83793G system monitor chip includes many features
    not currently supported by this driver.  The following are currently
    supported:
    
    * Up to 6 temperature sensors, 4 of which have 10-bit resolution
      (8.2),two with 8-bit resolution (no decimal component)
    * Up to 12 fans
      - Fans 0-4 (1-5 on the datasheet) are always enabled.  The remaining 7
        fans are individually enabled.
    * Multiple voltage sensors, reading up to 10 voltage sources.  Sysctls
      are labeled to match the datasheet.
    * Chassis open detection.
    
    The W83793AG is a feature-reduced version, which lacks 3 thermal diodes
    and 2 voltage monitors.  Since there is no way to tell the difference
    between the W83793AG and W83793G programmatically, sensors reported on
    the W83793AG will report strange values.
    
    Temperature sensors and 7 of the fans can be individually enabled on the
    chip, but currently not configured by this driver.  The driver only
    reports what was configured by the firmware.  Additionally, this driver
    numbers the sensors and fans according to the datasheet, so even if, for
    instance, fan 8 is disabled, it would skip from fan 7 to fan 9, it does
    not renumber.  This makes it easier to follow for hardware debugging
    purposes.
    
    Missing features:
    * Smart Fan support
    * Fan PWM control
    * ASF (Alert Standard Format)
    
    Only Bank 0 registers are used at this time.
    
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D56776
---
 sys/conf/files                   |   1 +
 sys/dev/iicbus/sensor/w83793g.c  | 366 +++++++++++++++++++++++++++++++++++++++
 sys/modules/i2c/Makefile         |   3 +-
 sys/modules/i2c/w83793g/Makefile |  14 ++
 4 files changed, 383 insertions(+), 1 deletion(-)

diff --git a/sys/conf/files b/sys/conf/files
index 6804c9c81c69..fac94252a362 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1866,6 +1866,7 @@ dev/iicbus/rtc/s35390a.c	optional s35390a
 dev/iicbus/sensor/htu21.c	optional htu21
 dev/iicbus/sensor/lm75.c	optional lm75
 dev/iicbus/sensor/max44009.c	optional max44009
+dev/iicbus/sensor/w83793g.c	optional w83793g
 dev/iicbus/gpio/pcf8574.c	optional pcf8574
 dev/iicbus/gpio/tca64xx.c	optional tca64xx fdt gpio
 dev/iicbus/pmic/fan53555.c	optional fan53555 fdt | tcs4525 fdt
diff --git a/sys/dev/iicbus/sensor/w83793g.c b/sys/dev/iicbus/sensor/w83793g.c
new file mode 100644
index 000000000000..772384aa57a0
--- /dev/null
+++ b/sys/dev/iicbus/sensor/w83793g.c
@@ -0,0 +1,366 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Justin Hibbits
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+/*
+ * Driver for the Winbond W83793G hardware monitor.
+ *
+ * The hardware monitor supports the following sensors:
+ * - 6 temperature sensors
+ *   - 4 with 1/4 integer precision
+ *   - 2 with integer precision
+ * - 11 voltage sensors
+ * - 12 fan sensors
+ *   - FanIn 6-12 are on multifunction pins, so may not be enabled.
+ *   8 DC/PWM fan outputs for fan speed control
+ * - Case open detection
+ */
+
+#define	WB_TD_BASE	0x1c
+#define	WB_TLOW		0x22
+
+#define	WB_VCORE_A	0x10
+#define	WB_VCORE_B	0x11
+#define	WB_VTT		0x12
+#define	WB_VSEN1	0x14
+#define	WB_VSEN2	0x15
+#define	WB_VSEN3	0x16
+#define	WB_VSEN4	0x17
+#define	WB_5VDD		0x18
+#define	WB_5VSB		0x19
+#define	WB_VBAT		0x1a
+#define	WB_VLOW		0x1b
+#define	WB_FAN_BASE	0x23
+
+#define	INT_STS1	0x41
+#define	INT_STS2	0x42
+#define	INT_STS3	0x43
+#define	INT_STS4	0x44
+#define	  CHASSIS	  0x40
+#define	INT_STS5	0x45
+#define	INT_MASK1	0x46
+#define	INT_MASK2	0x47
+#define	INT_MASK3	0x48
+#define	INT_MASK4	0x49
+#define	  CLR_CHS	  0x80
+#define	INT_MASK5	0x4a
+
+#define	WB_MFC		0x58	/* Multi-function pin control */
+#define	  MFC_VIDBSEL	  0x80
+#define	  MFC_SIB_SEL	  0x40
+#define	  MFC_SID_SEL_M	  0x30
+#define	  MFC_SID_VID	  0x00
+#define	  MFC_SID_FANIN	  0x20
+#define	  MFC_SIC_SEL_M	  0x0c
+#define	  MFC_SIC_VID	  0x00
+#define	  MFC_SIC_FANIN	  0x08
+#define	  MFC_SIA_SEL	  0x02
+#define	  MFC_FAN8SEL	  0x01
+#define	WB_FANIN_CTRL	0x5c
+#define	  FANIN_EN_12	  0x40
+#define	  FANIN_EN_11	  0x20
+#define	  FANIN_EN_10	  0x10
+#define	  FANIN_EN_9	  0x08
+#define	  FANIN_EN_8	  0x04
+#define	  FANIN_EN_7	  0x02
+#define	  FANIN_EN_6	  0x01
+#define	WB_FANIN_SEL	0x5d
+#define	WB_TD_MD	0x5e	/* TD mode select register */
+#define	  TD_MD_M(n)	  (0x3 << ((n) * 2))
+#define	  TD_MD_S(n)	  ((n) * 2)
+#define	  TD_STOP_M	  0x0
+#define	  TD_INT_MD	  0x1
+#define	  TD_EXT_MD	  0x2
+#define	WB_TR_MD	0x5f
+#define	  TR2_MD	  0x2
+#define	  TR1_MD	  0x1
+
+#define	WB_TEMP_COUNT		6	/* Total temperature sensors */
+#define	WB_TD_COUNT		4	/* Temp sensors with "low" part */
+#define	WB_TR_COUNT		2
+#define	WB_FAN_COUNT		12
+#define	WB_FAN_ALWAYS_ON	5	/* First 5 are not controlled */
+#define	WB_V_COUNT		11
+
+static const struct wb_vsens {
+	const char	*name;
+	int		reg;
+	int		scale;	/* Scale in millivolts */
+	int		add;	/* Scale in millivolts */
+	int		left_low;	/* left bit in VLOW, if applicable */
+} voltages[] = {
+	{ "v_core_a", WB_VCORE_A, 2, 0, 1 },
+	{ "v_core_b", WB_VCORE_B, 2, 0, 3 },
+	{ "v_tt", WB_VTT, 2, 0, 5 },
+	{ "v_sen_1", WB_VSEN1, 16 },
+	{ "v_sen_2", WB_VSEN2, 16 },
+	{ "v_sen_3", WB_VSEN3, 16 },
+	{ "v_sen_4", WB_VSEN4, 8 },
+	{ "5v", WB_5VDD, 24, 150 },
+	{ "5v_sb", WB_5VSB, 24, 150 },
+	{ "v_bat", WB_VBAT, 16 }
+};
+
+struct w83793g_softc {
+	device_t	sc_dev;
+
+};
+
+static device_probe_t	w83793g_probe;
+static device_attach_t	w83793g_attach;
+static device_detach_t	w83793g_detach;
+static int w83793g_temp_sysctl(SYSCTL_HANDLER_ARGS);
+static int w83793g_fan_sysctl(SYSCTL_HANDLER_ARGS);
+static int w83793g_voltage_sysctl(SYSCTL_HANDLER_ARGS);
+static int w83793g_case_sysctl(SYSCTL_HANDLER_ARGS);
+
+static device_method_t w83793g_methods[] = {
+	DEVMETHOD(device_probe,		w83793g_probe),
+	DEVMETHOD(device_attach,	w83793g_attach),
+	DEVMETHOD(device_detach,	w83793g_detach),
+
+	DEVMETHOD_END
+};
+
+static struct ofw_compat_data compat[] = {
+	{ "winbond,w83793", 1 },
+	{ NULL, 0 }
+};
+
+DEFINE_CLASS_0(w83793g, w83793g_driver, w83793g_methods,
+    sizeof(struct w83793g_softc));
+DRIVER_MODULE(w83793g, iicbus, w83793g_driver, NULL, NULL);
+MODULE_VERSION(w83793g, 1);
+MODULE_DEPEND(w83793g, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+IICBUS_FDT_PNP_INFO(compat);
+
+static int
+w83793g_readreg(device_t dev, int reg, uint8_t *output)
+{
+	return (iicdev_readfrom(dev, reg, output, sizeof(*output), IIC_WAIT));
+}
+
+static int
+w83793g_writereg(device_t dev, int reg, uint8_t *output)
+{
+	return (iicdev_writeto(dev, reg, output, sizeof(*output), IIC_WAIT));
+}
+
+static bool
+temp_enabled(struct w83793g_softc *sc, int sensor)
+{
+	uint8_t reg;
+	int error;
+
+	if (sensor < WB_TD_COUNT) {
+		error = w83793g_readreg(sc->sc_dev, WB_TD_MD, &reg);
+		if (error != 0)
+			return (false);
+		return ((reg & TD_MD_M(sensor)) != 0);
+	} else {
+		error = w83793g_readreg(sc->sc_dev, WB_TR_MD, &reg);
+		sensor -= WB_TD_COUNT;
+		if (error != 0)
+			return (false);
+		return ((reg & (1 << sensor)) != 0);
+	}
+}
+
+static bool
+fan_enabled(struct w83793g_softc *sc, int fan)
+{
+	int error;
+	uint8_t fanin_ctl;
+
+	if (fan < WB_FAN_ALWAYS_ON)
+		return (true);
+
+	error = w83793g_readreg(sc->sc_dev, WB_FANIN_CTRL, &fanin_ctl);
+	if (error != 0)
+		return (false);
+
+	fan -= WB_FAN_ALWAYS_ON;
+
+	return ((fanin_ctl & (1 << fan)) != 0);
+}
+
+static int
+w83793g_probe(device_t dev)
+{
+	if (ofw_bus_search_compatible(dev, compat)->ocd_data == 0)
+		return (ENXIO);
+
+	device_set_desc(dev, "Winbond W83793 Hardware Monitor");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+w83793g_attach(device_t dev)
+{
+	struct w83793g_softc *sc = device_get_softc(dev);
+	struct sysctl_oid *root = device_get_sysctl_tree(dev);
+	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
+	struct sysctl_oid *node;
+	int i;
+
+	sc->sc_dev = dev;
+	node = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(root), OID_AUTO, "voltages",
+	    CTLFLAG_RD, NULL, NULL);
+	for (i = 0; i < nitems(voltages); i++) {
+		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO,
+		    voltages[i].name, CTLTYPE_INT | CTLFLAG_RD, sc,
+		    i, w83793g_voltage_sysctl, "I",
+		    "voltage (millivolts)");
+	}
+	node = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(root), OID_AUTO, "temp",
+	    CTLFLAG_RD, NULL, NULL);
+	for (i = 0; i < WB_TEMP_COUNT; i++) {
+		/* Only supports single-digit sensors. */
+		char name[sizeof("sensor_") + 1];
+
+		if (!temp_enabled(sc, i))
+			continue;
+		snprintf(name, sizeof(name), "sensor_%d", i);
+		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, name,
+		    CTLTYPE_INT | CTLFLAG_RD, sc, WB_TD_BASE + i,
+		    w83793g_temp_sysctl, "IK2", NULL);
+	}
+	node = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(root), OID_AUTO, "fans",
+	    CTLFLAG_RD, NULL, NULL);
+	for (i = 0; i < WB_FAN_COUNT; i++) {
+		/* Supports up to 12 fans */
+		char name[sizeof("fan_") + 2];
+
+		if (!fan_enabled(sc, i))
+			continue;
+		snprintf(name, sizeof(name), "fan_%d", i);
+		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, name,
+		    CTLTYPE_INT | CTLFLAG_RD, sc, WB_FAN_BASE + i,
+		    w83793g_fan_sysctl, "I", NULL);
+	}
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(root), OID_AUTO, "chassis_open",
+	    CTLTYPE_U8 | CTLFLAG_RD, sc, 0, w83793g_case_sysctl, "CU",
+	    "report if the chassis_open was latched");
+	return (0);
+}
+
+static int
+w83793g_detach(device_t dev)
+{
+	return (ENXIO);
+}
+
+static int
+w83793g_temp_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct w83793g_softc *sc = arg1;
+	int reg = arg2;
+	int temp;
+	int error;
+	int8_t t_reg;
+	uint8_t t_low;
+
+	error = w83793g_readreg(sc->sc_dev, reg, &t_reg);
+	if (error != 0)
+		return (error);
+
+	if (reg < WB_TD_BASE + WB_TD_COUNT) {
+		error = w83793g_readreg(sc->sc_dev, WB_TLOW, &t_low);
+		if (error != 0)
+			return (error);
+	} else
+		t_low = 0;
+
+	temp = (int)t_reg * 100;
+	temp += (t_low >> (2 * (reg - WB_TD_BASE)) & 0x3) * 25;
+	temp += 27315;	/* Convert celsius to kelvin */
+
+	error = sysctl_handle_int(oidp, &temp, 0, req);
+
+	return (error);
+}
+
+static int
+w83793g_fan_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct w83793g_softc *sc = arg1;
+	int reg = arg2;
+	int count;
+	int error;
+	uint8_t reg_vals[2];	/* Fan count is 2 bytes */
+
+	error = iicdev_readfrom(sc->sc_dev, reg, reg_vals, sizeof(reg_vals),
+	    IIC_WAIT);
+	if (error != 0)
+		return (error);
+
+	count = ((int)reg_vals[0] << 8) | reg_vals[1];
+	error = sysctl_handle_int(oidp, &count, 0, req);
+
+	return (error);
+}
+
+static int
+w83793g_voltage_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct w83793g_softc *sc = arg1;
+	const struct wb_vsens *sensor;
+	int index = arg2;
+	int volts;
+	int error;
+	uint8_t v_reg;
+	uint8_t v_low;
+
+	sensor = &voltages[index];
+	error = w83793g_readreg(sc->sc_dev, sensor->reg, &v_reg);
+	if (error != 0)
+		return (error);
+
+	volts = v_reg;
+	if (sensor->left_low != 0) {
+		volts <<= 2;
+		error = w83793g_readreg(sc->sc_dev, WB_VLOW, &v_low);
+		if (error != 0)
+			return (error);
+		volts |= (v_low >> (sensor->left_low - 1) & 0x3);
+	}
+
+	volts *= sensor->scale;
+	volts += sensor->add;
+
+	error = sysctl_handle_int(oidp, &volts, 0, req);
+
+	return (error);
+}
+
+static int
+w83793g_case_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct w83793g_softc *sc = arg1;
+	int error;
+	uint8_t reg;
+	bool chassis;
+
+	error = w83793g_readreg(sc->sc_dev, INT_STS4, &reg);
+	if (error != 0)
+		return (error);
+
+	chassis = ((reg & CHASSIS) != 0);
+
+	return (sysctl_handle_bool(oidp, &chassis, 0, req));
+}
diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile
index ff4536694dfc..64b22db3319d 100644
--- a/sys/modules/i2c/Makefile
+++ b/sys/modules/i2c/Makefile
@@ -32,7 +32,8 @@ SUBDIR += hym8563 \
 	  rv3032 \
 	  rx8803 \
 	  tca64xx \
-	  tmp461
+	  tmp461 \
+	  w83793g
 .endif
 
 .if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
diff --git a/sys/modules/i2c/w83793g/Makefile b/sys/modules/i2c/w83793g/Makefile
new file mode 100644
index 000000000000..d4329b00d30a
--- /dev/null
+++ b/sys/modules/i2c/w83793g/Makefile
@@ -0,0 +1,14 @@
+.PATH:	${SRCTOP}/sys/dev/iicbus/sensor/
+KMOD	= w83793g
+SRCS	= w83793g.c
+
+# Generated files...
+SRCS+=	\
+	bus_if.h \
+	clock_if.h \
+	device_if.h \
+	iicbus_if.h \
+	opt_platform.h \
+	ofw_bus_if.h \
+
+.include <bsd.kmod.mk>