1 /* $NetBSD: viaenv.c,v 1.34 2018/03/04 13:24:17 jdolecek Exp $ */ 2 3 /* 4 * Copyright (c) 2000 Johan Danielsson 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * 3. Neither the name of author nor the names of any contributors may 19 * be used to endorse or promote products derived from this 20 * software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 26 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35 /* 36 * Driver for the hardware monitoring and power management timer 37 * in the VIA VT82C686A and VT8231 South Bridges. 38 */ 39 40 #include <sys/cdefs.h> 41 __KERNEL_RCSID(0, "$NetBSD: viaenv.c,v 1.34 2018/03/04 13:24:17 jdolecek Exp $"); 42 43 #include <sys/param.h> 44 #include <sys/systm.h> 45 #include <sys/kernel.h> 46 #include <sys/device.h> 47 48 #include <sys/bus.h> 49 #include <dev/ic/acpipmtimer.h> 50 51 #include <dev/pci/pcivar.h> 52 #include <dev/pci/pcireg.h> 53 #include <dev/pci/pcidevs.h> 54 55 #include <dev/sysmon/sysmonvar.h> 56 57 #ifdef VIAENV_DEBUG 58 unsigned int viaenv_debug = 0; 59 #define DPRINTF(X) do { if (viaenv_debug) printf X ; } while(0) 60 #else 61 #define DPRINTF(X) 62 #endif 63 64 #define VIANUMSENSORS 10 /* three temp, two fan, five voltage */ 65 66 struct viaenv_softc { 67 bus_space_tag_t sc_iot; 68 bus_space_handle_t sc_ioh; 69 bus_space_handle_t sc_pm_ioh; 70 71 int sc_fan_div[2]; /* fan RPM divisor */ 72 73 struct sysmon_envsys *sc_sme; 74 envsys_data_t sc_sensor[VIANUMSENSORS]; 75 76 struct timeval sc_lastread; 77 }; 78 79 /* autoconf(9) glue */ 80 static int viaenv_match(device_t, cfdata_t, void *); 81 static void viaenv_attach(device_t, device_t, void *); 82 83 CFATTACH_DECL_NEW(viaenv, sizeof(struct viaenv_softc), 84 viaenv_match, viaenv_attach, NULL, NULL); 85 86 /* envsys(4) glue */ 87 static void viaenv_refresh(struct sysmon_envsys *, envsys_data_t *); 88 89 static int val_to_uK(unsigned int); 90 static int val_to_rpm(unsigned int, int); 91 static long val_to_uV(unsigned int, int); 92 93 static int 94 viaenv_match(device_t parent, cfdata_t match, void *aux) 95 { 96 struct pci_attach_args *pa = aux; 97 98 if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_VIATECH) 99 return 0; 100 101 switch (PCI_PRODUCT(pa->pa_id)) { 102 case PCI_PRODUCT_VIATECH_VT82C686A_PWR: 103 case PCI_PRODUCT_VIATECH_VT8231_PWR: 104 return 1; 105 default: 106 return 0; 107 } 108 } 109 110 /* 111 * XXX there doesn't seem to exist much hard documentation on how to 112 * convert the raw values to usable units, this code is more or less 113 * stolen from the Linux driver, but changed to suit our conditions 114 */ 115 116 /* 117 * lookup-table to translate raw values to uK, this is the same table 118 * used by the Linux driver (modulo units); there is a fifth degree 119 * polynomial that supposedly been used to generate this table, but I 120 * haven't been able to figure out how -- it doesn't give the same values 121 */ 122 123 static const long val_to_temp[] = { 124 20225, 20435, 20645, 20855, 21045, 21245, 21425, 21615, 21785, 21955, 125 22125, 22285, 22445, 22605, 22755, 22895, 23035, 23175, 23315, 23445, 126 23565, 23695, 23815, 23925, 24045, 24155, 24265, 24365, 24465, 24565, 127 24665, 24765, 24855, 24945, 25025, 25115, 25195, 25275, 25355, 25435, 128 25515, 25585, 25655, 25725, 25795, 25865, 25925, 25995, 26055, 26115, 129 26175, 26235, 26295, 26355, 26405, 26465, 26515, 26575, 26625, 26675, 130 26725, 26775, 26825, 26875, 26925, 26975, 27025, 27065, 27115, 27165, 131 27205, 27255, 27295, 27345, 27385, 27435, 27475, 27515, 27565, 27605, 132 27645, 27685, 27735, 27775, 27815, 27855, 27905, 27945, 27985, 28025, 133 28065, 28105, 28155, 28195, 28235, 28275, 28315, 28355, 28405, 28445, 134 28485, 28525, 28565, 28615, 28655, 28695, 28735, 28775, 28825, 28865, 135 28905, 28945, 28995, 29035, 29075, 29125, 29165, 29205, 29245, 29295, 136 29335, 29375, 29425, 29465, 29505, 29555, 29595, 29635, 29685, 29725, 137 29765, 29815, 29855, 29905, 29945, 29985, 30035, 30075, 30125, 30165, 138 30215, 30255, 30305, 30345, 30385, 30435, 30475, 30525, 30565, 30615, 139 30655, 30705, 30755, 30795, 30845, 30885, 30935, 30975, 31025, 31075, 140 31115, 31165, 31215, 31265, 31305, 31355, 31405, 31455, 31505, 31545, 141 31595, 31645, 31695, 31745, 31805, 31855, 31905, 31955, 32005, 32065, 142 32115, 32175, 32225, 32285, 32335, 32395, 32455, 32515, 32575, 32635, 143 32695, 32755, 32825, 32885, 32955, 33025, 33095, 33155, 33235, 33305, 144 33375, 33455, 33525, 33605, 33685, 33765, 33855, 33935, 34025, 34115, 145 34205, 34295, 34395, 34495, 34595, 34695, 34805, 34905, 35015, 35135, 146 35245, 35365, 35495, 35615, 35745, 35875, 36015, 36145, 36295, 36435, 147 36585, 36745, 36895, 37065, 37225, 37395, 37575, 37755, 37935, 38125, 148 38325, 38525, 38725, 38935, 39155, 39375, 39605, 39835, 40075, 40325, 149 40575, 40835, 41095, 41375, 41655, 41935, 150 }; 151 152 /* use above table to convert values to temperatures in micro-Kelvins */ 153 static int 154 val_to_uK(unsigned int val) 155 { 156 int i = val / 4; 157 int j = val % 4; 158 159 assert(i >= 0 && i <= 255); 160 161 if (j == 0 || i == 255) 162 return val_to_temp[i] * 10000; 163 164 /* is linear interpolation ok? */ 165 return (val_to_temp[i] * (4 - j) + 166 val_to_temp[i + 1] * j) * 2500 /* really: / 4 * 10000 */ ; 167 } 168 169 static int 170 val_to_rpm(unsigned int val, int div) 171 { 172 173 if (val == 0) 174 return 0; 175 176 return 1350000 / val / div; 177 } 178 179 static long 180 val_to_uV(unsigned int val, int index) 181 { 182 static const long mult[] = 183 {1250000, 1250000, 1670000, 2600000, 6300000}; 184 185 assert(index >= 0 && index <= 4); 186 187 return (25LL * val + 133) * mult[index] / 2628; 188 } 189 190 #define VIAENV_TSENS3 0x1f 191 #define VIAENV_TSENS1 0x20 192 #define VIAENV_TSENS2 0x21 193 #define VIAENV_VSENS1 0x22 194 #define VIAENV_VSENS2 0x23 195 #define VIAENV_VCORE 0x24 196 #define VIAENV_VSENS3 0x25 197 #define VIAENV_VSENS4 0x26 198 #define VIAENV_FAN1 0x29 199 #define VIAENV_FAN2 0x2a 200 #define VIAENV_FANCONF 0x47 /* fan configuration */ 201 #define VIAENV_TLOW 0x49 /* temperature low order value */ 202 #define VIAENV_TIRQ 0x4b /* temperature interrupt configuration */ 203 204 #define VIAENV_GENCFG 0x40 /* general configuration */ 205 #define VIAENV_GENCFG_TMR32 (1 << 11) /* 32-bit PM timer */ 206 #define VIAENV_GENCFG_PMEN (1 << 15) /* enable PM I/O space */ 207 #define VIAENV_PMBASE 0x48 /* power management I/O space base */ 208 #define VIAENV_PMSIZE 128 /* HWM and power management I/O space size */ 209 #define VIAENV_PM_TMR 0x08 /* PM timer */ 210 #define VIAENV_HWMON_CONF 0x70 /* HWMon I/O base */ 211 #define VIAENV_HWMON_CTL 0x74 /* HWMon control register */ 212 213 static void 214 viaenv_refresh_sensor_data(struct viaenv_softc *sc, envsys_data_t *edata) 215 { 216 static const struct timeval onepointfive = { 1, 500000 }; 217 static int old_sensor = -1; 218 struct timeval t, utv; 219 uint8_t v, v2; 220 int i; 221 222 /* Read new values at most once every 1.5 seconds. */ 223 timeradd(&sc->sc_lastread, &onepointfive, &t); 224 getmicrouptime(&utv); 225 i = timercmp(&utv, &t, >); 226 if (i) 227 sc->sc_lastread = utv; 228 229 if (i == 0 && old_sensor == edata->sensor) 230 return; 231 232 old_sensor = edata->sensor; 233 234 /* temperature */ 235 if (edata->sensor == 0) { 236 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TIRQ); 237 v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS1); 238 DPRINTF(("TSENS1 = %d\n", (v2 << 2) | (v >> 6))); 239 edata->value_cur = val_to_uK((v2 << 2) | (v >> 6)); 240 edata->state = ENVSYS_SVALID; 241 } else if (edata->sensor == 1) { 242 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW); 243 v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS2); 244 DPRINTF(("TSENS2 = %d\n", (v2 << 2) | ((v >> 4) & 0x3))); 245 edata->value_cur = val_to_uK((v2 << 2) | ((v >> 4) & 0x3)); 246 edata->state = ENVSYS_SVALID; 247 } else if (edata->sensor == 2) { 248 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW); 249 v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS3); 250 DPRINTF(("TSENS3 = %d\n", (v2 << 2) | (v >> 6))); 251 edata->value_cur = val_to_uK((v2 << 2) | (v >> 6)); 252 edata->state = ENVSYS_SVALID; 253 } else if (edata->sensor > 2 && edata->sensor < 5) { 254 /* fans */ 255 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_FANCONF); 256 257 sc->sc_fan_div[0] = 1 << ((v >> 4) & 0x3); 258 sc->sc_fan_div[1] = 1 << ((v >> 6) & 0x3); 259 260 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, 261 VIAENV_FAN1 + edata->sensor - 3); 262 DPRINTF(("FAN%d = %d / %d\n", edata->sensor - 3, v, 263 sc->sc_fan_div[edata->sensor - 3])); 264 edata->value_cur = val_to_rpm(v, 265 sc->sc_fan_div[edata->sensor - 3]); 266 edata->state = ENVSYS_SVALID; 267 } else { 268 v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, 269 VIAENV_VSENS1 + edata->sensor - 5); 270 DPRINTF(("V%d = %d\n", edata->sensor - 5, v)); 271 edata->value_cur = val_to_uV(v, edata->sensor - 5); 272 edata->state = ENVSYS_SVALID; 273 } 274 } 275 276 static void 277 viaenv_attach(device_t parent, device_t self, void *aux) 278 { 279 struct viaenv_softc *sc = device_private(self); 280 struct pci_attach_args *pa = aux; 281 pcireg_t iobase, control; 282 int i; 283 284 aprint_naive("\n"); 285 aprint_normal(": VIA Technologies "); 286 switch (PCI_PRODUCT(pa->pa_id)) { 287 case PCI_PRODUCT_VIATECH_VT82C686A_PWR: 288 aprint_normal("VT82C686A Hardware Monitor\n"); 289 break; 290 case PCI_PRODUCT_VIATECH_VT8231_PWR: 291 aprint_normal("VT8231 Hardware Monitor\n"); 292 break; 293 default: 294 aprint_normal("Unknown Hardware Monitor\n"); 295 break; 296 } 297 298 sc->sc_iot = pa->pa_iot; 299 300 iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CONF); 301 DPRINTF(("%s: iobase 0x%x\n", device_xname(self), iobase)); 302 control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CTL); 303 304 /* Check if the Hardware Monitor enable bit is set */ 305 if ((control & 1) == 0) { 306 aprint_normal_dev(self, "Hardware Monitor disabled\n"); 307 goto nohwm; 308 } 309 310 /* Map Hardware Monitor I/O space */ 311 if (bus_space_map(sc->sc_iot, iobase & 0xff80, 312 VIAENV_PMSIZE, 0, &sc->sc_ioh)) { 313 aprint_error_dev(self, "failed to map I/O space\n"); 314 goto nohwm; 315 } 316 317 for (i = 0; i < 3; i++) 318 sc->sc_sensor[i].units = ENVSYS_STEMP; 319 320 #define COPYDESCR(x, y) \ 321 do { \ 322 strlcpy((x), (y), sizeof(x)); \ 323 } while (0) 324 325 COPYDESCR(sc->sc_sensor[0].desc, "TSENS1"); 326 COPYDESCR(sc->sc_sensor[1].desc, "TSENS2"); 327 COPYDESCR(sc->sc_sensor[2].desc, "TSENS3"); 328 329 for (i = 3; i < 5; i++) 330 sc->sc_sensor[i].units = ENVSYS_SFANRPM; 331 332 COPYDESCR(sc->sc_sensor[3].desc, "FAN1"); 333 COPYDESCR(sc->sc_sensor[4].desc, "FAN2"); 334 335 for (i = 5; i < 10; i++) 336 sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC; 337 338 COPYDESCR(sc->sc_sensor[5].desc, "VSENS1"); /* CPU core (2V) */ 339 COPYDESCR(sc->sc_sensor[6].desc, "VSENS2"); /* NB core? (2.5V) */ 340 COPYDESCR(sc->sc_sensor[7].desc, "Vcore"); /* Vcore (3.3V) */ 341 COPYDESCR(sc->sc_sensor[8].desc, "VSENS3"); /* VSENS3 (5V) */ 342 COPYDESCR(sc->sc_sensor[9].desc, "VSENS4"); /* VSENS4 (12V) */ 343 344 #undef COPYDESCR 345 346 for (i = 0; i < 10; i++) { 347 sc->sc_sensor[i].state = ENVSYS_SINVALID; 348 sc->sc_sensor[i].flags |= ENVSYS_FHAS_ENTROPY; 349 } 350 351 sc->sc_sme = sysmon_envsys_create(); 352 353 /* Initialize sensors */ 354 for (i = 0; i < VIANUMSENSORS; i++) { 355 if (sysmon_envsys_sensor_attach(sc->sc_sme, 356 &sc->sc_sensor[i])) { 357 sysmon_envsys_destroy(sc->sc_sme); 358 return; 359 } 360 } 361 362 /* 363 * Hook into the System Monitor. 364 */ 365 sc->sc_sme->sme_name = device_xname(self); 366 sc->sc_sme->sme_cookie = sc; 367 sc->sc_sme->sme_refresh = viaenv_refresh; 368 369 if (sysmon_envsys_register(sc->sc_sme)) { 370 aprint_error_dev(self, "unable to register with sysmon\n"); 371 sysmon_envsys_destroy(sc->sc_sme); 372 return; 373 } 374 375 nohwm: 376 /* Check if power management I/O space is enabled */ 377 control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_GENCFG); 378 if ((control & VIAENV_GENCFG_PMEN) == 0) { 379 aprint_normal_dev(self, 380 "Power Managament controller disabled\n"); 381 goto nopm; 382 } 383 384 /* Map power management I/O space */ 385 iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_PMBASE); 386 if (bus_space_map(sc->sc_iot, PCI_MAPREG_IO_ADDR(iobase), 387 VIAENV_PMSIZE, 0, &sc->sc_pm_ioh)) { 388 aprint_error_dev(self, "failed to map PM I/O space\n"); 389 goto nopm; 390 } 391 392 /* Attach our PM timer with the generic acpipmtimer function */ 393 acpipmtimer_attach(self, sc->sc_iot, sc->sc_pm_ioh, 394 VIAENV_PM_TMR, 395 ((control & VIAENV_GENCFG_TMR32) ? ACPIPMT_32BIT : 0)); 396 397 nopm: 398 return; 399 } 400 401 static void 402 viaenv_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 403 { 404 struct viaenv_softc *sc = sme->sme_cookie; 405 406 viaenv_refresh_sensor_data(sc, edata); 407 } 408