alpm.c revision 165951
1/*- 2 * Copyright (c) 1998, 1999, 2001 Nicolas Souchu 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27/* 28 * Power Management support for the Acer M15x3 chipsets 29 */ 30 31#include <sys/cdefs.h> 32__FBSDID("$FreeBSD: head/sys/pci/alpm.c 165951 2007-01-11 19:56:24Z jhb $"); 33 34#include <sys/param.h> 35#include <sys/bus.h> 36#include <sys/kernel.h> 37#include <sys/lock.h> 38#include <sys/module.h> 39#include <sys/mutex.h> 40#include <sys/systm.h> 41 42#include <machine/bus.h> 43#include <machine/resource.h> 44#include <sys/rman.h> 45 46#include <dev/pci/pcivar.h> 47#include <dev/pci/pcireg.h> 48 49#include <dev/smbus/smbconf.h> 50#include "smbus_if.h" 51 52#define ALPM_DEBUG(x) if (alpm_debug) (x) 53 54#ifdef DEBUG 55static int alpm_debug = 1; 56#else 57static int alpm_debug = 0; 58#endif 59 60#define ACER_M1543_PMU_ID 0x710110b9 61 62/* 63 * I/O registers offsets - the base address is programmed via the 64 * SMBBA PCI configuration register 65 */ 66#define SMBSTS 0x0 /* SMBus host/slave status register */ 67#define SMBCMD 0x1 /* SMBus host/slave command register */ 68#define SMBSTART 0x2 /* start to generate programmed cycle */ 69#define SMBHADDR 0x3 /* host address register */ 70#define SMBHDATA 0x4 /* data A register for host controller */ 71#define SMBHDATB 0x5 /* data B register for host controller */ 72#define SMBHBLOCK 0x6 /* block register for host controller */ 73#define SMBHCMD 0x7 /* command register for host controller */ 74 75/* SMBHADDR mask. */ 76#define LSB 0x1 /* XXX: Better name: Read/Write? */ 77 78/* SMBSTS masks */ 79#define TERMINATE 0x80 80#define BUS_COLLI 0x40 81#define DEVICE_ERR 0x20 82#define SMI_I_STS 0x10 83#define HST_BSY 0x08 84#define IDL_STS 0x04 85#define HSTSLV_STS 0x02 86#define HSTSLV_BSY 0x01 87 88/* SMBCMD masks */ 89#define SMB_BLK_CLR 0x80 90#define T_OUT_CMD 0x08 91#define ABORT_HOST 0x04 92 93/* SMBus commands */ 94#define SMBQUICK 0x00 95#define SMBSRBYTE 0x10 /* send/receive byte */ 96#define SMBWRBYTE 0x20 /* write/read byte */ 97#define SMBWRWORD 0x30 /* write/read word */ 98#define SMBWRBLOCK 0x40 /* write/read block */ 99 100/* PCI configuration registers and masks 101 */ 102#define COM 0x4 103#define COM_ENABLE_IO 0x1 104 105#define SMBBA PCIR_BAR(1) 106 107#define ATPC 0x5b 108#define ATPC_SMBCTRL 0x04 /* XX linux has this as 0x6 */ 109 110#define SMBHSI 0xe0 111#define SMBHSI_SLAVE 0x2 112#define SMBHSI_HOST 0x1 113 114#define SMBHCBC 0xe2 115#define SMBHCBC_CLOCK 0x70 116 117#define SMBCLOCK_149K 0x0 118#define SMBCLOCK_74K 0x20 119#define SMBCLOCK_37K 0x40 120#define SMBCLOCK_223K 0x80 121#define SMBCLOCK_111K 0xa0 122#define SMBCLOCK_55K 0xc0 123 124struct alpm_softc { 125 int base; 126 struct resource *res; 127 bus_space_tag_t smbst; 128 bus_space_handle_t smbsh; 129 device_t smbus; 130 struct mtx lock; 131}; 132 133#define ALPM_LOCK(alpm) mtx_lock(&(alpm)->lock) 134#define ALPM_UNLOCK(alpm) mtx_unlock(&(alpm)->lock) 135#define ALPM_LOCK_ASSERT(alpm) mtx_assert(&(alpm)->lock, MA_OWNED) 136 137#define ALPM_SMBINB(alpm,register) \ 138 (bus_space_read_1(alpm->smbst, alpm->smbsh, register)) 139#define ALPM_SMBOUTB(alpm,register,value) \ 140 (bus_space_write_1(alpm->smbst, alpm->smbsh, register, value)) 141 142static int alpm_detach(device_t dev); 143 144static int 145alpm_probe(device_t dev) 146{ 147 148 if (pci_get_devid(dev) == ACER_M1543_PMU_ID) { 149 device_set_desc(dev, "AcerLabs M15x3 Power Management Unit"); 150 151 return (BUS_PROBE_DEFAULT); 152 } 153 154 return (ENXIO); 155} 156 157static int 158alpm_attach(device_t dev) 159{ 160 int rid; 161 u_int32_t l; 162 struct alpm_softc *alpm; 163 164 alpm = device_get_softc(dev); 165 166 /* Unlock SMBIO base register access */ 167 l = pci_read_config(dev, ATPC, 1); 168 pci_write_config(dev, ATPC, l & ~ATPC_SMBCTRL, 1); 169 170 /* 171 * XX linux sets clock to 74k, should we? 172 l = pci_read_config(dev, SMBHCBC, 1); 173 l &= 0x1f; 174 l |= SMBCLOCK_74K; 175 pci_write_config(dev, SMBHCBC, l, 1); 176 */ 177 178 if (bootverbose || alpm_debug) { 179 l = pci_read_config(dev, SMBHSI, 1); 180 device_printf(dev, "%s/%s", 181 (l & SMBHSI_HOST) ? "host":"nohost", 182 (l & SMBHSI_SLAVE) ? "slave":"noslave"); 183 184 l = pci_read_config(dev, SMBHCBC, 1); 185 switch (l & SMBHCBC_CLOCK) { 186 case SMBCLOCK_149K: 187 printf(" 149K"); 188 break; 189 case SMBCLOCK_74K: 190 printf(" 74K"); 191 break; 192 case SMBCLOCK_37K: 193 printf(" 37K"); 194 break; 195 case SMBCLOCK_223K: 196 printf(" 223K"); 197 break; 198 case SMBCLOCK_111K: 199 printf(" 111K"); 200 break; 201 case SMBCLOCK_55K: 202 printf(" 55K"); 203 break; 204 default: 205 printf("unkown"); 206 break; 207 } 208 printf("\n"); 209 } 210 211 rid = SMBBA; 212 alpm->res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, 213 RF_ACTIVE); 214 215 if (alpm->res == NULL) { 216 device_printf(dev,"Could not allocate Bus space\n"); 217 return (ENXIO); 218 } 219 alpm->smbst = rman_get_bustag(alpm->res); 220 alpm->smbsh = rman_get_bushandle(alpm->res); 221 mtx_init(&alpm->lock, device_get_nameunit(dev), "alpm", MTX_DEF); 222 223 /* attach the smbus */ 224 alpm->smbus = device_add_child(dev, "smbus", -1); 225 if (alpm->smbus == NULL) { 226 alpm_detach(dev); 227 return (EINVAL); 228 } 229 bus_generic_attach(dev); 230 231 return (0); 232} 233 234static int 235alpm_detach(device_t dev) 236{ 237 struct alpm_softc *alpm = device_get_softc(dev); 238 239 if (alpm->smbus) { 240 device_delete_child(dev, alpm->smbus); 241 alpm->smbus = NULL; 242 } 243 mtx_destroy(&alpm->lock); 244 245 if (alpm->res) 246 bus_release_resource(dev, SYS_RES_IOPORT, SMBBA, alpm->res); 247 248 return (0); 249} 250 251static int 252alpm_callback(device_t dev, int index, void *data) 253{ 254 int error = 0; 255 256 switch (index) { 257 case SMB_REQUEST_BUS: 258 case SMB_RELEASE_BUS: 259 /* ok, bus allocation accepted */ 260 break; 261 default: 262 error = EINVAL; 263 } 264 265 return (error); 266} 267 268static int 269alpm_clear(struct alpm_softc *sc) 270{ 271 ALPM_SMBOUTB(sc, SMBSTS, 0xff); 272 DELAY(10); 273 274 return (0); 275} 276 277#if 0 278static int 279alpm_abort(struct alpm_softc *sc) 280{ 281 ALPM_SMBOUTB(sc, SMBCMD, T_OUT_CMD | ABORT_HOST); 282 283 return (0); 284} 285#endif 286 287static int 288alpm_idle(struct alpm_softc *sc) 289{ 290 u_char sts; 291 292 sts = ALPM_SMBINB(sc, SMBSTS); 293 294 ALPM_DEBUG(printf("alpm: idle? STS=0x%x\n", sts)); 295 296 return (sts & IDL_STS); 297} 298 299/* 300 * Poll the SMBus controller 301 */ 302static int 303alpm_wait(struct alpm_softc *sc) 304{ 305 int count = 10000; 306 u_char sts = 0; 307 int error; 308 309 /* wait for command to complete and SMBus controller is idle */ 310 while (count--) { 311 DELAY(10); 312 sts = ALPM_SMBINB(sc, SMBSTS); 313 if (sts & SMI_I_STS) 314 break; 315 } 316 317 ALPM_DEBUG(printf("alpm: STS=0x%x\n", sts)); 318 319 error = SMB_ENOERR; 320 321 if (!count) 322 error |= SMB_ETIMEOUT; 323 324 if (sts & TERMINATE) 325 error |= SMB_EABORT; 326 327 if (sts & BUS_COLLI) 328 error |= SMB_ENOACK; 329 330 if (sts & DEVICE_ERR) 331 error |= SMB_EBUSERR; 332 333 if (error != SMB_ENOERR) 334 alpm_clear(sc); 335 336 return (error); 337} 338 339static int 340alpm_quick(device_t dev, u_char slave, int how) 341{ 342 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 343 int error; 344 345 ALPM_LOCK(sc); 346 alpm_clear(sc); 347 if (!alpm_idle(sc)) { 348 ALPM_UNLOCK(sc); 349 return (EBUSY); 350 } 351 352 switch (how) { 353 case SMB_QWRITE: 354 ALPM_DEBUG(printf("alpm: QWRITE to 0x%x", slave)); 355 ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB); 356 break; 357 case SMB_QREAD: 358 ALPM_DEBUG(printf("alpm: QREAD to 0x%x", slave)); 359 ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB); 360 break; 361 default: 362 panic("%s: unknown QUICK command (%x)!", __func__, 363 how); 364 } 365 ALPM_SMBOUTB(sc, SMBCMD, SMBQUICK); 366 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 367 368 error = alpm_wait(sc); 369 370 ALPM_DEBUG(printf(", error=0x%x\n", error)); 371 ALPM_UNLOCK(sc); 372 373 return (error); 374} 375 376static int 377alpm_sendb(device_t dev, u_char slave, char byte) 378{ 379 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 380 int error; 381 382 ALPM_LOCK(sc); 383 alpm_clear(sc); 384 if (!alpm_idle(sc)) { 385 ALPM_UNLOCK(sc); 386 return (SMB_EBUSY); 387 } 388 389 ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB); 390 ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE); 391 ALPM_SMBOUTB(sc, SMBHDATA, byte); 392 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 393 394 error = alpm_wait(sc); 395 396 ALPM_DEBUG(printf("alpm: SENDB to 0x%x, byte=0x%x, error=0x%x\n", slave, byte, error)); 397 ALPM_UNLOCK(sc); 398 399 return (error); 400} 401 402static int 403alpm_recvb(device_t dev, u_char slave, char *byte) 404{ 405 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 406 int error; 407 408 ALPM_LOCK(sc); 409 alpm_clear(sc); 410 if (!alpm_idle(sc)) { 411 ALPM_UNLOCK(sc); 412 return (SMB_EBUSY); 413 } 414 415 ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB); 416 ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE); 417 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 418 419 if ((error = alpm_wait(sc)) == SMB_ENOERR) 420 *byte = ALPM_SMBINB(sc, SMBHDATA); 421 422 ALPM_DEBUG(printf("alpm: RECVB from 0x%x, byte=0x%x, error=0x%x\n", slave, *byte, error)); 423 ALPM_UNLOCK(sc); 424 425 return (error); 426} 427 428static int 429alpm_writeb(device_t dev, u_char slave, char cmd, char byte) 430{ 431 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 432 int error; 433 434 ALPM_LOCK(sc); 435 alpm_clear(sc); 436 if (!alpm_idle(sc)) { 437 ALPM_UNLOCK(sc); 438 return (SMB_EBUSY); 439 } 440 441 ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB); 442 ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE); 443 ALPM_SMBOUTB(sc, SMBHDATA, byte); 444 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 445 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 446 447 error = alpm_wait(sc); 448 449 ALPM_DEBUG(printf("alpm: WRITEB to 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, byte, error)); 450 ALPM_UNLOCK(sc); 451 452 return (error); 453} 454 455static int 456alpm_readb(device_t dev, u_char slave, char cmd, char *byte) 457{ 458 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 459 int error; 460 461 ALPM_LOCK(sc); 462 alpm_clear(sc); 463 if (!alpm_idle(sc)) { 464 ALPM_UNLOCK(sc); 465 return (SMB_EBUSY); 466 } 467 468 ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB); 469 ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE); 470 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 471 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 472 473 if ((error = alpm_wait(sc)) == SMB_ENOERR) 474 *byte = ALPM_SMBINB(sc, SMBHDATA); 475 476 ALPM_DEBUG(printf("alpm: READB from 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, *byte, error)); 477 ALPM_UNLOCK(sc); 478 479 return (error); 480} 481 482static int 483alpm_writew(device_t dev, u_char slave, char cmd, short word) 484{ 485 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 486 int error; 487 488 ALPM_LOCK(sc); 489 alpm_clear(sc); 490 if (!alpm_idle(sc)) { 491 ALPM_UNLOCK(sc); 492 return (SMB_EBUSY); 493 } 494 495 ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB); 496 ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD); 497 ALPM_SMBOUTB(sc, SMBHDATA, word & 0x00ff); 498 ALPM_SMBOUTB(sc, SMBHDATB, (word & 0xff00) >> 8); 499 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 500 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 501 502 error = alpm_wait(sc); 503 504 ALPM_DEBUG(printf("alpm: WRITEW to 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, word, error)); 505 ALPM_UNLOCK(sc); 506 507 return (error); 508} 509 510static int 511alpm_readw(device_t dev, u_char slave, char cmd, short *word) 512{ 513 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 514 int error; 515 u_char high, low; 516 517 ALPM_LOCK(sc); 518 alpm_clear(sc); 519 if (!alpm_idle(sc)) { 520 ALPM_UNLOCK(sc); 521 return (SMB_EBUSY); 522 } 523 524 ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB); 525 ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD); 526 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 527 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 528 529 if ((error = alpm_wait(sc)) == SMB_ENOERR) { 530 low = ALPM_SMBINB(sc, SMBHDATA); 531 high = ALPM_SMBINB(sc, SMBHDATB); 532 533 *word = ((high & 0xff) << 8) | (low & 0xff); 534 } 535 536 ALPM_DEBUG(printf("alpm: READW from 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, *word, error)); 537 ALPM_UNLOCK(sc); 538 539 return (error); 540} 541 542static int 543alpm_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) 544{ 545 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 546 u_char i; 547 int error; 548 549 if (count < 1 || count > 32) 550 return (SMB_EINVAL); 551 552 ALPM_LOCK(sc); 553 alpm_clear(sc); 554 if(!alpm_idle(sc)) { 555 ALPM_UNLOCK(sc); 556 return (SMB_EBUSY); 557 } 558 559 ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB); 560 561 /* set the cmd and reset the 562 * 32-byte long internal buffer */ 563 ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR); 564 565 ALPM_SMBOUTB(sc, SMBHDATA, count); 566 567 /* fill the 32-byte internal buffer */ 568 for (i = 0; i < count; i++) { 569 ALPM_SMBOUTB(sc, SMBHBLOCK, buf[i]); 570 DELAY(2); 571 } 572 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 573 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 574 575 error = alpm_wait(sc); 576 577 ALPM_DEBUG(printf("alpm: WRITEBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error)); 578 ALPM_UNLOCK(sc); 579 580 return (error); 581} 582 583static int 584alpm_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) 585{ 586 struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev); 587 u_char data, len, i; 588 int error; 589 590 if (*count < 1 || *count > 32) 591 return (SMB_EINVAL); 592 593 ALPM_LOCK(sc); 594 alpm_clear(sc); 595 if (!alpm_idle(sc)) { 596 ALPM_UNLOCK(sc); 597 return (SMB_EBUSY); 598 } 599 600 ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB); 601 602 /* set the cmd and reset the 603 * 32-byte long internal buffer */ 604 ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR); 605 606 ALPM_SMBOUTB(sc, SMBHCMD, cmd); 607 ALPM_SMBOUTB(sc, SMBSTART, 0xff); 608 609 if ((error = alpm_wait(sc)) != SMB_ENOERR) 610 goto error; 611 612 len = ALPM_SMBINB(sc, SMBHDATA); 613 614 /* read the 32-byte internal buffer */ 615 for (i = 0; i < len; i++) { 616 data = ALPM_SMBINB(sc, SMBHBLOCK); 617 if (i < *count) 618 buf[i] = data; 619 DELAY(2); 620 } 621 *count = len; 622 623error: 624 ALPM_DEBUG(printf("alpm: READBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, *count, cmd, error)); 625 ALPM_UNLOCK(sc); 626 627 return (error); 628} 629 630static devclass_t alpm_devclass; 631 632static device_method_t alpm_methods[] = { 633 /* device interface */ 634 DEVMETHOD(device_probe, alpm_probe), 635 DEVMETHOD(device_attach, alpm_attach), 636 DEVMETHOD(device_detach, alpm_detach), 637 638 /* smbus interface */ 639 DEVMETHOD(smbus_callback, alpm_callback), 640 DEVMETHOD(smbus_quick, alpm_quick), 641 DEVMETHOD(smbus_sendb, alpm_sendb), 642 DEVMETHOD(smbus_recvb, alpm_recvb), 643 DEVMETHOD(smbus_writeb, alpm_writeb), 644 DEVMETHOD(smbus_readb, alpm_readb), 645 DEVMETHOD(smbus_writew, alpm_writew), 646 DEVMETHOD(smbus_readw, alpm_readw), 647 DEVMETHOD(smbus_bwrite, alpm_bwrite), 648 DEVMETHOD(smbus_bread, alpm_bread), 649 650 { 0, 0 } 651}; 652 653static driver_t alpm_driver = { 654 "alpm", 655 alpm_methods, 656 sizeof(struct alpm_softc) 657}; 658 659DRIVER_MODULE(alpm, pci, alpm_driver, alpm_devclass, 0, 0); 660DRIVER_MODULE(smbus, alpm, smbus_driver, smbus_devclass, 0, 0); 661MODULE_DEPEND(alpm, pci, 1, 1, 1); 662MODULE_DEPEND(alpm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); 663MODULE_VERSION(alpm, 1); 664