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