1 /* $OpenBSD: fins.c,v 1.2 2009/03/30 00:36:26 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2005, 2006 Mark Kettenis 5 * Copyright (c) 2007, 2008 Geoff Steckel 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/systm.h> 22 #include <sys/device.h> 23 #include <sys/kernel.h> 24 #include <sys/queue.h> 25 #include <sys/sensors.h> 26 #include <machine/bus.h> 27 28 #include <dev/isa/isareg.h> 29 #include <dev/isa/isavar.h> 30 31 /* Derived from LM78 code. Only handles chips attached to ISA bus */ 32 33 /* 34 * Fintek F71805 registers and constants 35 * http://www.fintek.com.tw/files/productfiles/F71805F_V025.pdf 36 * This chip is a multi-io chip with many functions. 37 * Each function may be relocated in I/O space by the BIOS. 38 * The base address (2E or 4E) accesses a configuration space which 39 * has pointers to the individual functions. The config space must be 40 * unlocked with a cookie and relocked afterwards. The chip ID is stored 41 * in config space so it is not normally visible. 42 * 43 * We assume that the monitor is enabled. We don't try to start or stop it. 44 * The voltage dividers specified are from reading the chips on one board. 45 * There is no way to determine what they are in the general case. 46 * This section of the chip controls the fans. We don't do anything to them. 47 */ 48 49 #define FINS_UNLOCK 0x87 /* magic constant - write 2x to select chip */ 50 #define FINS_LOCK 0xaa /* magic constant - write 1x to deselect reg */ 51 52 /* ISA registers index to an internal register space on chip */ 53 #define FINS_ADDR 0x00 54 #define FINS_DATA 0x01 55 56 #define FINS_FUNC_SEL 0x07 /* select which chip function to access */ 57 #define FINS_CHIP 0x20 /* chip ID */ 58 #define FINS_MANUF 0x23 /* manufacturer ID */ 59 #define FINS_BASEADDR 0x60 /* I/O base of chip function */ 60 61 #define FINS_71806 0x0341 /* same as F71872 */ 62 #define FINS_71805 0x0406 63 #define FINS_71882 0x0541 /* same as F71883 */ 64 #define FINS_71862 0x0601 /* same as F71863 */ 65 #define FINTEK_ID 0x1934 66 67 #define FINS_FUNC_SENSORS 0x04 68 #define FINS_FUNC_WATCHDOG 0x07 69 70 /* sensors device registers */ 71 #define FINS_SENS_TMODE(sc) ((sc)->fins_chipid <= FINS_71805 ? 0x01 : 0x6b) 72 #define FINS_SENS_VDIVS 0x0e 73 74 /* watchdog device registers (mapped straight to i/o port offsets) */ 75 #define FINS_WDOG_CR0 0x00 76 #define FINS_WDOG_CR1 0x05 77 #define FINS_WDOG_TIMER 0x06 78 79 /* CR0 flags */ 80 #define FINS_WDOG_OUTEN 0x80 81 82 /* CR1 flags */ 83 #define FINS_WDOG_EN 0x20 84 #define FINS_WDOG_MINS 0x08 85 86 #define FINS_MAX_SENSORS 18 87 /* 88 * Fintek chips typically measure voltages using 8mv steps. 89 * To measure higher voltages the input is attenuated with (external) 90 * resistors. Negative voltages are measured using inverting op amps 91 * and resistors. So we have to convert the sensor values back to 92 * real voltages by applying the appropriate resistor factor. 93 */ 94 #define FRFACT_NONE 8000 95 #define FRFACT(x, y) (FRFACT_NONE * ((x) + (y)) / (y)) 96 #define FNRFACT(x, y) (-FRFACT_NONE * (x) / (y)) 97 98 struct fins_softc; 99 100 struct fins_sensor { 101 char *fs_desc; 102 void (*fs_refresh)(struct fins_softc *, int); 103 enum sensor_type fs_type; 104 int fs_aux; 105 u_int8_t fs_reg; 106 }; 107 108 struct fins_softc { 109 struct device sc_dev; 110 111 struct ksensor fins_ksensors[FINS_MAX_SENSORS]; 112 struct ksensordev fins_sensordev; 113 struct sensor_task *fins_sensortask; 114 struct fins_sensor *fins_sensors; 115 116 bus_space_handle_t sc_ioh_sens; 117 bus_space_handle_t sc_ioh_wdog; 118 bus_space_tag_t sc_iot; 119 120 u_int16_t fins_chipid; 121 u_int8_t fins_tempsel; 122 u_int8_t fins_wdog_cr; 123 }; 124 125 int fins_match(struct device *, void *, void *); 126 void fins_attach(struct device *, struct device *, void *); 127 128 void fins_unlock(bus_space_tag_t, bus_space_handle_t); 129 void fins_lock(bus_space_tag_t, bus_space_handle_t); 130 131 u_int8_t fins_read(bus_space_tag_t, bus_space_handle_t, int); 132 u_int16_t fins_read_2(bus_space_tag_t, bus_space_handle_t, int); 133 void fins_write(bus_space_tag_t, bus_space_handle_t, int, u_int8_t); 134 135 static __inline u_int8_t fins_read_sens(struct fins_softc *, int); 136 static __inline u_int16_t fins_read_sens_2(struct fins_softc *, int); 137 138 static __inline u_int8_t fins_read_wdog(struct fins_softc *, int); 139 static __inline void fins_write_wdog(struct fins_softc *, int, u_int8_t); 140 141 void fins_setup_sensors(struct fins_softc *, struct fins_sensor *); 142 void fins_refresh(void *); 143 144 void fins_get_rpm(struct fins_softc *, int); 145 void fins_get_temp(struct fins_softc *, int); 146 void fins_get_volt(struct fins_softc *, int); 147 148 int fins_wdog_cb(void *, int); 149 150 struct cfattach fins_ca = { 151 sizeof(struct fins_softc), 152 fins_match, 153 fins_attach 154 }; 155 156 struct cfdriver fins_cd = { 157 NULL, "fins", DV_DULL 158 }; 159 160 struct fins_sensor fins_71805_sensors[] = { 161 { "+3.3V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x10 }, 162 { "Vtt", fins_get_volt, SENSOR_VOLTS_DC, FRFACT_NONE, 0x11 }, 163 { "Vram", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x12 }, 164 { "Vchips", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(47, 100), 0x13 }, 165 { "+5V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 47), 0x14 }, 166 { "+12V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 20), 0x15 }, 167 { "+1.5V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT_NONE, 0x16 }, 168 { "Vcore", fins_get_volt, SENSOR_VOLTS_DC, FRFACT_NONE, 0x17 }, 169 { "Vsb", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 47), 0x18 }, 170 { "Vsbint", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 47), 0x19 }, 171 { "Vbat", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 47), 0x1a }, 172 173 { NULL, fins_get_temp, SENSOR_TEMP, 0x01, 0x1b }, 174 { NULL, fins_get_temp, SENSOR_TEMP, 0x02, 0x1c }, 175 { NULL, fins_get_temp, SENSOR_TEMP, 0x04, 0x1d }, 176 177 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0x20 }, 178 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0x22 }, 179 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0x24 }, 180 181 { NULL } 182 }; 183 184 struct fins_sensor fins_71882_sensors[] = { 185 { "+3.3V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x20 }, 186 { "Vcore", fins_get_volt, SENSOR_VOLTS_DC, FRFACT_NONE, 0x21 }, 187 { "Vram", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x22 }, 188 { "Vchips", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(47, 100), 0x23 }, 189 { "+5V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 47), 0x24 }, 190 { "+12V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(200, 20), 0x25 }, 191 { "+1.5V", fins_get_volt, SENSOR_VOLTS_DC, FRFACT_NONE, 0x26 }, 192 { "Vsb", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x27 }, 193 { "Vbat", fins_get_volt, SENSOR_VOLTS_DC, FRFACT(100, 100), 0x28 }, 194 195 { NULL, fins_get_temp, SENSOR_TEMP, 0x02, 0x72 }, 196 { NULL, fins_get_temp, SENSOR_TEMP, 0x04, 0x74 }, 197 { NULL, fins_get_temp, SENSOR_TEMP, 0x08, 0x76 }, 198 199 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0xa0 }, 200 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0xb0 }, 201 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0xc0 }, 202 { NULL, fins_get_rpm, SENSOR_FANRPM, 0, 0xd0 }, 203 204 { NULL } 205 }; 206 207 int 208 fins_match(struct device *parent, void *match, void *aux) 209 { 210 struct isa_attach_args *ia = aux; 211 bus_space_handle_t ioh; 212 bus_space_tag_t iot; 213 int ret = 0; 214 u_int16_t id; 215 216 iot = ia->ia_iot; 217 if (bus_space_map(iot, ia->ipa_io[0].base, 2, 0, &ioh)) 218 return (0); 219 220 /* Fintek uses magic cookie locks to distinguish their chips */ 221 fins_unlock(iot, ioh); 222 223 fins_write(iot, ioh, FINS_FUNC_SEL, 0); /* IDs appear only in space 0 */ 224 if (fins_read_2(iot, ioh, FINS_MANUF) != FINTEK_ID) 225 goto match_done; 226 id = fins_read_2(iot, ioh, FINS_CHIP); 227 switch(id) { 228 case FINS_71882: 229 case FINS_71862: 230 ia->ipa_nio = 3; 231 fins_write(iot, ioh, FINS_FUNC_SEL, FINS_FUNC_WATCHDOG); 232 ia->ipa_io[2].base = fins_read_2(iot, ioh, FINS_BASEADDR); 233 ia->ipa_io[2].length = 8; 234 fins_write(iot, ioh, FINS_FUNC_SEL, FINS_FUNC_SENSORS); 235 ia->ipa_io[1].base = fins_read_2(iot, ioh, FINS_BASEADDR); 236 ia->ipa_io[1].base += 5; 237 break; 238 case FINS_71806: 239 case FINS_71805: 240 ia->ipa_nio = 2; 241 fins_write(iot, ioh, FINS_FUNC_SEL, FINS_FUNC_SENSORS); 242 ia->ipa_io[1].base = fins_read_2(iot, ioh, FINS_BASEADDR); 243 break; 244 default: 245 goto match_done; 246 } 247 ia->ipa_io[0].length = ia->ipa_io[1].length = 2; 248 ia->ipa_nmem = ia->ipa_nirq = ia->ipa_ndrq = 0; 249 ia->ia_aux = (void *)(u_long)id; 250 ret = 1; 251 match_done: 252 fins_lock(iot, ioh); 253 return (ret); 254 } 255 256 void 257 fins_attach(struct device *parent, struct device *self, void *aux) 258 { 259 struct fins_softc *sc = (struct fins_softc *)self; 260 struct isa_attach_args *ia = aux; 261 bus_addr_t iobase; 262 u_int32_t iosize; 263 u_int i; 264 265 sc->sc_iot = ia->ia_iot; 266 sc->fins_chipid = (u_int16_t)(u_long)ia->ia_aux; 267 iobase = ia->ipa_io[1].base; 268 iosize = ia->ipa_io[1].length; 269 if (bus_space_map(sc->sc_iot, iobase, iosize, 0, &sc->sc_ioh_sens)) { 270 printf(": can't map sensor i/o space\n"); 271 return; 272 } 273 switch(sc->fins_chipid) { 274 case FINS_71882: 275 case FINS_71862: 276 fins_setup_sensors(sc, fins_71882_sensors); 277 break; 278 case FINS_71806: 279 case FINS_71805: 280 fins_setup_sensors(sc, fins_71805_sensors); 281 break; 282 } 283 sc->fins_sensortask = sensor_task_register(sc, fins_refresh, 5); 284 if (sc->fins_sensortask == NULL) { 285 printf(": can't register update task\n"); 286 return; 287 } 288 for (i = 0; sc->fins_sensors[i].fs_refresh != NULL; ++i) 289 sensor_attach(&sc->fins_sensordev, &sc->fins_ksensors[i]); 290 sensordev_install(&sc->fins_sensordev); 291 292 if (sc->fins_chipid <= FINS_71805) 293 goto attach_done; 294 iobase = ia->ipa_io[2].base; 295 iosize = ia->ipa_io[2].length; 296 if (bus_space_map(sc->sc_iot, iobase, iosize, 0, &sc->sc_ioh_wdog)) { 297 printf(": can't map watchdog i/o space\n"); 298 return; 299 } 300 sc->fins_wdog_cr = fins_read_wdog(sc, FINS_WDOG_CR1); 301 sc->fins_wdog_cr &= ~(FINS_WDOG_MINS | FINS_WDOG_EN); 302 fins_write_wdog(sc, FINS_WDOG_CR1, sc->fins_wdog_cr); 303 wdog_register(sc, fins_wdog_cb); 304 attach_done: 305 printf("\n"); 306 } 307 308 u_int8_t 309 fins_read(bus_space_tag_t iot, bus_space_handle_t ioh, int reg) 310 { 311 bus_space_write_1(iot, ioh, FINS_ADDR, reg); 312 return (bus_space_read_1(iot, ioh, FINS_DATA)); 313 } 314 315 u_int16_t 316 fins_read_2(bus_space_tag_t iot, bus_space_handle_t ioh, int reg) 317 { 318 u_int16_t val; 319 320 bus_space_write_1(iot, ioh, FINS_ADDR, reg); 321 val = bus_space_read_1(iot, ioh, FINS_DATA) << 8; 322 bus_space_write_1(iot, ioh, FINS_ADDR, reg + 1); 323 return (val | bus_space_read_1(iot, ioh, FINS_DATA)); 324 } 325 326 void 327 fins_write(bus_space_tag_t iot, bus_space_handle_t ioh, int reg, u_int8_t val) 328 { 329 bus_space_write_1(iot, ioh, FINS_ADDR, reg); 330 bus_space_write_1(iot, ioh, FINS_DATA, val); 331 } 332 333 static __inline u_int8_t 334 fins_read_sens(struct fins_softc *sc, int reg) 335 { 336 return (fins_read(sc->sc_iot, sc->sc_ioh_sens, reg)); 337 } 338 339 static __inline u_int16_t 340 fins_read_sens_2(struct fins_softc *sc, int reg) 341 { 342 return (fins_read_2(sc->sc_iot, sc->sc_ioh_sens, reg)); 343 } 344 345 static __inline u_int8_t 346 fins_read_wdog(struct fins_softc *sc, int reg) 347 { 348 return (bus_space_read_1(sc->sc_iot, sc->sc_ioh_wdog, reg)); 349 } 350 351 static __inline void 352 fins_write_wdog(struct fins_softc *sc, int reg, u_int8_t val) 353 { 354 bus_space_write_1(sc->sc_iot, sc->sc_ioh_wdog, reg, val); 355 } 356 357 void 358 fins_unlock(bus_space_tag_t iot, bus_space_handle_t ioh) 359 { 360 bus_space_write_1(iot, ioh, 0, FINS_UNLOCK); 361 bus_space_write_1(iot, ioh, 0, FINS_UNLOCK); 362 } 363 364 void 365 fins_lock(bus_space_tag_t iot, bus_space_handle_t ioh) 366 { 367 bus_space_write_1(iot, ioh, 0, FINS_LOCK); 368 bus_space_unmap(iot, ioh, 2); 369 } 370 371 void 372 fins_setup_sensors(struct fins_softc *sc, struct fins_sensor *sensors) 373 { 374 int i; 375 376 for (i = 0; sensors[i].fs_refresh != NULL; ++i) { 377 sc->fins_ksensors[i].type = sensors[i].fs_type; 378 if (sensors[i].fs_desc != NULL) 379 strlcpy(sc->fins_ksensors[i].desc, sensors[i].fs_desc, 380 sizeof(sc->fins_ksensors[i].desc)); 381 } 382 strlcpy(sc->fins_sensordev.xname, sc->sc_dev.dv_xname, 383 sizeof(sc->fins_sensordev.xname)); 384 sc->fins_sensors = sensors; 385 sc->fins_tempsel = fins_read_sens(sc, FINS_SENS_TMODE(sc)); 386 } 387 388 #if 0 389 void 390 fins_get_dividers(struct fins_softc *sc) 391 { 392 int i, p, m; 393 u_int16_t r = fins_read_sens_2(sc, FINS_SENS_VDIVS); 394 395 for (i = 0; i < 6; ++i) { 396 p = (i < 4) ? i : i + 2; 397 m = (r & (0x03 << p)) >> p; 398 if (m == 3) 399 m = 4; 400 fins_71882_sensors[i + 1].fs_aux = FRFACT_NONE << m; 401 } 402 } 403 #endif 404 405 void 406 fins_refresh(void *arg) 407 { 408 struct fins_softc *sc = arg; 409 int i; 410 411 for (i = 0; sc->fins_sensors[i].fs_refresh != NULL; ++i) 412 sc->fins_sensors[i].fs_refresh(sc, i); 413 } 414 415 void 416 fins_get_volt(struct fins_softc *sc, int n) 417 { 418 struct ksensor *sensor = &sc->fins_ksensors[n]; 419 struct fins_sensor *fs = &sc->fins_sensors[n]; 420 int data; 421 422 data = fins_read_sens(sc, fs->fs_reg); 423 if (data == 0xff || data == 0) { 424 sensor->flags |= SENSOR_FINVALID; 425 sensor->value = 0; 426 } else { 427 sensor->flags &= ~SENSOR_FINVALID; 428 sensor->value = data * fs->fs_aux; 429 } 430 } 431 432 /* The BIOS seems to add a fudge factor to the CPU temp of +5C */ 433 void 434 fins_get_temp(struct fins_softc *sc, int n) 435 { 436 struct ksensor *sensor = &sc->fins_ksensors[n]; 437 struct fins_sensor *fs = &sc->fins_sensors[n]; 438 u_int data; 439 u_int max; 440 441 /* 442 * The data sheet says that the range of the temperature 443 * sensor is between 0 and 127 or 140 degrees C depending on 444 * what kind of sensor is used. 445 * A disconnected sensor seems to read over 110 or so. 446 */ 447 data = fins_read_sens(sc, fs->fs_reg); 448 max = (sc->fins_tempsel & fs->fs_aux) ? 111 : 128; 449 if (data == 0 || data >= max) { /* disconnected? */ 450 sensor->flags |= SENSOR_FINVALID; 451 sensor->value = 0; 452 } else { 453 sensor->flags &= ~SENSOR_FINVALID; 454 sensor->value = data * 1000000 + 273150000; 455 } 456 } 457 458 /* The chip holds a fudge factor for BJT sensors */ 459 /* this is currently unused but might be reenabled */ 460 #if 0 461 void 462 fins_refresh_offset(struct fins_softc *sc, int n) 463 { 464 struct ksensor *sensor = &sc->fins_ksensors[n]; 465 struct fins_sensor *fs = &sc->fins_sensors[n]; 466 u_int data; 467 468 sensor->flags &= ~SENSOR_FINVALID; 469 data = fins_read_sens(sc, fs->fs_reg); 470 data |= ~0 * (data & 0x40); /* sign extend 7-bit value */ 471 sensor->value = data * 1000000 + 273150000; 472 } 473 #endif 474 475 /* fan speed appears to be a 12-bit number */ 476 void 477 fins_get_rpm(struct fins_softc *sc, int n) 478 { 479 struct ksensor *sensor = &sc->fins_ksensors[n]; 480 struct fins_sensor *fs = &sc->fins_sensors[n]; 481 int data; 482 483 data = fins_read_sens_2(sc, fs->fs_reg); 484 if (data >= 0xfff) { 485 sensor->value = 0; 486 sensor->flags |= SENSOR_FINVALID; 487 } else { 488 sensor->value = 1500000 / data; 489 sensor->flags &= ~SENSOR_FINVALID; 490 } 491 } 492 493 int 494 fins_wdog_cb(void *arg, int period) 495 { 496 struct fins_softc *sc = arg; 497 u_int8_t cr0, cr1, t; 498 499 cr0 = fins_read_wdog(sc, FINS_WDOG_CR0) & ~FINS_WDOG_OUTEN; 500 fins_write_wdog(sc, FINS_WDOG_CR0, cr0); 501 502 cr1 = sc->fins_wdog_cr; 503 if (period > 0xff) { 504 cr1 |= FINS_WDOG_MINS; 505 t = (period + 59) / 60; 506 period = (int)t * 60; 507 } else if (period > 0) 508 t = period; 509 else 510 return (0); 511 512 fins_write_wdog(sc, FINS_WDOG_TIMER, t); 513 fins_write_wdog(sc, FINS_WDOG_CR0, cr0 | FINS_WDOG_OUTEN); 514 fins_write_wdog(sc, FINS_WDOG_CR1, cr1 | FINS_WDOG_EN); 515 return (period); 516 } 517