1/* $OpenBSD: kb3310.c,v 1.16 2010/10/14 21:23:04 pirofti Exp $ */ 2/* 3 * Copyright (c) 2010 Otto Moerbeek <otto@drijf.net> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/param.h> 19#include <sys/kernel.h> 20#include <sys/systm.h> 21#include <sys/device.h> 22#include <sys/sensors.h> 23#include <sys/timeout.h> 24 25#include <mips64/archtype.h> 26#include <machine/apmvar.h> 27#include <evbmips/loongson/autoconf.h> 28#include <machine/bus.h> 29#include <dev/isa/isavar.h> 30 31#include <dev/pci/glxreg.h> 32 33#include <loongson/dev/bonitoreg.h> 34#include <loongson/dev/kb3310var.h> 35 36#include "apm.h" 37#include "pckbd.h" 38#include "hidkbd.h" 39 40#if NPCKBD > 0 || NHIDKBD > 0 41#include <dev/ic/pckbcvar.h> 42#include <dev/pckbc/pckbdvar.h> 43#include <dev/usb/hidkbdvar.h> 44#endif 45 46struct cfdriver ykbec_cd = { 47 NULL, "ykbec", DV_DULL, 48}; 49 50#ifdef KB3310_DEBUG 51#define DPRINTF(x) printf x 52#else 53#define DPRINTF(x) 54#endif 55 56#define IO_YKBEC 0x381 57#define IO_YKBECSIZE 0x3 58 59static const struct { 60 const char *desc; 61 int type; 62} ykbec_table[] = { 63#define YKBEC_FAN 0 64 { NULL, SENSOR_FANRPM }, 65#define YKBEC_ITEMP 1 66 { "Internal temperature", SENSOR_TEMP }, 67#define YKBEC_FCAP 2 68 { "Battery full charge capacity", SENSOR_AMPHOUR }, 69#define YKBEC_BCURRENT 3 70 { "Battery current", SENSOR_AMPS }, 71#define YKBEC_BVOLT 4 72 { "Battery voltage", SENSOR_VOLTS_DC }, 73#define YKBEC_BTEMP 5 74 { "Battery temperature", SENSOR_TEMP }, 75#define YKBEC_CAP 6 76 { "Battery capacity", SENSOR_PERCENT }, 77#define YKBEC_CHARGING 7 78 { "Battery charging", SENSOR_INDICATOR }, 79#define YKBEC_AC 8 80 { "AC-Power", SENSOR_INDICATOR } 81#define YKBEC_NSENSORS 9 82}; 83 84struct ykbec_softc { 85 struct device sc_dev; 86 bus_space_tag_t sc_iot; 87 bus_space_handle_t sc_ioh; 88 struct ksensor sc_sensor[YKBEC_NSENSORS]; 89 struct ksensordev sc_sensordev; 90#if NPCKBD > 0 || NHIDKBD > 0 91 struct timeout sc_bell_tmo; 92#endif 93}; 94 95static struct ykbec_softc *ykbec_sc; 96static int ykbec_chip_config; 97 98extern void loongson_set_isa_imr(uint); 99 100int ykbec_match(struct device *, void *, void *); 101void ykbec_attach(struct device *, struct device *, void *); 102 103const struct cfattach ykbec_ca = { 104 sizeof(struct ykbec_softc), ykbec_match, ykbec_attach 105}; 106 107int ykbec_apminfo(struct apm_power_info *); 108void ykbec_bell(void *, u_int, u_int, u_int, int); 109void ykbec_bell_stop(void *); 110void ykbec_print_bat_info(struct ykbec_softc *); 111u_int ykbec_read(struct ykbec_softc *, u_int); 112u_int ykbec_read16(struct ykbec_softc *, u_int); 113void ykbec_refresh(void *arg); 114void ykbec_write(struct ykbec_softc *, u_int, u_int); 115 116#if NAPM > 0 117struct apm_power_info ykbec_apmdata; 118const char *ykbec_batstate[] = { 119 "high", 120 "low", 121 "critical", 122 "charging", 123 "unknown" 124}; 125#define BATTERY_STRING(x) ((x) < nitems(ykbec_batstate) ? \ 126 ykbec_batstate[x] : ykbec_batstate[4]) 127#endif 128 129int 130ykbec_match(struct device *parent, void *match, void *aux) 131{ 132 struct isa_attach_args *ia = aux; 133 bus_space_handle_t ioh; 134 135 if (sys_platform->system_type != LOONGSON_YEELOONG) 136 return (0); 137 138 if ((ia->ia_iobase != IOBASEUNK && ia->ia_iobase != IO_YKBEC) || 139 /* (ia->ia_iosize != 0 && ia->ia_iosize != IO_YKBECSIZE) || XXX isa.c */ 140 ia->ia_maddr != MADDRUNK || ia->ia_msize != 0 || 141 ia->ia_irq != IRQUNK || ia->ia_drq != DRQUNK) 142 return (0); 143 144 if (bus_space_map(ia->ia_iot, IO_YKBEC, IO_YKBECSIZE, 0, &ioh)) 145 return (0); 146 147 bus_space_unmap(ia->ia_iot, ioh, IO_YKBECSIZE); 148 149 ia->ia_iobase = IO_YKBEC; 150 ia->ia_iosize = IO_YKBECSIZE; 151 152 return (1); 153} 154 155void 156ykbec_attach(struct device *parent, struct device *self, void *aux) 157{ 158 struct isa_attach_args *ia = aux; 159 struct ykbec_softc *sc = (struct ykbec_softc *)self; 160 int i; 161 162 sc->sc_iot = ia->ia_iot; 163 if (bus_space_map(sc->sc_iot, ia->ia_iobase, ia->ia_iosize, 0, 164 &sc->sc_ioh)) { 165 aprint_error(": couldn't map I/O space"); 166 return; 167 } 168 169 /* Initialize sensor data. */ 170 strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 171 sizeof(sc->sc_sensordev.xname)); 172 if (sensor_task_register(sc, ykbec_refresh, 5) == NULL) { 173 aprint_error(", unable to register update task\n"); 174 return; 175 } 176 177#ifdef DEBUG 178 ykbec_print_bat_info(sc); 179#endif 180 aprint_normal("\n"); 181 182 for (i = 0; i < YKBEC_NSENSORS; i++) { 183 sc->sc_sensor[i].type = ykbec_table[i].type; 184 if (ykbec_table[i].desc) 185 strlcpy(sc->sc_sensor[i].desc, ykbec_table[i].desc, 186 sizeof(sc->sc_sensor[i].desc)); 187 sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]); 188 } 189 190 sensordev_install(&sc->sc_sensordev); 191 192#if NAPM > 0 193 /* make sure we have the apm state initialized before apm attaches */ 194 ykbec_refresh(sc); 195 apm_setinfohook(ykbec_apminfo); 196#endif 197#if NPCKBD > 0 || NHIDKBD > 0 198 timeout_set(&sc->sc_bell_tmo, ykbec_bell_stop, sc); 199#if NPCKBD > 0 200 pckbd_hookup_bell(ykbec_bell, sc); 201#endif 202#if NHIDKBD > 0 203 hidkbd_hookup_bell(ykbec_bell, sc); 204#endif 205#endif 206 ykbec_sc = sc; 207} 208 209void 210ykbec_write(struct ykbec_softc *mcsc, u_int reg, u_int datum) 211{ 212 struct ykbec_softc *sc = (struct ykbec_softc *)mcsc; 213 bus_space_tag_t iot = sc->sc_iot; 214 bus_space_handle_t ioh = sc->sc_ioh; 215 216 bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff); 217 bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff); 218 bus_space_write_1(iot, ioh, 2, datum); 219} 220 221u_int 222ykbec_read(struct ykbec_softc *mcsc, u_int reg) 223{ 224 struct ykbec_softc *sc = (struct ykbec_softc *)mcsc; 225 bus_space_tag_t iot = sc->sc_iot; 226 bus_space_handle_t ioh = sc->sc_ioh; 227 228 bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff); 229 bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff); 230 return bus_space_read_1(iot, ioh, 2); 231} 232 233u_int 234ykbec_read16(struct ykbec_softc *mcsc, u_int reg) 235{ 236 u_int val; 237 238 val = ykbec_read(mcsc, reg); 239 return (val << 8) | ykbec_read(mcsc, reg + 1); 240} 241 242#define KB3310_FAN_SPEED_DIVIDER 480000 243 244#define ECTEMP_CURRENT_REG 0xf458 245#define REG_FAN_SPEED_HIGH 0xfe22 246#define REG_FAN_SPEED_LOW 0xfe23 247 248#define REG_DESIGN_CAP_HIGH 0xf77d 249#define REG_DESIGN_CAP_LOW 0xf77e 250#define REG_FULLCHG_CAP_HIGH 0xf780 251#define REG_FULLCHG_CAP_LOW 0xf781 252 253#define REG_DESIGN_VOL_HIGH 0xf782 254#define REG_DESIGN_VOL_LOW 0xf783 255#define REG_CURRENT_HIGH 0xf784 256#define REG_CURRENT_LOW 0xf785 257#define REG_VOLTAGE_HIGH 0xf786 258#define REG_VOLTAGE_LOW 0xf787 259#define REG_TEMPERATURE_HIGH 0xf788 260#define REG_TEMPERATURE_LOW 0xf789 261#define REG_RELATIVE_CAT_HIGH 0xf492 262#define REG_RELATIVE_CAT_LOW 0xf493 263#define REG_BAT_VENDOR 0xf4c4 264#define REG_BAT_CELL_COUNT 0xf4c6 265 266#define REG_BAT_CHARGE 0xf4a2 267#define BAT_CHARGE_AC 0x00 268#define BAT_CHARGE_DISCHARGE 0x01 269#define BAT_CHARGE_CHARGE 0x02 270 271#define REG_POWER_FLAG 0xf440 272#define POWER_FLAG_ADAPTER_IN (1<<0) 273#define POWER_FLAG_POWER_ON (1<<1) 274#define POWER_FLAG_ENTER_SUS (1<<2) 275 276#define REG_BAT_STATUS 0xf4b0 277#define BAT_STATUS_BAT_EXISTS (1<<0) 278#define BAT_STATUS_BAT_FULL (1<<1) 279#define BAT_STATUS_BAT_DESTROY (1<<2) 280#define BAT_STATUS_BAT_LOW (1<<5) 281 282#define REG_CHARGE_STATUS 0xf4b1 283#define CHARGE_STATUS_PRECHARGE (1<<1) 284#define CHARGE_STATUS_OVERHEAT (1<<2) 285 286#define REG_BAT_STATE 0xf482 287#define BAT_STATE_DISCHARGING (1<<0) 288#define BAT_STATE_CHARGING (1<<1) 289 290#define REG_BEEP_CONTROL 0xf4d0 291#define BEEP_ENABLE (1<<0) 292 293#define REG_PMUCFG 0xff0c 294#define PMUCFG_STOP_MODE (1<<7) 295#define PMUCFG_IDLE_MODE (1<<6) 296#define PMUCFG_LPC_WAKEUP (1<<5) 297#define PMUCFG_RESET_8051 (1<<4) 298#define PMUCFG_SCI_WAKEUP (1<<3) 299#define PMUCFG_WDT_WAKEUP (1<<2) 300#define PMUCFG_GPWU_WAKEUP (1<<1) 301#define PMUCFG_IRQ_IDLE (1<<0) 302 303#define REG_USB0 0xf461 304#define REG_USB1 0xf462 305#define REG_USB2 0xf463 306#define USB_FLAG_ON 1 307#define USB_FLAG_OFF 0 308 309#define REG_FAN_CONTROL 0xf4d2 310#define REG_FAN_ON 1 311#define REG_FAN_OFF 0 312 313#define YKBEC_SCI_IRQ 0xa 314 315#ifdef DEBUG 316void 317ykbec_print_bat_info(struct ykbec_softc *sc) 318{ 319 uint bat_status, count, dvolt, dcap; 320 321 printf(": battery "); 322 bat_status = ykbec_read(sc, REG_BAT_STATUS); 323 if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) { 324 printf("absent"); 325 return; 326 } 327 328 count = ykbec_read(sc, REG_BAT_CELL_COUNT); 329 dvolt = ykbec_read16(sc, REG_DESIGN_VOL_HIGH); 330 dcap = ykbec_read16(sc, REG_DESIGN_CAP_HIGH); 331 printf("%d cells, design capacity %dmV %dmAh", count, dvolt, dcap); 332} 333#endif 334 335void 336ykbec_refresh(void *arg) 337{ 338 struct ykbec_softc *sc = (struct ykbec_softc *)arg; 339 u_int val, bat_charge, bat_status, charge_status, bat_state, power_flag; 340 u_int cap_pct, fullcap; 341 int current; 342#if NAPM > 0 343 struct apm_power_info old; 344#endif 345 346 val = ykbec_read16(sc, REG_FAN_SPEED_HIGH) & 0xfffff; 347 if (val != 0) { 348 val = KB3310_FAN_SPEED_DIVIDER / val; 349 sc->sc_sensor[YKBEC_FAN].value = val; 350 CLR(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID); 351 } else 352 SET(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID); 353 354 val = ykbec_read(sc, ECTEMP_CURRENT_REG); 355 sc->sc_sensor[YKBEC_ITEMP].value = val * 1000000 + 273150000; 356 357 fullcap = ykbec_read16(sc, REG_FULLCHG_CAP_HIGH); 358 sc->sc_sensor[YKBEC_FCAP].value = fullcap * 1000; 359 360 current = ykbec_read16(sc, REG_CURRENT_HIGH); 361 /* sign extend short -> int, int -> int64 will be done next statement */ 362 current |= -(current & 0x8000); 363 sc->sc_sensor[YKBEC_BCURRENT].value = -1000 * current; 364 365 sc->sc_sensor[YKBEC_BVOLT].value = ykbec_read16(sc, REG_VOLTAGE_HIGH) * 366 1000; 367 368 val = ykbec_read16(sc, REG_TEMPERATURE_HIGH); 369 sc->sc_sensor[YKBEC_BTEMP].value = val * 1000000 + 273150000; 370 371 cap_pct = ykbec_read16(sc, REG_RELATIVE_CAT_HIGH); 372 sc->sc_sensor[YKBEC_CAP].value = cap_pct * 1000; 373 374 bat_charge = ykbec_read(sc, REG_BAT_CHARGE); 375 bat_status = ykbec_read(sc, REG_BAT_STATUS); 376 charge_status = ykbec_read(sc, REG_CHARGE_STATUS); 377 bat_state = ykbec_read(sc, REG_BAT_STATE); 378 power_flag = ykbec_read(sc, REG_POWER_FLAG); 379 380 sc->sc_sensor[YKBEC_CHARGING].value = !!ISSET(bat_state, 381 BAT_STATE_CHARGING); 382 sc->sc_sensor[YKBEC_AC].value = !!ISSET(power_flag, 383 POWER_FLAG_ADAPTER_IN); 384 385 sc->sc_sensor[YKBEC_CAP].status = ISSET(bat_status, BAT_STATUS_BAT_LOW) ? 386 SENSOR_S_CRIT : SENSOR_S_OK; 387 388#if NAPM > 0 389 bcopy(&ykbec_apmdata, &old, sizeof(old)); 390 ykbec_apmdata.battery_life = cap_pct; 391 ykbec_apmdata.ac_state = ISSET(power_flag, POWER_FLAG_ADAPTER_IN) ? 392 APM_AC_ON : APM_AC_OFF; 393 if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) { 394 ykbec_apmdata.battery_state = APM_BATTERY_ABSENT; 395 ykbec_apmdata.minutes_left = 0; 396 ykbec_apmdata.battery_life = 0; 397 } else { 398 if (ISSET(bat_state, BAT_STATE_CHARGING)) 399 ykbec_apmdata.battery_state = APM_BATT_CHARGING; 400 else if (ISSET(bat_status, BAT_STATUS_BAT_LOW)) 401 ykbec_apmdata.battery_state = APM_BATT_CRITICAL; 402 /* XXX arbitrary */ 403 else if (cap_pct > 60) 404 ykbec_apmdata.battery_state = APM_BATT_HIGH; 405 else 406 ykbec_apmdata.battery_state = APM_BATT_LOW; 407 408 /* if charging, current is positive */ 409 if (ISSET(bat_state, BAT_STATE_CHARGING)) 410 current = 0; 411 else 412 current = -current; 413 /* XXX Yeeloong draw is about 1A */ 414 if (current <= 0) 415 current = 1000; 416 /* XXX at 5?%, the Yeeloong shuts down */ 417 if (cap_pct <= 5) 418 cap_pct = 0; 419 else 420 cap_pct -= 5; 421 fullcap = cap_pct * 60 * fullcap / 100; 422 ykbec_apmdata.minutes_left = fullcap / current; 423 424 } 425 if (old.ac_state != ykbec_apmdata.ac_state) 426 apm_record_event(APM_POWER_CHANGE, "AC power", 427 ykbec_apmdata.ac_state ? "restored" : "lost"); 428 if (old.battery_state != ykbec_apmdata.battery_state) 429 apm_record_event(APM_POWER_CHANGE, "battery", 430 BATTERY_STRING(ykbec_apmdata.battery_state)); 431#endif 432} 433 434 435#if NAPM > 0 436int 437ykbec_apminfo(struct apm_power_info *info) 438{ 439 bcopy(&ykbec_apmdata, info, sizeof(struct apm_power_info)); 440 return 0; 441} 442 443int 444ykbec_suspend() 445{ 446 struct ykbec_softc *sc = ykbec_sc; 447 int ctrl; 448 449 /* 450 * Set up wakeup sources: currently only the internal keyboard. 451 */ 452 loongson_set_isa_imr(1 << 1); 453 454 /* USB */ 455 DPRINTF(("USB\n")); 456 ykbec_write(sc, REG_USB0, USB_FLAG_OFF); 457 ykbec_write(sc, REG_USB1, USB_FLAG_OFF); 458 ykbec_write(sc, REG_USB2, USB_FLAG_OFF); 459 460 /* EC */ 461 DPRINTF(("REG_PMUCFG\n")); 462 ctrl = PMUCFG_SCI_WAKEUP | PMUCFG_WDT_WAKEUP | PMUCFG_GPWU_WAKEUP | 463 PMUCFG_LPC_WAKEUP | PMUCFG_STOP_MODE | PMUCFG_RESET_8051; 464 ykbec_write(sc, REG_PMUCFG, ctrl); 465 466 /* FAN */ 467 DPRINTF(("FAN\n")); 468 ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_OFF); 469 470 /* CPU */ 471 DPRINTF(("CPU\n")); 472 ykbec_chip_config = REGVAL(LOONGSON_CHIP_CONFIG0); 473 enableintr(); 474 REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config & ~0x7; 475 (void)REGVAL(LOONGSON_CHIP_CONFIG0); 476 477 /* 478 * When a resume interrupt fires, we will enter the interrupt 479 * dispatcher, which will do nothing because we are at splhigh, 480 * and execution flow will return here and continue. 481 */ 482 (void)disableintr(); 483 484 return 0; 485} 486 487int 488ykbec_resume() 489{ 490 struct ykbec_softc *sc = ykbec_sc; 491 492 /* CPU */ 493 DPRINTF(("CPU\n")); 494 REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config; 495 (void)REGVAL(LOONGSON_CHIP_CONFIG0); 496 497 /* FAN */ 498 DPRINTF(("FAN\n")); 499 ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_ON); 500 501 /* USB */ 502 DPRINTF(("USB\n")); 503 ykbec_write(sc, REG_USB0, USB_FLAG_ON); 504 ykbec_write(sc, REG_USB1, USB_FLAG_ON); 505 ykbec_write(sc, REG_USB2, USB_FLAG_ON); 506 507 ykbec_refresh(sc); 508 509 return 0; 510} 511#endif 512 513#if NPCKBD > 0 || NHIDKBD > 0 514void 515ykbec_bell(void *arg, u_int pitch, u_int period, u_int volume, int poll) 516{ 517 struct ykbec_softc *sc = (struct ykbec_softc *)arg; 518 int bctrl; 519 int s; 520 521 s = spltty(); 522 bctrl = ykbec_read(sc, REG_BEEP_CONTROL); 523 if (volume == 0 || timeout_pending(&sc->sc_bell_tmo)) { 524 timeout_del(&sc->sc_bell_tmo); 525 /* inline ykbec_bell_stop(arg); */ 526 ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE); 527 } 528 529 if (volume != 0) { 530 ykbec_write(sc, REG_BEEP_CONTROL, bctrl | BEEP_ENABLE); 531 if (poll) { 532 delay(period * 1000); 533 ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE); 534 } else { 535 timeout_add_msec(&sc->sc_bell_tmo, period); 536 } 537 } 538 splx(s); 539} 540 541void 542ykbec_bell_stop(void *arg) 543{ 544 struct ykbec_softc *sc = (struct ykbec_softc *)arg; 545 int s; 546 547 s = spltty(); 548 ykbec_write(sc, REG_BEEP_CONTROL, 549 ykbec_read(sc, REG_BEEP_CONTROL) & ~BEEP_ENABLE); 550 splx(s); 551} 552#endif 553