/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2026 Justin Hibbits */ #include #include #include #include #include #include #include #include #include /* * 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, ®); if (error != 0) return (false); return ((reg & TD_MD_M(sensor)) != 0); } else { error = w83793g_readreg(sc->sc_dev, WB_TR_MD, ®); 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, ®); if (error != 0) return (error); chassis = ((reg & CHASSIS) != 0); return (sysctl_handle_bool(oidp, &chassis, 0, req)); }