1 /* $NetBSD: thinkpad_acpi.c,v 1.15 2008/05/05 00:14:11 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2007 Jared D. McNeill <jmcneill@invisible.ca> 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 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: thinkpad_acpi.c,v 1.15 2008/05/05 00:14:11 jmcneill Exp $"); 31 32 #include <sys/types.h> 33 #include <sys/param.h> 34 #include <sys/malloc.h> 35 #include <sys/buf.h> 36 #include <sys/callout.h> 37 #include <sys/kernel.h> 38 #include <sys/device.h> 39 #include <sys/pmf.h> 40 #include <sys/queue.h> 41 #include <sys/kmem.h> 42 43 #include <dev/acpi/acpivar.h> 44 #include <dev/acpi/acpi_ecvar.h> 45 46 #if defined(__i386__) || defined(__amd64__) 47 #include <dev/isa/isareg.h> 48 #include <machine/pio.h> 49 #endif 50 51 #define THINKPAD_NSENSORS 8 52 53 typedef struct thinkpad_softc { 54 device_t sc_dev; 55 device_t sc_ecdev; 56 struct acpi_devnode *sc_node; 57 ACPI_HANDLE sc_cmoshdl; 58 bool sc_cmoshdl_valid; 59 60 #define TP_PSW_SLEEP 0 61 #define TP_PSW_HIBERNATE 1 62 #define TP_PSW_DISPLAY_CYCLE 2 63 #define TP_PSW_LOCK_SCREEN 3 64 #define TP_PSW_BATTERY_INFO 4 65 #define TP_PSW_EJECT_BUTTON 5 66 #define TP_PSW_ZOOM_BUTTON 6 67 #define TP_PSW_VENDOR_BUTTON 7 68 #define TP_PSW_LAST 8 69 struct sysmon_pswitch sc_smpsw[TP_PSW_LAST]; 70 bool sc_smpsw_valid; 71 72 struct sysmon_envsys *sc_sme; 73 envsys_data_t sc_sensor[THINKPAD_NSENSORS]; 74 75 int sc_display_state; 76 } thinkpad_softc_t; 77 78 /* Hotkey events */ 79 #define THINKPAD_NOTIFY_FnF1 0x001 80 #define THINKPAD_NOTIFY_LockScreen 0x002 81 #define THINKPAD_NOTIFY_BatteryInfo 0x003 82 #define THINKPAD_NOTIFY_SleepButton 0x004 83 #define THINKPAD_NOTIFY_WirelessSwitch 0x005 84 #define THINKPAD_NOTIFY_FnF6 0x006 85 #define THINKPAD_NOTIFY_DisplayCycle 0x007 86 #define THINKPAD_NOTIFY_PointerSwitch 0x008 87 #define THINKPAD_NOTIFY_EjectButton 0x009 88 #define THINKPAD_NOTIFY_FnF10 0x00a 89 #define THINKPAD_NOTIFY_FnF11 0x00b 90 #define THINKPAD_NOTIFY_HibernateButton 0x00c 91 #define THINKPAD_NOTIFY_BrightnessUp 0x010 92 #define THINKPAD_NOTIFY_BrightnessDown 0x011 93 #define THINKPAD_NOTIFY_ThinkLight 0x012 94 #define THINKPAD_NOTIFY_Zoom 0x014 95 #define THINKPAD_NOTIFY_ThinkVantage 0x018 96 97 #define THINKPAD_CMOS_BRIGHTNESS_UP 0x04 98 #define THINKPAD_CMOS_BRIGHTNESS_DOWN 0x05 99 100 #define THINKPAD_HKEY_VERSION 0x0100 101 102 #define THINKPAD_DISPLAY_LCD 0x01 103 #define THINKPAD_DISPLAY_CRT 0x02 104 #define THINKPAD_DISPLAY_DVI 0x08 105 #define THINKPAD_DISPLAY_ALL \ 106 (THINKPAD_DISPLAY_LCD | THINKPAD_DISPLAY_CRT | THINKPAD_DISPLAY_DVI) 107 108 static int thinkpad_match(device_t, struct cfdata *, void *); 109 static void thinkpad_attach(device_t, device_t, void *); 110 111 static ACPI_STATUS thinkpad_mask_init(thinkpad_softc_t *, uint32_t); 112 static void thinkpad_notify_handler(ACPI_HANDLE, UINT32, void *); 113 static void thinkpad_get_hotkeys(void *); 114 115 static void thinkpad_temp_init(thinkpad_softc_t *); 116 static void thinkpad_temp_refresh(struct sysmon_envsys *, envsys_data_t *); 117 118 static void thinkpad_wireless_toggle(thinkpad_softc_t *); 119 120 static bool thinkpad_resume(device_t PMF_FN_PROTO); 121 static void thinkpad_brightness_up(device_t); 122 static void thinkpad_brightness_down(device_t); 123 static uint8_t thinkpad_brightness_read(thinkpad_softc_t *sc); 124 static void thinkpad_cmos(thinkpad_softc_t *, uint8_t); 125 126 CFATTACH_DECL_NEW(thinkpad, sizeof(thinkpad_softc_t), 127 thinkpad_match, thinkpad_attach, NULL, NULL); 128 129 static const char * const thinkpad_ids[] = { 130 "IBM0068", 131 NULL 132 }; 133 134 static int 135 thinkpad_match(device_t parent, struct cfdata *match, void *opaque) 136 { 137 struct acpi_attach_args *aa = (struct acpi_attach_args *)opaque; 138 ACPI_HANDLE hdl; 139 ACPI_INTEGER ver; 140 141 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) 142 return 0; 143 144 if (!acpi_match_hid(aa->aa_node->ad_devinfo, thinkpad_ids)) 145 return 0; 146 147 /* No point in attaching if we can't find the CMOS method */ 148 if (ACPI_FAILURE(AcpiGetHandle(NULL, "\\UCMS", &hdl))) 149 return 0; 150 151 /* We only support hotkey version 0x0100 */ 152 if (ACPI_FAILURE(acpi_eval_integer(aa->aa_node->ad_handle, "MHKV", 153 &ver))) 154 return 0; 155 156 if (ver != THINKPAD_HKEY_VERSION) 157 return 0; 158 159 /* Cool, looks like we're good to go */ 160 return 1; 161 } 162 163 static void 164 thinkpad_attach(device_t parent, device_t self, void *opaque) 165 { 166 thinkpad_softc_t *sc = device_private(self); 167 struct acpi_attach_args *aa = (struct acpi_attach_args *)opaque; 168 struct sysmon_pswitch *psw; 169 device_t curdev; 170 ACPI_STATUS rv; 171 ACPI_INTEGER val; 172 int i; 173 174 sc->sc_node = aa->aa_node; 175 sc->sc_dev = self; 176 sc->sc_display_state = THINKPAD_DISPLAY_LCD; 177 178 aprint_naive("\n"); 179 aprint_normal("\n"); 180 181 /* T61 uses \UCMS method for issuing CMOS commands */ 182 rv = AcpiGetHandle(NULL, "\\UCMS", &sc->sc_cmoshdl); 183 if (ACPI_FAILURE(rv)) 184 sc->sc_cmoshdl_valid = false; 185 else { 186 aprint_verbose_dev(self, "using CMOS at \\UCMS\n"); 187 sc->sc_cmoshdl_valid = true; 188 } 189 190 sc->sc_ecdev = NULL; 191 TAILQ_FOREACH(curdev, &alldevs, dv_list) 192 if (device_is_a(curdev, "acpiecdt") || 193 device_is_a(curdev, "acpiec")) { 194 sc->sc_ecdev = curdev; 195 break; 196 } 197 if (sc->sc_ecdev) 198 aprint_verbose_dev(self, "using EC at %s\n", 199 device_xname(sc->sc_ecdev)); 200 201 /* Get the supported event mask */ 202 rv = acpi_eval_integer(sc->sc_node->ad_handle, "MHKA", &val); 203 if (ACPI_FAILURE(rv)) { 204 aprint_error_dev(self, "couldn't evaluate MHKA: %s\n", 205 AcpiFormatException(rv)); 206 goto fail; 207 } 208 209 /* Enable all supported events */ 210 rv = thinkpad_mask_init(sc, val); 211 if (ACPI_FAILURE(rv)) { 212 aprint_error_dev(self, "couldn't set event mask: %s\n", 213 AcpiFormatException(rv)); 214 goto fail; 215 } 216 217 /* Install notify handler for events */ 218 rv = AcpiInstallNotifyHandler(sc->sc_node->ad_handle, 219 ACPI_DEVICE_NOTIFY, thinkpad_notify_handler, sc); 220 if (ACPI_FAILURE(rv)) 221 aprint_error_dev(self, "couldn't install notify handler: %s\n", 222 AcpiFormatException(rv)); 223 224 /* Register power switches with sysmon */ 225 psw = sc->sc_smpsw; 226 sc->sc_smpsw_valid = true; 227 228 psw[TP_PSW_SLEEP].smpsw_name = device_xname(self); 229 psw[TP_PSW_SLEEP].smpsw_type = PSWITCH_TYPE_SLEEP; 230 #if notyet 231 psw[TP_PSW_HIBERNATE].smpsw_name = device_xname(self); 232 mpsw[TP_PSW_HIBERNATE].smpsw_type = PSWITCH_TYPE_HIBERNATE; 233 #endif 234 for (i = TP_PSW_DISPLAY_CYCLE; i < TP_PSW_LAST; i++) 235 sc->sc_smpsw[i].smpsw_type = PSWITCH_TYPE_HOTKEY; 236 psw[TP_PSW_DISPLAY_CYCLE].smpsw_name = PSWITCH_HK_DISPLAY_CYCLE; 237 psw[TP_PSW_LOCK_SCREEN].smpsw_name = PSWITCH_HK_LOCK_SCREEN; 238 psw[TP_PSW_BATTERY_INFO].smpsw_name = PSWITCH_HK_BATTERY_INFO; 239 psw[TP_PSW_EJECT_BUTTON].smpsw_name = PSWITCH_HK_EJECT_BUTTON; 240 psw[TP_PSW_ZOOM_BUTTON].smpsw_name = PSWITCH_HK_ZOOM_BUTTON; 241 psw[TP_PSW_VENDOR_BUTTON].smpsw_name = PSWITCH_HK_VENDOR_BUTTON; 242 243 for (i = 0; i < TP_PSW_LAST; i++) { 244 /* not supported yet */ 245 if (i == TP_PSW_HIBERNATE) 246 continue; 247 if (sysmon_pswitch_register(&sc->sc_smpsw[i]) != 0) { 248 aprint_error_dev(self, 249 "couldn't register with sysmon\n"); 250 sc->sc_smpsw_valid = false; 251 break; 252 } 253 } 254 255 /* Register temperature sensors with envsys */ 256 thinkpad_temp_init(sc); 257 258 fail: 259 if (!pmf_device_register(self, NULL, thinkpad_resume)) 260 aprint_error_dev(self, "couldn't establish power handler\n"); 261 if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_UP, 262 thinkpad_brightness_up, true)) 263 aprint_error_dev(self, "couldn't register event handler\n"); 264 if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_DOWN, 265 thinkpad_brightness_down, true)) 266 aprint_error_dev(self, "couldn't register event handler\n"); 267 } 268 269 static void 270 thinkpad_notify_handler(ACPI_HANDLE hdl, UINT32 notify, void *opaque) 271 { 272 thinkpad_softc_t *sc = (thinkpad_softc_t *)opaque; 273 device_t self = sc->sc_dev; 274 ACPI_STATUS rv; 275 276 if (notify != 0x80) { 277 aprint_debug_dev(self, "unknown notify 0x%02x\n", notify); 278 return; 279 } 280 281 rv = AcpiOsExecute(OSL_NOTIFY_HANDLER, thinkpad_get_hotkeys, sc); 282 if (ACPI_FAILURE(rv)) 283 aprint_error_dev(self, "couldn't queue hotkey handler: %s\n", 284 AcpiFormatException(rv)); 285 } 286 287 static void 288 thinkpad_get_hotkeys(void *opaque) 289 { 290 thinkpad_softc_t *sc = (thinkpad_softc_t *)opaque; 291 device_t self = sc->sc_dev; 292 ACPI_STATUS rv; 293 ACPI_INTEGER val; 294 int type, event; 295 296 for (;;) { 297 rv = acpi_eval_integer(sc->sc_node->ad_handle, "MHKP", &val); 298 if (ACPI_FAILURE(rv)) { 299 aprint_error_dev(self, "couldn't evaluate MHKP: %s\n", 300 AcpiFormatException(rv)); 301 return; 302 } 303 304 if (val == 0) 305 return; 306 307 type = (val & 0xf000) >> 12; 308 event = val & 0x0fff; 309 310 if (type != 1) 311 /* Only type 1 events are supported for now */ 312 continue; 313 314 switch (event) { 315 case THINKPAD_NOTIFY_BrightnessUp: 316 thinkpad_brightness_up(self); 317 break; 318 case THINKPAD_NOTIFY_BrightnessDown: 319 thinkpad_brightness_down(self); 320 break; 321 case THINKPAD_NOTIFY_WirelessSwitch: 322 thinkpad_wireless_toggle(sc); 323 break; 324 case THINKPAD_NOTIFY_SleepButton: 325 if (sc->sc_smpsw_valid == false) 326 break; 327 sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_SLEEP], 328 PSWITCH_EVENT_PRESSED); 329 break; 330 case THINKPAD_NOTIFY_HibernateButton: 331 #if notyet 332 if (sc->sc_smpsw_valid == false) 333 break; 334 sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_HIBERNATE], 335 PSWITCH_EVENT_PRESSED); 336 #endif 337 break; 338 case THINKPAD_NOTIFY_DisplayCycle: 339 if (sc->sc_smpsw_valid == false) 340 break; 341 sysmon_pswitch_event( 342 &sc->sc_smpsw[TP_PSW_DISPLAY_CYCLE], 343 PSWITCH_EVENT_PRESSED); 344 break; 345 case THINKPAD_NOTIFY_LockScreen: 346 if (sc->sc_smpsw_valid == false) 347 break; 348 sysmon_pswitch_event( 349 &sc->sc_smpsw[TP_PSW_LOCK_SCREEN], 350 PSWITCH_EVENT_PRESSED); 351 break; 352 case THINKPAD_NOTIFY_BatteryInfo: 353 if (sc->sc_smpsw_valid == false) 354 break; 355 sysmon_pswitch_event( 356 &sc->sc_smpsw[TP_PSW_BATTERY_INFO], 357 PSWITCH_EVENT_PRESSED); 358 break; 359 case THINKPAD_NOTIFY_EjectButton: 360 if (sc->sc_smpsw_valid == false) 361 break; 362 sysmon_pswitch_event( 363 &sc->sc_smpsw[TP_PSW_EJECT_BUTTON], 364 PSWITCH_EVENT_PRESSED); 365 break; 366 case THINKPAD_NOTIFY_Zoom: 367 if (sc->sc_smpsw_valid == false) 368 break; 369 sysmon_pswitch_event( 370 &sc->sc_smpsw[TP_PSW_ZOOM_BUTTON], 371 PSWITCH_EVENT_PRESSED); 372 break; 373 case THINKPAD_NOTIFY_ThinkVantage: 374 if (sc->sc_smpsw_valid == false) 375 break; 376 sysmon_pswitch_event( 377 &sc->sc_smpsw[TP_PSW_VENDOR_BUTTON], 378 PSWITCH_EVENT_PRESSED); 379 break; 380 case THINKPAD_NOTIFY_FnF1: 381 case THINKPAD_NOTIFY_FnF6: 382 case THINKPAD_NOTIFY_PointerSwitch: 383 case THINKPAD_NOTIFY_FnF10: 384 case THINKPAD_NOTIFY_FnF11: 385 case THINKPAD_NOTIFY_ThinkLight: 386 /* XXXJDM we should deliver hotkeys as keycodes */ 387 break; 388 default: 389 aprint_debug_dev(self, "notify event 0x%03x\n", event); 390 break; 391 } 392 } 393 } 394 395 static ACPI_STATUS 396 thinkpad_mask_init(thinkpad_softc_t *sc, uint32_t mask) 397 { 398 ACPI_OBJECT param[2]; 399 ACPI_OBJECT_LIST params; 400 ACPI_STATUS rv; 401 int i; 402 403 /* Update hotkey mask */ 404 params.Count = 2; 405 params.Pointer = param; 406 param[0].Type = param[1].Type = ACPI_TYPE_INTEGER; 407 408 for (i = 0; i < 32; i++) { 409 param[0].Integer.Value = i + 1; 410 param[1].Integer.Value = (((1 << i) & mask) != 0); 411 412 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "MHKM", 413 ¶ms, NULL); 414 if (ACPI_FAILURE(rv)) 415 return rv; 416 } 417 418 /* Enable hotkey events */ 419 params.Count = 1; 420 param[0].Integer.Value = 1; 421 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "MHKC", ¶ms, NULL); 422 if (ACPI_FAILURE(rv)) { 423 aprint_error_dev(sc->sc_dev, "couldn't enable hotkeys: %s\n", 424 AcpiFormatException(rv)); 425 return rv; 426 } 427 428 /* Claim ownership of brightness control */ 429 param[0].Integer.Value = 0; 430 (void)AcpiEvaluateObject(sc->sc_node->ad_handle, "PWMS", ¶ms, NULL); 431 432 return AE_OK; 433 } 434 435 static void 436 thinkpad_temp_init(thinkpad_softc_t *sc) 437 { 438 char sname[5] = "TMP?"; 439 int i, err; 440 441 if (sc->sc_ecdev == NULL) 442 return; /* no chance of this working */ 443 444 sc->sc_sme = sysmon_envsys_create(); 445 for (i = 0; i < THINKPAD_NSENSORS; i++) { 446 sname[3] = '0' + i; 447 strcpy(sc->sc_sensor[i].desc, sname); 448 sc->sc_sensor[i].units = ENVSYS_STEMP; 449 450 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[i])) 451 aprint_error_dev(sc->sc_dev, 452 "couldn't attach sensor %s\n", sname); 453 } 454 455 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 456 sc->sc_sme->sme_cookie = sc; 457 sc->sc_sme->sme_refresh = thinkpad_temp_refresh; 458 459 err = sysmon_envsys_register(sc->sc_sme); 460 if (err) { 461 aprint_error_dev(sc->sc_dev, 462 "couldn't register with sysmon: %d\n", err); 463 sysmon_envsys_destroy(sc->sc_sme); 464 } 465 } 466 467 static void 468 thinkpad_temp_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 469 { 470 thinkpad_softc_t *sc = sme->sme_cookie; 471 char sname[5] = "TMP?"; 472 ACPI_INTEGER val; 473 ACPI_STATUS rv; 474 int temp; 475 476 sname[3] = '0' + edata->sensor; 477 rv = acpi_eval_integer(acpiec_get_handle(sc->sc_ecdev), sname, &val); 478 if (ACPI_FAILURE(rv)) { 479 edata->state = ENVSYS_SINVALID; 480 return; 481 } 482 temp = (int)val; 483 if (temp > 127 || temp < -127) { 484 edata->state = ENVSYS_SINVALID; 485 return; 486 } 487 488 edata->value_cur = temp * 1000000 + 273150000; 489 edata->state = ENVSYS_SVALID; 490 } 491 492 static void 493 thinkpad_wireless_toggle(thinkpad_softc_t *sc) 494 { 495 /* Ignore return value, as the hardware may not support bluetooth */ 496 (void)AcpiEvaluateObject(sc->sc_node->ad_handle, "BTGL", NULL, NULL); 497 } 498 499 static uint8_t 500 thinkpad_brightness_read(thinkpad_softc_t *sc) 501 { 502 #if defined(__i386__) || defined(__amd64__) 503 /* 504 * We have two ways to get the current brightness -- either via 505 * magic RTC registers, or using the EC. Since I don't dare mess 506 * with the EC, and Thinkpads are x86-only, this will have to do 507 * for now. 508 */ 509 outb(IO_RTC, 0x6c); 510 return inb(IO_RTC+1) & 7; 511 #else 512 return 0; 513 #endif 514 } 515 516 static void 517 thinkpad_brightness_up(device_t self) 518 { 519 thinkpad_softc_t *sc = device_private(self); 520 521 if (thinkpad_brightness_read(sc) == 7) 522 return; 523 thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP); 524 } 525 526 static void 527 thinkpad_brightness_down(device_t self) 528 { 529 thinkpad_softc_t *sc = device_private(self); 530 531 if (thinkpad_brightness_read(sc) == 0) 532 return; 533 thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN); 534 } 535 536 static void 537 thinkpad_cmos(thinkpad_softc_t *sc, uint8_t cmd) 538 { 539 ACPI_OBJECT param; 540 ACPI_OBJECT_LIST params; 541 ACPI_STATUS rv; 542 543 if (sc->sc_cmoshdl_valid == false) 544 return; 545 546 params.Count = 1; 547 params.Pointer = ¶m; 548 param.Type = ACPI_TYPE_INTEGER; 549 param.Integer.Value = cmd; 550 rv = AcpiEvaluateObject(sc->sc_cmoshdl, NULL, ¶ms, NULL); 551 if (ACPI_FAILURE(rv)) 552 aprint_error_dev(sc->sc_dev, "couldn't evalute CMOS: %s\n", 553 AcpiFormatException(rv)); 554 } 555 556 static bool 557 thinkpad_resume(device_t dv PMF_FN_ARGS) 558 { 559 ACPI_STATUS rv; 560 ACPI_HANDLE pubs; 561 562 rv = AcpiGetHandle(NULL, "\\_SB.PCI0.LPC.EC.PUBS", &pubs); 563 if (ACPI_FAILURE(rv)) 564 return true; /* not fatal */ 565 566 rv = AcpiEvaluateObject(pubs, "_ON", NULL, NULL); 567 if (ACPI_FAILURE(rv)) 568 aprint_error_dev(dv, "failed to execute PUBS._ON: %s\n", 569 AcpiFormatException(rv)); 570 571 return true; 572 } 573