pmu.c revision 184299
1/*- 2 * Copyright (c) 2006 Michael Lorenz 3 * Copyright 2008 by Nathan Whitehorn 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 24 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 */ 30 31#include <sys/cdefs.h> 32__FBSDID("$FreeBSD: head/sys/powerpc/powermac/pmu.c 184299 2008-10-26 19:37:38Z nwhitehorn $"); 33 34#include <sys/param.h> 35#include <sys/systm.h> 36#include <sys/module.h> 37#include <sys/bus.h> 38#include <sys/conf.h> 39#include <sys/kernel.h> 40 41#include <dev/ofw/ofw_bus.h> 42#include <dev/ofw/openfirm.h> 43 44#include <machine/bus.h> 45#include <machine/intr.h> 46#include <machine/intr_machdep.h> 47#include <machine/md_var.h> 48#include <machine/pio.h> 49#include <machine/resource.h> 50 51#include <vm/vm.h> 52#include <vm/pmap.h> 53 54#include <sys/rman.h> 55 56#include <dev/adb/adb.h> 57 58#include "pmuvar.h" 59#include "viareg.h" 60 61/* 62 * MacIO interface 63 */ 64static int pmu_probe(device_t); 65static int pmu_attach(device_t); 66static int pmu_detach(device_t); 67 68static u_int pmu_adb_send(device_t dev, u_char command_byte, int len, 69 u_char *data, u_char poll); 70static u_int pmu_adb_autopoll(device_t dev, uint16_t mask); 71static void pmu_poll(device_t dev); 72 73static device_method_t pmu_methods[] = { 74 /* Device interface */ 75 DEVMETHOD(device_probe, pmu_probe), 76 DEVMETHOD(device_attach, pmu_attach), 77 DEVMETHOD(device_detach, pmu_detach), 78 DEVMETHOD(device_shutdown, bus_generic_shutdown), 79 DEVMETHOD(device_suspend, bus_generic_suspend), 80 DEVMETHOD(device_resume, bus_generic_resume), 81 82 /* bus interface, for ADB root */ 83 DEVMETHOD(bus_print_child, bus_generic_print_child), 84 DEVMETHOD(bus_driver_added, bus_generic_driver_added), 85 86 /* ADB bus interface */ 87 DEVMETHOD(adb_hb_send_raw_packet, pmu_adb_send), 88 DEVMETHOD(adb_hb_controller_poll, pmu_poll), 89 DEVMETHOD(adb_hb_set_autopoll_mask, pmu_adb_autopoll), 90 91 { 0, 0 }, 92}; 93 94static driver_t pmu_driver = { 95 "pmu", 96 pmu_methods, 97 sizeof(struct pmu_softc), 98}; 99 100static devclass_t pmu_devclass; 101 102DRIVER_MODULE(pmu, macio, pmu_driver, pmu_devclass, 0, 0); 103DRIVER_MODULE(adb, pmu, adb_driver, adb_devclass, 0, 0); 104 105static int pmuextint_probe(device_t); 106static int pmuextint_attach(device_t); 107 108static device_method_t pmuextint_methods[] = { 109 /* Device interface */ 110 DEVMETHOD(device_probe, pmuextint_probe), 111 DEVMETHOD(device_attach, pmuextint_attach), 112 113 {0,0} 114}; 115 116static driver_t pmuextint_driver = { 117 "pmuextint", 118 pmuextint_methods, 119 0 120}; 121 122static devclass_t pmuextint_devclass; 123 124DRIVER_MODULE(pmuextint, macgpio, pmuextint_driver, pmuextint_devclass, 0, 0); 125 126/* Make sure uhid is loaded, as it turns off some of the ADB emulation */ 127MODULE_DEPEND(pmu, usb, 1, 1, 1); 128 129static void pmu_intr(void *arg); 130static void pmu_in(struct pmu_softc *sc); 131static void pmu_out(struct pmu_softc *sc); 132static void pmu_ack_on(struct pmu_softc *sc); 133static void pmu_ack_off(struct pmu_softc *sc); 134static int pmu_send(void *cookie, int cmd, int length, uint8_t *in_msg, 135 int rlen, uint8_t *out_msg); 136static uint8_t pmu_read_reg(struct pmu_softc *sc, u_int offset); 137static void pmu_write_reg(struct pmu_softc *sc, u_int offset, uint8_t value); 138static int pmu_intr_state(struct pmu_softc *); 139 140/* these values shows that number of data returned after 'send' cmd is sent */ 141static signed char pm_send_cmd_type[] = { 142 -1, -1, -1, -1, -1, -1, -1, -1, 143 -1, -1, -1, -1, -1, -1, -1, -1, 144 0x01, 0x01, -1, -1, -1, -1, -1, -1, 145 0x00, 0x00, -1, -1, -1, -1, -1, 0x00, 146 -1, 0x00, 0x02, 0x01, 0x01, -1, -1, -1, 147 0x00, -1, -1, -1, -1, -1, -1, -1, 148 0x04, 0x14, -1, 0x03, -1, -1, -1, -1, 149 0x00, 0x00, 0x02, 0x02, -1, -1, -1, -1, 150 0x01, 0x01, -1, -1, -1, -1, -1, -1, 151 0x00, 0x00, -1, -1, 0x01, -1, -1, -1, 152 0x01, 0x00, 0x02, 0x02, -1, 0x01, 0x03, 0x01, 153 0x00, 0x01, 0x00, 0x00, 0x00, -1, -1, -1, 154 0x02, -1, -1, -1, -1, -1, -1, -1, 155 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -1, -1, 156 0x01, 0x01, 0x01, -1, -1, -1, -1, -1, 157 0x00, 0x00, -1, -1, -1, -1, 0x04, 0x04, 158 0x04, -1, 0x00, -1, -1, -1, -1, -1, 159 0x00, -1, -1, -1, -1, -1, -1, -1, 160 0x01, 0x02, -1, -1, -1, -1, -1, -1, 161 0x00, 0x00, -1, -1, -1, -1, -1, -1, 162 0x02, 0x02, 0x02, 0x04, -1, 0x00, -1, -1, 163 0x01, 0x01, 0x03, 0x02, -1, -1, -1, -1, 164 -1, -1, -1, -1, -1, -1, -1, -1, 165 -1, -1, -1, -1, -1, -1, -1, -1, 166 -1, -1, -1, -1, -1, -1, -1, -1, 167 -1, -1, -1, -1, -1, -1, -1, -1, 168 0x00, -1, -1, -1, -1, -1, -1, -1, 169 0x01, 0x01, -1, -1, 0x00, 0x00, -1, -1, 170 -1, 0x04, 0x00, -1, -1, -1, -1, -1, 171 0x03, -1, 0x00, -1, 0x00, -1, -1, 0x00, 172 -1, -1, -1, -1, -1, -1, -1, -1, 173 -1, -1, -1, -1, -1, -1, -1, -1 174}; 175 176/* these values shows that number of data returned after 'receive' cmd is sent */ 177static signed char pm_receive_cmd_type[] = { 178 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 179 -1, -1, -1, -1, -1, -1, -1, -1, 180 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 181 0x02, 0x02, -1, -1, -1, -1, -1, 0x00, 182 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 183 -1, -1, -1, -1, -1, -1, -1, -1, 184 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 185 0x05, 0x15, -1, 0x02, -1, -1, -1, -1, 186 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 187 0x02, 0x02, -1, -1, -1, -1, -1, -1, 188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 189 0x02, 0x00, 0x03, 0x03, -1, -1, -1, -1, 190 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 191 0x04, 0x04, 0x03, 0x09, -1, -1, -1, -1, 192 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 193 -1, -1, -1, -1, -1, -1, 0x01, 0x01, 194 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 195 0x06, -1, -1, -1, -1, -1, -1, -1, 196 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 197 0x02, 0x02, -1, -1, -1, -1, -1, -1, 198 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 199 0x02, 0x00, 0x00, 0x00, -1, -1, -1, -1, 200 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 201 -1, -1, -1, -1, -1, -1, -1, -1, 202 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 203 -1, -1, -1, -1, -1, -1, -1, -1, 204 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 205 0x02, 0x02, -1, -1, 0x02, -1, -1, -1, 206 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 207 -1, -1, 0x02, -1, -1, -1, -1, 0x00, 208 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 209 -1, -1, -1, -1, -1, -1, -1, -1, 210}; 211 212/* We only have one of each device, so globals are safe */ 213static device_t pmu = NULL; 214static device_t pmu_extint = NULL; 215 216static int 217pmuextint_probe(device_t dev) 218{ 219 const char *type = ofw_bus_get_type(dev); 220 221 if (strcmp(type, "extint-gpio1") != 0) 222 return (ENXIO); 223 224 device_set_desc(dev, "Apple PMU99 External Interrupt"); 225 return (0); 226} 227 228static int 229pmu_probe(device_t dev) 230{ 231 const char *type = ofw_bus_get_type(dev); 232 233 if (strcmp(type, "via-pmu") != 0) 234 return (ENXIO); 235 236 device_set_desc(dev, "Apple PMU99 Controller"); 237 return (0); 238} 239 240 241static int 242setup_pmu_intr(device_t dev, device_t extint) 243{ 244 struct pmu_softc *sc; 245 sc = device_get_softc(dev); 246 247 sc->sc_irqrid = 0; 248 sc->sc_irq = bus_alloc_resource_any(extint, SYS_RES_IRQ, &sc->sc_irqrid, 249 RF_ACTIVE); 250 if (sc->sc_irq == NULL) { 251 device_printf(dev, "could not allocate interrupt\n"); 252 return (ENXIO); 253 } 254 255 if (bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_MISC | INTR_MPSAFE 256 | INTR_ENTROPY, NULL, pmu_intr, dev, &sc->sc_ih) != 0) { 257 device_printf(dev, "could not setup interrupt\n"); 258 bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irqrid, 259 sc->sc_irq); 260 return (ENXIO); 261 } 262 263 return (0); 264} 265 266static int 267pmuextint_attach(device_t dev) 268{ 269 pmu_extint = dev; 270 if (pmu) 271 return (setup_pmu_intr(pmu,dev)); 272 273 return (0); 274} 275 276static int 277pmu_attach(device_t dev) 278{ 279 struct pmu_softc *sc; 280 281 uint8_t reg; 282 uint8_t cmd[2] = {2, 0}; 283 uint8_t resp[16]; 284 phandle_t node,child; 285 286 sc = device_get_softc(dev); 287 sc->sc_dev = dev; 288 289 sc->sc_memrid = 0; 290 sc->sc_memr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 291 &sc->sc_memrid, RF_ACTIVE); 292 293 mtx_init(&sc->sc_mutex,"pmu",NULL,MTX_DEF | MTX_RECURSE); 294 295 if (sc->sc_memr == NULL) { 296 device_printf(dev, "Could not alloc mem resource!\n"); 297 return (ENXIO); 298 } 299 300 /* 301 * Our interrupt is attached to a GPIO pin. Depending on probe order, 302 * we may not have found it yet. If we haven't, it will find us, and 303 * attach our interrupt then. 304 */ 305 pmu = dev; 306 if (pmu_extint != NULL) { 307 if (setup_pmu_intr(dev,pmu_extint) != 0) 308 return (ENXIO); 309 } 310 311 sc->sc_error = 0; 312 sc->sc_polling = 0; 313 sc->sc_autopoll = 0; 314 315 /* Init PMU */ 316 317 reg = PMU_INT_TICK | PMU_INT_ADB | PMU_INT_PCEJECT | PMU_INT_SNDBRT; 318 reg |= PMU_INT_BATTERY; 319 reg |= PMU_INT_ENVIRONMENT; 320 pmu_send(sc, PMU_SET_IMASK, 1, ®, 16, resp); 321 322 pmu_write_reg(sc, vIER, 0x90); /* make sure VIA interrupts are on */ 323 324 pmu_send(sc, PMU_SYSTEM_READY, 1, cmd, 16, resp); 325 pmu_send(sc, PMU_GET_VERSION, 1, cmd, 16, resp); 326 327 /* Initialize child buses (ADB) */ 328 node = ofw_bus_get_node(dev); 329 330 for (child = OF_child(node); child != 0; child = OF_peer(child)) { 331 char name[32]; 332 333 memset(name, 0, sizeof(name)); 334 OF_getprop(child, "name", name, sizeof(name)); 335 336 if (bootverbose) 337 device_printf(dev, "PMU child <%s>\n",name); 338 339 if (strncmp(name, "adb", 4) == 0) { 340 sc->adb_bus = device_add_child(dev,"adb",-1); 341 } 342 } 343 344 return (bus_generic_attach(dev)); 345} 346 347static int 348pmu_detach(device_t dev) 349{ 350 struct pmu_softc *sc; 351 352 sc = device_get_softc(dev); 353 354 bus_teardown_intr(dev, sc->sc_irq, sc->sc_ih); 355 bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irqrid, sc->sc_irq); 356 bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_memrid, sc->sc_memr); 357 mtx_destroy(&sc->sc_mutex); 358 359 return (bus_generic_detach(dev)); 360} 361 362static uint8_t 363pmu_read_reg(struct pmu_softc *sc, u_int offset) 364{ 365 return (bus_read_1(sc->sc_memr, offset)); 366} 367 368static void 369pmu_write_reg(struct pmu_softc *sc, u_int offset, uint8_t value) 370{ 371 bus_write_1(sc->sc_memr, offset, value); 372} 373 374static int 375pmu_send_byte(struct pmu_softc *sc, uint8_t data) 376{ 377 378 pmu_out(sc); 379 pmu_write_reg(sc, vSR, data); 380 pmu_ack_off(sc); 381 /* wait for intr to come up */ 382 /* XXX should add a timeout and bail if it expires */ 383 do {} while (pmu_intr_state(sc) == 0); 384 pmu_ack_on(sc); 385 do {} while (pmu_intr_state(sc)); 386 pmu_ack_on(sc); 387 return 0; 388} 389 390static inline int 391pmu_read_byte(struct pmu_softc *sc, uint8_t *data) 392{ 393 volatile uint8_t scratch; 394 pmu_in(sc); 395 scratch = pmu_read_reg(sc, vSR); 396 pmu_ack_off(sc); 397 /* wait for intr to come up */ 398 do {} while (pmu_intr_state(sc) == 0); 399 pmu_ack_on(sc); 400 do {} while (pmu_intr_state(sc)); 401 *data = pmu_read_reg(sc, vSR); 402 return 0; 403} 404 405static int 406pmu_intr_state(struct pmu_softc *sc) 407{ 408 return ((pmu_read_reg(sc, vBufB) & vPB3) == 0); 409} 410 411static int 412pmu_send(void *cookie, int cmd, int length, uint8_t *in_msg, int rlen, 413 uint8_t *out_msg) 414{ 415 struct pmu_softc *sc = cookie; 416 int i, rcv_len = -1; 417 uint8_t out_len, intreg; 418 419 intreg = pmu_read_reg(sc, vIER); 420 intreg &= 0x10; 421 pmu_write_reg(sc, vIER, intreg); 422 423 /* wait idle */ 424 do {} while (pmu_intr_state(sc)); 425 sc->sc_error = 0; 426 427 /* send command */ 428 pmu_send_byte(sc, cmd); 429 430 /* send length if necessary */ 431 if (pm_send_cmd_type[cmd] < 0) { 432 pmu_send_byte(sc, length); 433 } 434 435 for (i = 0; i < length; i++) { 436 pmu_send_byte(sc, in_msg[i]); 437 } 438 439 /* see if there's data to read */ 440 rcv_len = pm_receive_cmd_type[cmd]; 441 if (rcv_len == 0) 442 goto done; 443 444 /* read command */ 445 if (rcv_len == 1) { 446 pmu_read_byte(sc, out_msg); 447 goto done; 448 } else 449 out_msg[0] = cmd; 450 if (rcv_len < 0) { 451 pmu_read_byte(sc, &out_len); 452 rcv_len = out_len + 1; 453 } 454 for (i = 1; i < min(rcv_len, rlen); i++) 455 pmu_read_byte(sc, &out_msg[i]); 456 457done: 458 pmu_write_reg(sc, vIER, (intreg == 0) ? 0 : 0x90); 459 460 return rcv_len; 461} 462 463 464static void 465pmu_poll(device_t dev) 466{ 467 pmu_intr(dev); 468} 469 470static void 471pmu_in(struct pmu_softc *sc) 472{ 473 uint8_t reg; 474 475 reg = pmu_read_reg(sc, vACR); 476 reg &= ~vSR_OUT; 477 reg |= 0x0c; 478 pmu_write_reg(sc, vACR, reg); 479} 480 481static void 482pmu_out(struct pmu_softc *sc) 483{ 484 uint8_t reg; 485 486 reg = pmu_read_reg(sc, vACR); 487 reg |= vSR_OUT; 488 reg |= 0x0c; 489 pmu_write_reg(sc, vACR, reg); 490} 491 492static void 493pmu_ack_off(struct pmu_softc *sc) 494{ 495 uint8_t reg; 496 497 reg = pmu_read_reg(sc, vBufB); 498 reg &= ~vPB4; 499 pmu_write_reg(sc, vBufB, reg); 500} 501 502static void 503pmu_ack_on(struct pmu_softc *sc) 504{ 505 uint8_t reg; 506 507 reg = pmu_read_reg(sc, vBufB); 508 reg |= vPB4; 509 pmu_write_reg(sc, vBufB, reg); 510} 511 512static void 513pmu_intr(void *arg) 514{ 515 device_t dev; 516 struct pmu_softc *sc; 517 518 unsigned int len; 519 uint8_t resp[16]; 520 uint8_t junk[16]; 521 522 dev = (device_t)arg; 523 sc = device_get_softc(dev); 524 525 mtx_lock(&sc->sc_mutex); 526 527 pmu_write_reg(sc, vIFR, 0x90); /* Clear 'em */ 528 len = pmu_send(sc, PMU_INT_ACK, 0, NULL, 16, resp); 529 530 mtx_unlock(&sc->sc_mutex); 531 532 if ((len < 1) || (resp[1] == 0)) { 533 return; 534 } 535 536 if (resp[1] & PMU_INT_ADB) { 537 /* 538 * the PMU will turn off autopolling after each command that 539 * it did not issue, so we assume any but TALK R0 is ours and 540 * re-enable autopoll here whenever we receive an ACK for a 541 * non TR0 command. 542 */ 543 mtx_lock(&sc->sc_mutex); 544 545 if ((resp[2] & 0x0f) != (ADB_COMMAND_TALK << 2)) { 546 if (sc->sc_autopoll) { 547 uint8_t cmd[] = {0, PMU_SET_POLL_MASK, 548 (sc->sc_autopoll >> 8) & 0xff, 549 sc->sc_autopoll & 0xff}; 550 551 pmu_send(sc, PMU_ADB_CMD, 4, cmd, 16, junk); 552 } 553 } 554 555 mtx_unlock(&sc->sc_mutex); 556 557 adb_receive_raw_packet(sc->adb_bus,resp[1],resp[2], 558 len - 3,&resp[3]); 559 } 560} 561 562static u_int 563pmu_adb_send(device_t dev, u_char command_byte, int len, u_char *data, 564 u_char poll) 565{ 566 struct pmu_softc *sc = device_get_softc(dev); 567 int i,replen; 568 uint8_t packet[16], resp[16]; 569 570 /* construct an ADB command packet and send it */ 571 572 packet[0] = command_byte; 573 574 packet[1] = 0; 575 packet[2] = len; 576 for (i = 0; i < len; i++) 577 packet[i + 3] = data[i]; 578 579 mtx_lock(&sc->sc_mutex); 580 replen = pmu_send(sc, PMU_ADB_CMD, len + 3, packet, 16, resp); 581 mtx_unlock(&sc->sc_mutex); 582 583 if (poll) 584 pmu_poll(dev); 585 586 return 0; 587} 588 589static u_int 590pmu_adb_autopoll(device_t dev, uint16_t mask) 591{ 592 struct pmu_softc *sc = device_get_softc(dev); 593 594 mask = 0xffff; 595 596 /* magical incantation to re-enable autopolling */ 597 uint8_t cmd[] = {0, PMU_SET_POLL_MASK, (mask >> 8) & 0xff, mask & 0xff}; 598 uint8_t resp[16]; 599 600 mtx_lock(&sc->sc_mutex); 601 602 if (sc->sc_autopoll == mask) { 603 mtx_unlock(&sc->sc_mutex); 604 return 0; 605 } 606 607 sc->sc_autopoll = mask & 0xffff; 608 609 if (mask) 610 pmu_send(sc, PMU_ADB_CMD, 4, cmd, 16, resp); 611 else 612 pmu_send(sc, PMU_ADB_POLL_OFF, 0, NULL, 16, resp); 613 614 mtx_unlock(&sc->sc_mutex); 615 616 return 0; 617} 618