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