1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2016-2022 Stormshield 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/param.h> 29 #include <sys/systm.h> 30 #include <sys/bus.h> 31 #include <sys/kernel.h> 32 #include <sys/module.h> 33 #include <sys/rman.h> 34 #include <sys/sysctl.h> 35 #include <sys/watchdog.h> 36 37 #include <dev/superio/superio.h> 38 39 #include <machine/bus.h> 40 #include <machine/resource.h> 41 42 #define NCTHWM_FAN_MAX 5 43 44 #define NCTHWM_BANK_SELECT 0x4e 45 #define NCTHWM_VENDOR_ID 0x4f 46 47 #define NCTHWM_VERBOSE_PRINTF(dev, ...) \ 48 do { \ 49 if (__predict_false(bootverbose)) \ 50 device_printf(dev, __VA_ARGS__); \ 51 } while (0) 52 53 struct ncthwm_softc { 54 device_t dev; 55 struct ncthwm_device *nctdevp; 56 struct resource *iores; 57 int iorid; 58 }; 59 60 struct ncthwm_fan_info 61 { 62 const char *name; 63 uint8_t low_byte_offset; 64 uint8_t high_byte_offset; 65 }; 66 67 struct ncthwm_device { 68 uint16_t devid; 69 const char *descr; 70 uint8_t base_offset; 71 uint8_t fan_bank; 72 uint8_t fan_count; 73 struct ncthwm_fan_info fan_info[NCTHWM_FAN_MAX]; 74 } ncthwm_devices[] = { 75 { 76 .devid = 0xc562, 77 .descr = "HWM on Nuvoton NCT6779D", 78 .base_offset = 5, 79 .fan_bank = 4, 80 .fan_count = 5, 81 .fan_info = { 82 { .name = "SYSFAN", .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 }, 83 { .name = "CPUFAN", .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 }, 84 { .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 }, 85 { .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 }, 86 { .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 }, 87 }, 88 }, { 89 .devid = 0xd42a, 90 .descr = "HWM on Nuvoton NCT6796D-E", 91 .base_offset = 5, 92 .fan_bank = 4, 93 .fan_count = 5, 94 .fan_info = { 95 { .name = "SYSFAN", .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 }, 96 { .name = "CPUFAN", .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 }, 97 { .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 }, 98 { .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 }, 99 { .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 }, 100 }, 101 } 102 }; 103 104 static struct ncthwm_device * 105 ncthwm_lookup_device(device_t dev) 106 { 107 int i; 108 uint16_t devid; 109 110 devid = superio_devid(dev); 111 for (i = 0; i < nitems(ncthwm_devices); i++) { 112 if (devid == ncthwm_devices[i].devid) 113 return (ncthwm_devices + i); 114 } 115 return (NULL); 116 } 117 118 static void 119 ncthwm_write(struct ncthwm_softc *sc, uint8_t reg, uint8_t val) 120 { 121 bus_write_1(sc->iores, 0, reg); 122 bus_write_1(sc->iores, 1, val); 123 } 124 125 static uint8_t 126 ncthwm_read(struct ncthwm_softc *sc, uint8_t reg) 127 { 128 bus_write_1(sc->iores, 0, reg); 129 return (bus_read_1(sc->iores, 1)); 130 } 131 132 static int 133 ncthwm_query_fan_speed(SYSCTL_HANDLER_ARGS) 134 { 135 struct ncthwm_softc *sc; 136 struct ncthwm_fan_info *fan; 137 uint16_t val; 138 139 sc = arg1; 140 if (sc == NULL) 141 return (EINVAL); 142 143 KASSERT(sc->nctdevp != NULL, ("Unreachable")); 144 145 if (sc->nctdevp->fan_count <= arg2) 146 return (EINVAL); 147 fan = &sc->nctdevp->fan_info[arg2]; 148 149 KASSERT(sc->iores != NULL, ("Unreachable")); 150 151 ncthwm_write(sc, NCTHWM_BANK_SELECT, sc->nctdevp->fan_bank); 152 val = ncthwm_read(sc, fan->high_byte_offset) << 8; 153 val |= ncthwm_read(sc, fan->low_byte_offset); 154 155 NCTHWM_VERBOSE_PRINTF(sc->dev, "%s: read %u from bank %u offset 0x%x-0x%x\n", 156 fan->name, val, sc->nctdevp->fan_bank, fan->high_byte_offset, fan->low_byte_offset); 157 158 return (sysctl_handle_16(oidp, &val, 0, req)); 159 } 160 161 static int 162 ncthwm_probe(device_t dev) 163 { 164 struct ncthwm_device *nctdevp; 165 uint8_t ldn; 166 167 ldn = superio_get_ldn(dev); 168 169 if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON) { 170 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a Nuvoton device\n", ldn); 171 return (ENXIO); 172 } 173 if (superio_get_type(dev) != SUPERIO_DEV_HWM) { 174 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a HWM device\n", ldn); 175 return (ENXIO); 176 } 177 178 nctdevp = ncthwm_lookup_device(dev); 179 if (nctdevp == NULL) { 180 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not supported\n", ldn); 181 return (ENXIO); 182 } 183 device_set_desc(dev, nctdevp->descr); 184 return (BUS_PROBE_DEFAULT); 185 } 186 187 static int 188 ncthwm_attach(device_t dev) 189 { 190 struct ncthwm_softc *sc; 191 int i; 192 uint16_t iobase; 193 194 sc = device_get_softc(dev); 195 sc->dev = dev; 196 197 sc->nctdevp = ncthwm_lookup_device(dev); 198 if (sc->nctdevp == NULL) { 199 device_printf(dev, "device not supported\n"); 200 return (ENXIO); 201 } 202 203 iobase = superio_get_iobase(dev) + sc->nctdevp->base_offset; 204 sc->iorid = 0; 205 if (bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid, iobase, 2) != 0) { 206 device_printf(dev, "failed to set I/O port resource at 0x%x\n", iobase); 207 return (ENXIO); 208 } 209 sc->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, 210 &sc->iorid, RF_ACTIVE); 211 if (sc->iores == NULL) { 212 device_printf(dev, "can't map I/O space at 0x%x\n", iobase); 213 return (ENXIO); 214 } 215 NCTHWM_VERBOSE_PRINTF(dev, "iobase 0x%x iores %p\n", iobase, sc->iores); 216 217 /* Register FAN sysctl */ 218 for (i = 0; i < sc->nctdevp->fan_count; i++) { 219 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), 220 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, 221 sc->nctdevp->fan_info[i].name, 222 CTLTYPE_U16 | CTLFLAG_RD, sc, i, 223 ncthwm_query_fan_speed, "SU", "Fan speed in RPM"); 224 } 225 226 return (0); 227 } 228 229 static int 230 ncthwm_detach(device_t dev) 231 { 232 struct ncthwm_softc *sc = device_get_softc(dev); 233 234 if (sc->iores) 235 bus_release_resource(dev, SYS_RES_IOPORT, sc->iorid, sc->iores); 236 237 return (0); 238 } 239 240 static device_method_t ncthwm_methods[] = { 241 /* Methods from the device interface */ 242 DEVMETHOD(device_probe, ncthwm_probe), 243 DEVMETHOD(device_attach, ncthwm_attach), 244 DEVMETHOD(device_detach, ncthwm_detach), 245 246 /* Terminate method list */ 247 { 0, 0 } 248 }; 249 250 static driver_t ncthwm_driver = { 251 "ncthwm", 252 ncthwm_methods, 253 sizeof (struct ncthwm_softc) 254 }; 255 256 DRIVER_MODULE(ncthwm, superio, ncthwm_driver, NULL, NULL); 257 MODULE_DEPEND(ncthwm, superio, 1, 1, 1); 258 MODULE_VERSION(ncthwm, 1); 259