1 /*- 2 * Copyright (c) 2004, 2005 Philip Paeps <philip@FreeBSD.org> 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 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 /* 31 * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on 32 * recent Asus (and Medion) laptops. Inspired by the acpi4asus project which 33 * implements these features in the Linux kernel. 34 * 35 * <http://sourceforge.net/projects/acpi4asus/> 36 * 37 * Currently should support most features, but could use some more testing. 38 * Particularly the display-switching stuff is a bit hairy. If you have an 39 * Asus laptop which doesn't appear to be supported, or strange things happen 40 * when using this driver, please report to <acpi@FreeBSD.org>. 41 */ 42 43 #include "opt_acpi.h" 44 #include <sys/param.h> 45 #include <sys/kernel.h> 46 #include <sys/module.h> 47 #include <sys/bus.h> 48 #include <sys/sbuf.h> 49 50 #include <contrib/dev/acpica/include/acpi.h> 51 #include <contrib/dev/acpica/include/accommon.h> 52 53 #include <dev/acpica/acpivar.h> 54 #include <dev/led/led.h> 55 56 /* Methods */ 57 #define ACPI_ASUS_METHOD_BRN 1 58 #define ACPI_ASUS_METHOD_DISP 2 59 #define ACPI_ASUS_METHOD_LCD 3 60 #define ACPI_ASUS_METHOD_CAMERA 4 61 #define ACPI_ASUS_METHOD_CARDRD 5 62 #define ACPI_ASUS_METHOD_WLAN 6 63 64 #define _COMPONENT ACPI_OEM 65 ACPI_MODULE_NAME("ASUS") 66 67 struct acpi_asus_model { 68 char *name; 69 70 char *bled_set; 71 char *dled_set; 72 char *gled_set; 73 char *mled_set; 74 char *tled_set; 75 char *wled_set; 76 77 char *brn_get; 78 char *brn_set; 79 char *brn_up; 80 char *brn_dn; 81 82 char *lcd_get; 83 char *lcd_set; 84 85 char *disp_get; 86 char *disp_set; 87 88 char *cam_get; 89 char *cam_set; 90 91 char *crd_get; 92 char *crd_set; 93 94 char *wlan_get; 95 char *wlan_set; 96 97 void (*n_func)(ACPI_HANDLE, UINT32, void *); 98 99 char *lcdd; 100 void (*lcdd_n_func)(ACPI_HANDLE, UINT32, void *); 101 }; 102 103 struct acpi_asus_led { 104 struct acpi_asus_softc *sc; 105 struct cdev *cdev; 106 int busy; 107 int state; 108 enum { 109 ACPI_ASUS_LED_BLED, 110 ACPI_ASUS_LED_DLED, 111 ACPI_ASUS_LED_GLED, 112 ACPI_ASUS_LED_MLED, 113 ACPI_ASUS_LED_TLED, 114 ACPI_ASUS_LED_WLED, 115 } type; 116 }; 117 118 struct acpi_asus_softc { 119 device_t dev; 120 ACPI_HANDLE handle; 121 ACPI_HANDLE lcdd_handle; 122 123 struct acpi_asus_model *model; 124 struct sysctl_ctx_list sysctl_ctx; 125 struct sysctl_oid *sysctl_tree; 126 127 struct acpi_asus_led s_bled; 128 struct acpi_asus_led s_dled; 129 struct acpi_asus_led s_gled; 130 struct acpi_asus_led s_mled; 131 struct acpi_asus_led s_tled; 132 struct acpi_asus_led s_wled; 133 134 int s_brn; 135 int s_disp; 136 int s_lcd; 137 int s_cam; 138 int s_crd; 139 int s_wlan; 140 }; 141 142 static void acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, 143 void *context); 144 145 /* 146 * We can identify Asus laptops from the string they return 147 * as a result of calling the ATK0100 'INIT' method. 148 */ 149 static struct acpi_asus_model acpi_asus_models[] = { 150 { 151 .name = "xxN", 152 .mled_set = "MLED", 153 .wled_set = "WLED", 154 .lcd_get = "\\BKLT", 155 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 156 .brn_get = "GPLV", 157 .brn_set = "SPLV", 158 .disp_get = "\\ADVG", 159 .disp_set = "SDSP" 160 }, 161 { 162 .name = "A1x", 163 .mled_set = "MLED", 164 .lcd_get = "\\BKLI", 165 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", 166 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E", 167 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F" 168 }, 169 { 170 .name = "A2x", 171 .mled_set = "MLED", 172 .wled_set = "WLED", 173 .lcd_get = "\\BAOF", 174 .lcd_set = "\\Q10", 175 .brn_get = "GPLV", 176 .brn_set = "SPLV", 177 .disp_get = "\\INFB", 178 .disp_set = "SDSP" 179 }, 180 { 181 .name = "A3E", 182 .mled_set = "MLED", 183 .wled_set = "WLED", 184 .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x67)", 185 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 186 .brn_get = "GPLV", 187 .brn_set = "SPLV", 188 .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", 189 .disp_set = "SDSP" 190 }, 191 { 192 .name = "A3F", 193 .mled_set = "MLED", 194 .wled_set = "WLED", 195 .bled_set = "BLED", 196 .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x11)", 197 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 198 .brn_get = "GPLV", 199 .brn_set = "SPLV", 200 .disp_get = "\\SSTE", 201 .disp_set = "SDSP" 202 }, 203 { 204 .name = "A3N", 205 .mled_set = "MLED", 206 .bled_set = "BLED", 207 .wled_set = "WLED", 208 .lcd_get = "\\BKLT", 209 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 210 .brn_get = "GPLV", 211 .brn_set = "SPLV", 212 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", 213 .disp_set = "SDSP" 214 }, 215 { 216 .name = "A4D", 217 .mled_set = "MLED", 218 .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E", 219 .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F", 220 .brn_get = "GPLV", 221 .brn_set = "SPLV", 222 #ifdef notyet 223 .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10", 224 .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11" 225 #endif 226 }, 227 { 228 .name = "A6V", 229 .bled_set = "BLED", 230 .mled_set = "MLED", 231 .wled_set = "WLED", 232 .lcd_get = NULL, 233 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 234 .brn_get = "GPLV", 235 .brn_set = "SPLV", 236 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", 237 .disp_set = "SDSP" 238 }, 239 { 240 .name = "A8SR", 241 .bled_set = "BLED", 242 .mled_set = "MLED", 243 .wled_set = "WLED", 244 .lcd_get = NULL, 245 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 246 .brn_get = "GPLV", 247 .brn_set = "SPLV", 248 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", 249 .disp_set = "SDSP", 250 .lcdd = "\\_SB.PCI0.P0P1.VGA.LCDD", 251 .lcdd_n_func = acpi_asus_lcdd_notify 252 }, 253 { 254 .name = "D1x", 255 .mled_set = "MLED", 256 .lcd_get = "\\GP11", 257 .lcd_set = "\\Q0D", 258 .brn_up = "\\Q0C", 259 .brn_dn = "\\Q0B", 260 .disp_get = "\\INFB", 261 .disp_set = "SDSP" 262 }, 263 { 264 .name = "G2K", 265 .bled_set = "BLED", 266 .dled_set = "DLED", 267 .gled_set = "GLED", 268 .mled_set = "MLED", 269 .tled_set = "TLED", 270 .wled_set = "WLED", 271 .brn_get = "GPLV", 272 .brn_set = "SPLV", 273 .lcd_get = "GBTL", 274 .lcd_set = "SBTL", 275 .disp_get = "\\_SB.PCI0.PCE2.VGA.GETD", 276 .disp_set = "SDSP", 277 }, 278 { 279 .name = "L2D", 280 .mled_set = "MLED", 281 .wled_set = "WLED", 282 .brn_up = "\\Q0E", 283 .brn_dn = "\\Q0F", 284 .lcd_get = "\\SGP0", 285 .lcd_set = "\\Q10" 286 }, 287 { 288 .name = "L3C", 289 .mled_set = "MLED", 290 .wled_set = "WLED", 291 .brn_get = "GPLV", 292 .brn_set = "SPLV", 293 .lcd_get = "\\GL32", 294 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10" 295 }, 296 { 297 .name = "L3D", 298 .mled_set = "MLED", 299 .wled_set = "WLED", 300 .brn_get = "GPLV", 301 .brn_set = "SPLV", 302 .lcd_get = "\\BKLG", 303 .lcd_set = "\\Q10" 304 }, 305 { 306 .name = "L3H", 307 .mled_set = "MLED", 308 .wled_set = "WLED", 309 .brn_get = "GPLV", 310 .brn_set = "SPLV", 311 .lcd_get = "\\_SB.PCI0.PM.PBC", 312 .lcd_set = "EHK", 313 .disp_get = "\\_SB.INFB", 314 .disp_set = "SDSP" 315 }, 316 { 317 .name = "L4R", 318 .mled_set = "MLED", 319 .wled_set = "WLED", 320 .brn_get = "GPLV", 321 .brn_set = "SPLV", 322 .lcd_get = "\\_SB.PCI0.SBSM.SEO4", 323 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 324 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", 325 .disp_set = "SDSP" 326 }, 327 { 328 .name = "L5x", 329 .mled_set = "MLED", 330 .tled_set = "TLED", 331 .lcd_get = "\\BAOF", 332 .lcd_set = "\\Q0D", 333 .brn_get = "GPLV", 334 .brn_set = "SPLV", 335 .disp_get = "\\INFB", 336 .disp_set = "SDSP" 337 }, 338 { 339 .name = "L8L" 340 /* Only has hotkeys, apparently */ 341 }, 342 { 343 .name = "M1A", 344 .mled_set = "MLED", 345 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E", 346 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F", 347 .lcd_get = "\\PNOF", 348 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10" 349 }, 350 { 351 .name = "M2E", 352 .mled_set = "MLED", 353 .wled_set = "WLED", 354 .brn_get = "GPLV", 355 .brn_set = "SPLV", 356 .lcd_get = "\\GP06", 357 .lcd_set = "\\Q10" 358 }, 359 { 360 .name = "M6N", 361 .mled_set = "MLED", 362 .wled_set = "WLED", 363 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 364 .lcd_get = "\\_SB.BKLT", 365 .brn_set = "SPLV", 366 .brn_get = "GPLV", 367 .disp_set = "SDSP", 368 .disp_get = "\\SSTE" 369 }, 370 { 371 .name = "M6R", 372 .mled_set = "MLED", 373 .wled_set = "WLED", 374 .brn_get = "GPLV", 375 .brn_set = "SPLV", 376 .lcd_get = "\\_SB.PCI0.SBSM.SEO4", 377 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 378 .disp_get = "\\SSTE", 379 .disp_set = "SDSP" 380 }, 381 { 382 .name = "S1x", 383 .mled_set = "MLED", 384 .wled_set = "WLED", 385 .lcd_get = "\\PNOF", 386 .lcd_set = "\\_SB.PCI0.PX40.Q10", 387 .brn_get = "GPLV", 388 .brn_set = "SPLV" 389 }, 390 { 391 .name = "S2x", 392 .mled_set = "MLED", 393 .lcd_get = "\\BKLI", 394 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", 395 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B", 396 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A" 397 }, 398 { 399 .name = "V6V", 400 .bled_set = "BLED", 401 .tled_set = "TLED", 402 .wled_set = "WLED", 403 .lcd_get = "\\BKLT", 404 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 405 .brn_get = "GPLV", 406 .brn_set = "SPLV", 407 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", 408 .disp_set = "SDSP" 409 }, 410 { 411 .name = "W5A", 412 .bled_set = "BLED", 413 .lcd_get = "\\BKLT", 414 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 415 .brn_get = "GPLV", 416 .brn_set = "SPLV", 417 .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", 418 .disp_set = "SDSP" 419 }, 420 421 { .name = NULL } 422 }; 423 424 /* 425 * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface, 426 * but they can't be probed quite the same way as Asus laptops. 427 */ 428 static struct acpi_asus_model acpi_samsung_models[] = { 429 { 430 .name = "P30", 431 .wled_set = "WLED", 432 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68", 433 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69", 434 .lcd_get = "\\BKLT", 435 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E" 436 }, 437 438 { .name = NULL } 439 }; 440 441 static void acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context); 442 443 /* 444 * EeePC have an Asus ASUS010 gadget interface, 445 * but they can't be probed quite the same way as Asus laptops. 446 */ 447 static struct acpi_asus_model acpi_eeepc_models[] = { 448 { 449 .name = "EEE", 450 .brn_get = "\\_SB.ATKD.PBLG", 451 .brn_set = "\\_SB.ATKD.PBLS", 452 .cam_get = "\\_SB.ATKD.CAMG", 453 .cam_set = "\\_SB.ATKD.CAMS", 454 .crd_set = "\\_SB.ATKD.CRDS", 455 .crd_get = "\\_SB.ATKD.CRDG", 456 .wlan_get = "\\_SB.ATKD.WLDG", 457 .wlan_set = "\\_SB.ATKD.WLDS", 458 .n_func = acpi_asus_eeepc_notify 459 }, 460 461 { .name = NULL } 462 }; 463 464 static struct { 465 char *name; 466 char *description; 467 int method; 468 int flag_anybody; 469 } acpi_asus_sysctls[] = { 470 { 471 .name = "lcd_backlight", 472 .method = ACPI_ASUS_METHOD_LCD, 473 .description = "state of the lcd backlight", 474 .flag_anybody = 1 475 }, 476 { 477 .name = "lcd_brightness", 478 .method = ACPI_ASUS_METHOD_BRN, 479 .description = "brightness of the lcd panel", 480 .flag_anybody = 1 481 }, 482 { 483 .name = "video_output", 484 .method = ACPI_ASUS_METHOD_DISP, 485 .description = "display output state", 486 }, 487 { 488 .name = "camera", 489 .method = ACPI_ASUS_METHOD_CAMERA, 490 .description = "internal camera state", 491 }, 492 { 493 .name = "cardreader", 494 .method = ACPI_ASUS_METHOD_CARDRD, 495 .description = "internal card reader state", 496 }, 497 { 498 .name = "wlan", 499 .method = ACPI_ASUS_METHOD_WLAN, 500 .description = "wireless lan state", 501 }, 502 503 { .name = NULL } 504 }; 505 506 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras"); 507 508 /* Function prototypes */ 509 static int acpi_asus_probe(device_t dev); 510 static int acpi_asus_attach(device_t dev); 511 static int acpi_asus_detach(device_t dev); 512 513 static void acpi_asus_led(struct acpi_asus_led *led, int state); 514 static void acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused); 515 516 static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS); 517 static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method); 518 static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method); 519 static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val); 520 521 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context); 522 523 static device_method_t acpi_asus_methods[] = { 524 DEVMETHOD(device_probe, acpi_asus_probe), 525 DEVMETHOD(device_attach, acpi_asus_attach), 526 DEVMETHOD(device_detach, acpi_asus_detach), 527 528 { 0, 0 } 529 }; 530 531 static driver_t acpi_asus_driver = { 532 "acpi_asus", 533 acpi_asus_methods, 534 sizeof(struct acpi_asus_softc) 535 }; 536 537 static devclass_t acpi_asus_devclass; 538 539 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0); 540 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1); 541 542 static int 543 acpi_asus_probe(device_t dev) 544 { 545 struct acpi_asus_model *model; 546 struct acpi_asus_softc *sc; 547 struct sbuf *sb; 548 ACPI_BUFFER Buf; 549 ACPI_OBJECT Arg, *Obj; 550 ACPI_OBJECT_LIST Args; 551 static char *asus_ids[] = { "ATK0100", "ASUS010", NULL }; 552 int rv; 553 char *rstr; 554 555 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 556 557 if (acpi_disabled("asus")) 558 return (ENXIO); 559 rv = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids, &rstr); 560 if (rv > 0) { 561 return (rv); 562 } 563 564 sc = device_get_softc(dev); 565 sc->dev = dev; 566 sc->handle = acpi_get_handle(dev); 567 568 Arg.Type = ACPI_TYPE_INTEGER; 569 Arg.Integer.Value = 0; 570 571 Args.Count = 1; 572 Args.Pointer = &Arg; 573 574 Buf.Pointer = NULL; 575 Buf.Length = ACPI_ALLOCATE_BUFFER; 576 577 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf); 578 Obj = Buf.Pointer; 579 580 /* 581 * The Samsung P30 returns a null-pointer from INIT, we 582 * can identify it from the 'ODEM' string in the DSDT. 583 */ 584 if (Obj->String.Pointer == NULL) { 585 ACPI_STATUS status; 586 ACPI_TABLE_HEADER th; 587 588 status = AcpiGetTableHeader(ACPI_SIG_DSDT, 0, &th); 589 if (ACPI_FAILURE(status)) { 590 device_printf(dev, "Unsupported (Samsung?) laptop\n"); 591 AcpiOsFree(Buf.Pointer); 592 return (ENXIO); 593 } 594 595 if (strncmp("ODEM", th.OemTableId, 4) == 0) { 596 sc->model = &acpi_samsung_models[0]; 597 device_set_desc(dev, "Samsung P30 Laptop Extras"); 598 AcpiOsFree(Buf.Pointer); 599 return (rv); 600 } 601 602 /* EeePC */ 603 if (strncmp("ASUS010", rstr, 7) == 0) { 604 sc->model = &acpi_eeepc_models[0]; 605 device_set_desc(dev, "ASUS EeePC"); 606 AcpiOsFree(Buf.Pointer); 607 return (rv); 608 } 609 } 610 611 sb = sbuf_new_auto(); 612 if (sb == NULL) 613 return (ENOMEM); 614 615 /* 616 * Asus laptops are simply identified by name, easy! 617 */ 618 for (model = acpi_asus_models; model->name != NULL; model++) { 619 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) { 620 621 good: 622 sbuf_printf(sb, "Asus %s Laptop Extras", 623 Obj->String.Pointer); 624 sbuf_finish(sb); 625 626 sc->model = model; 627 device_set_desc_copy(dev, sbuf_data(sb)); 628 629 sbuf_delete(sb); 630 AcpiOsFree(Buf.Pointer); 631 return (rv); 632 } 633 634 /* 635 * Some models look exactly the same as other models, but have 636 * their own ids. If we spot these, set them up with the same 637 * details as the models they're like, possibly dealing with 638 * small differences. 639 * 640 * XXX: there must be a prettier way to do this! 641 */ 642 else if (strncmp(model->name, "xxN", 3) == 0 && 643 (strncmp(Obj->String.Pointer, "M3N", 3) == 0 || 644 strncmp(Obj->String.Pointer, "S1N", 3) == 0)) 645 goto good; 646 else if (strncmp(model->name, "A1x", 3) == 0 && 647 strncmp(Obj->String.Pointer, "A1", 2) == 0) 648 goto good; 649 else if (strncmp(model->name, "A2x", 3) == 0 && 650 strncmp(Obj->String.Pointer, "A2", 2) == 0) 651 goto good; 652 else if (strncmp(model->name, "A3F", 3) == 0 && 653 strncmp(Obj->String.Pointer, "A6F", 3) == 0) 654 goto good; 655 else if (strncmp(model->name, "D1x", 3) == 0 && 656 strncmp(Obj->String.Pointer, "D1", 2) == 0) 657 goto good; 658 else if (strncmp(model->name, "L3H", 3) == 0 && 659 strncmp(Obj->String.Pointer, "L2E", 3) == 0) 660 goto good; 661 else if (strncmp(model->name, "L5x", 3) == 0 && 662 strncmp(Obj->String.Pointer, "L5", 2) == 0) 663 goto good; 664 else if (strncmp(model->name, "M2E", 3) == 0 && 665 (strncmp(Obj->String.Pointer, "M2", 2) == 0 || 666 strncmp(Obj->String.Pointer, "L4E", 3) == 0)) 667 goto good; 668 else if (strncmp(model->name, "S1x", 3) == 0 && 669 (strncmp(Obj->String.Pointer, "L8", 2) == 0 || 670 strncmp(Obj->String.Pointer, "S1", 2) == 0)) 671 goto good; 672 else if (strncmp(model->name, "S2x", 3) == 0 && 673 (strncmp(Obj->String.Pointer, "J1", 2) == 0 || 674 strncmp(Obj->String.Pointer, "S2", 2) == 0)) 675 goto good; 676 677 /* L2B is like L3C but has no lcd_get method */ 678 else if (strncmp(model->name, "L3C", 3) == 0 && 679 strncmp(Obj->String.Pointer, "L2B", 3) == 0) { 680 model->lcd_get = NULL; 681 goto good; 682 } 683 684 /* A3G is like M6R but with a different lcd_get method */ 685 else if (strncmp(model->name, "M6R", 3) == 0 && 686 strncmp(Obj->String.Pointer, "A3G", 3) == 0) { 687 model->lcd_get = "\\BLFG"; 688 goto good; 689 } 690 691 /* M2N and W1N are like xxN with added WLED */ 692 else if (strncmp(model->name, "xxN", 3) == 0 && 693 (strncmp(Obj->String.Pointer, "M2N", 3) == 0 || 694 strncmp(Obj->String.Pointer, "W1N", 3) == 0)) { 695 model->wled_set = "WLED"; 696 goto good; 697 } 698 699 /* M5N and S5N are like xxN without MLED */ 700 else if (strncmp(model->name, "xxN", 3) == 0 && 701 (strncmp(Obj->String.Pointer, "M5N", 3) == 0 || 702 strncmp(Obj->String.Pointer, "S5N", 3) == 0)) { 703 model->mled_set = NULL; 704 goto good; 705 } 706 } 707 708 sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer); 709 sbuf_finish(sb); 710 711 device_printf(dev, "%s", sbuf_data(sb)); 712 713 sbuf_delete(sb); 714 AcpiOsFree(Buf.Pointer); 715 716 return (ENXIO); 717 } 718 719 static int 720 acpi_asus_attach(device_t dev) 721 { 722 struct acpi_asus_softc *sc; 723 struct acpi_softc *acpi_sc; 724 725 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 726 727 sc = device_get_softc(dev); 728 acpi_sc = acpi_device_get_parent_softc(dev); 729 730 /* Build sysctl tree */ 731 sysctl_ctx_init(&sc->sysctl_ctx); 732 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 733 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), 734 OID_AUTO, "asus", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, ""); 735 736 /* Hook up nodes */ 737 for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) { 738 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method)) 739 continue; 740 741 if (acpi_asus_sysctls[i].flag_anybody != 0) { 742 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 743 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 744 acpi_asus_sysctls[i].name, 745 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | 746 CTLFLAG_NEEDGIANT, sc, i, acpi_asus_sysctl, "I", 747 acpi_asus_sysctls[i].description); 748 } else { 749 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 750 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 751 acpi_asus_sysctls[i].name, 752 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 753 sc, i, acpi_asus_sysctl, "I", 754 acpi_asus_sysctls[i].description); 755 } 756 } 757 758 /* Attach leds */ 759 if (sc->model->bled_set) { 760 sc->s_bled.busy = 0; 761 sc->s_bled.sc = sc; 762 sc->s_bled.type = ACPI_ASUS_LED_BLED; 763 sc->s_bled.cdev = 764 led_create_state((led_t *)acpi_asus_led, &sc->s_bled, 765 "bled", 1); 766 } 767 768 if (sc->model->dled_set) { 769 sc->s_dled.busy = 0; 770 sc->s_dled.sc = sc; 771 sc->s_dled.type = ACPI_ASUS_LED_DLED; 772 sc->s_dled.cdev = 773 led_create((led_t *)acpi_asus_led, &sc->s_dled, "dled"); 774 } 775 776 if (sc->model->gled_set) { 777 sc->s_gled.busy = 0; 778 sc->s_gled.sc = sc; 779 sc->s_gled.type = ACPI_ASUS_LED_GLED; 780 sc->s_gled.cdev = 781 led_create((led_t *)acpi_asus_led, &sc->s_gled, "gled"); 782 } 783 784 if (sc->model->mled_set) { 785 sc->s_mled.busy = 0; 786 sc->s_mled.sc = sc; 787 sc->s_mled.type = ACPI_ASUS_LED_MLED; 788 sc->s_mled.cdev = 789 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled"); 790 } 791 792 if (sc->model->tled_set) { 793 sc->s_tled.busy = 0; 794 sc->s_tled.sc = sc; 795 sc->s_tled.type = ACPI_ASUS_LED_TLED; 796 sc->s_tled.cdev = 797 led_create_state((led_t *)acpi_asus_led, &sc->s_tled, 798 "tled", 1); 799 } 800 801 if (sc->model->wled_set) { 802 sc->s_wled.busy = 0; 803 sc->s_wled.sc = sc; 804 sc->s_wled.type = ACPI_ASUS_LED_WLED; 805 sc->s_wled.cdev = 806 led_create_state((led_t *)acpi_asus_led, &sc->s_wled, 807 "wled", 1); 808 } 809 810 /* Activate hotkeys */ 811 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); 812 813 /* Handle notifies */ 814 if (sc->model->n_func == NULL) 815 sc->model->n_func = acpi_asus_notify; 816 817 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 818 sc->model->n_func, dev); 819 820 /* Find and hook the 'LCDD' object */ 821 if (sc->model->lcdd != NULL && sc->model->lcdd_n_func != NULL) { 822 ACPI_STATUS res; 823 824 sc->lcdd_handle = NULL; 825 res = AcpiGetHandle((sc->model->lcdd[0] == '\\' ? 826 NULL : sc->handle), sc->model->lcdd, &(sc->lcdd_handle)); 827 if (ACPI_SUCCESS(res)) { 828 AcpiInstallNotifyHandler((sc->lcdd_handle), 829 ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func, dev); 830 } else { 831 printf("%s: unable to find LCD device '%s'\n", 832 __func__, sc->model->lcdd); 833 } 834 } 835 836 return (0); 837 } 838 839 static int 840 acpi_asus_detach(device_t dev) 841 { 842 struct acpi_asus_softc *sc; 843 844 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 845 846 sc = device_get_softc(dev); 847 848 /* Turn the lights off */ 849 if (sc->model->bled_set) 850 led_destroy(sc->s_bled.cdev); 851 852 if (sc->model->dled_set) 853 led_destroy(sc->s_dled.cdev); 854 855 if (sc->model->gled_set) 856 led_destroy(sc->s_gled.cdev); 857 858 if (sc->model->mled_set) 859 led_destroy(sc->s_mled.cdev); 860 861 if (sc->model->tled_set) 862 led_destroy(sc->s_tled.cdev); 863 864 if (sc->model->wled_set) 865 led_destroy(sc->s_wled.cdev); 866 867 /* Remove notify handler */ 868 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 869 acpi_asus_notify); 870 871 if (sc->lcdd_handle) { 872 KASSERT(sc->model->lcdd_n_func != NULL, 873 ("model->lcdd_n_func is NULL, but lcdd_handle is non-zero")); 874 AcpiRemoveNotifyHandler((sc->lcdd_handle), 875 ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func); 876 } 877 878 /* Free sysctl tree */ 879 sysctl_ctx_free(&sc->sysctl_ctx); 880 881 return (0); 882 } 883 884 static void 885 acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused) 886 { 887 struct acpi_asus_softc *sc; 888 char *method; 889 int state; 890 891 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 892 893 sc = led->sc; 894 895 switch (led->type) { 896 case ACPI_ASUS_LED_BLED: 897 method = sc->model->bled_set; 898 state = led->state; 899 break; 900 case ACPI_ASUS_LED_DLED: 901 method = sc->model->dled_set; 902 state = led->state; 903 break; 904 case ACPI_ASUS_LED_GLED: 905 method = sc->model->gled_set; 906 state = led->state + 1; /* 1: off, 2: on */ 907 break; 908 case ACPI_ASUS_LED_MLED: 909 method = sc->model->mled_set; 910 state = !led->state; /* inverted */ 911 break; 912 case ACPI_ASUS_LED_TLED: 913 method = sc->model->tled_set; 914 state = led->state; 915 break; 916 case ACPI_ASUS_LED_WLED: 917 method = sc->model->wled_set; 918 state = led->state; 919 break; 920 default: 921 printf("acpi_asus_led: invalid LED type %d\n", 922 (int)led->type); 923 return; 924 } 925 926 acpi_SetInteger(sc->handle, method, state); 927 led->busy = 0; 928 } 929 930 static void 931 acpi_asus_led(struct acpi_asus_led *led, int state) 932 { 933 934 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 935 936 if (led->busy) 937 return; 938 939 led->busy = 1; 940 led->state = state; 941 942 AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led); 943 } 944 945 static int 946 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS) 947 { 948 struct acpi_asus_softc *sc; 949 int arg; 950 int error = 0; 951 int function; 952 int method; 953 954 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 955 956 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 957 function = oidp->oid_arg2; 958 method = acpi_asus_sysctls[function].method; 959 960 ACPI_SERIAL_BEGIN(asus); 961 arg = acpi_asus_sysctl_get(sc, method); 962 error = sysctl_handle_int(oidp, &arg, 0, req); 963 964 /* Sanity check */ 965 if (error != 0 || req->newptr == NULL) 966 goto out; 967 968 /* Update */ 969 error = acpi_asus_sysctl_set(sc, method, arg); 970 971 out: 972 ACPI_SERIAL_END(asus); 973 return (error); 974 } 975 976 static int 977 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method) 978 { 979 int val = 0; 980 981 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 982 ACPI_SERIAL_ASSERT(asus); 983 984 switch (method) { 985 case ACPI_ASUS_METHOD_BRN: 986 val = sc->s_brn; 987 break; 988 case ACPI_ASUS_METHOD_DISP: 989 val = sc->s_disp; 990 break; 991 case ACPI_ASUS_METHOD_LCD: 992 val = sc->s_lcd; 993 break; 994 case ACPI_ASUS_METHOD_CAMERA: 995 val = sc->s_cam; 996 break; 997 case ACPI_ASUS_METHOD_CARDRD: 998 val = sc->s_crd; 999 break; 1000 case ACPI_ASUS_METHOD_WLAN: 1001 val = sc->s_wlan; 1002 break; 1003 } 1004 1005 return (val); 1006 } 1007 1008 static int 1009 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg) 1010 { 1011 ACPI_STATUS status = AE_OK; 1012 ACPI_OBJECT_LIST acpiargs; 1013 ACPI_OBJECT acpiarg[1]; 1014 1015 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 1016 ACPI_SERIAL_ASSERT(asus); 1017 1018 acpiargs.Count = 1; 1019 acpiargs.Pointer = acpiarg; 1020 acpiarg[0].Type = ACPI_TYPE_INTEGER; 1021 acpiarg[0].Integer.Value = arg; 1022 1023 switch (method) { 1024 case ACPI_ASUS_METHOD_BRN: 1025 if (arg < 0 || arg > 15) 1026 return (EINVAL); 1027 1028 if (sc->model->brn_set) 1029 status = acpi_SetInteger(sc->handle, 1030 sc->model->brn_set, arg); 1031 else { 1032 while (arg != 0) { 1033 status = AcpiEvaluateObject(sc->handle, 1034 (arg > 0) ? sc->model->brn_up : 1035 sc->model->brn_dn, NULL, NULL); 1036 (arg > 0) ? arg-- : arg++; 1037 } 1038 } 1039 1040 if (ACPI_SUCCESS(status)) 1041 sc->s_brn = arg; 1042 1043 break; 1044 case ACPI_ASUS_METHOD_DISP: 1045 if (arg < 0 || arg > 7) 1046 return (EINVAL); 1047 1048 status = acpi_SetInteger(sc->handle, 1049 sc->model->disp_set, arg); 1050 1051 if (ACPI_SUCCESS(status)) 1052 sc->s_disp = arg; 1053 1054 break; 1055 case ACPI_ASUS_METHOD_LCD: 1056 if (arg < 0 || arg > 1) 1057 return (EINVAL); 1058 1059 if (strncmp(sc->model->name, "L3H", 3) != 0) 1060 status = AcpiEvaluateObject(sc->handle, 1061 sc->model->lcd_set, NULL, NULL); 1062 else 1063 status = acpi_SetInteger(sc->handle, 1064 sc->model->lcd_set, 0x7); 1065 1066 if (ACPI_SUCCESS(status)) 1067 sc->s_lcd = arg; 1068 1069 break; 1070 case ACPI_ASUS_METHOD_CAMERA: 1071 if (arg < 0 || arg > 1) 1072 return (EINVAL); 1073 1074 status = AcpiEvaluateObject(sc->handle, 1075 sc->model->cam_set, &acpiargs, NULL); 1076 1077 if (ACPI_SUCCESS(status)) 1078 sc->s_cam = arg; 1079 break; 1080 case ACPI_ASUS_METHOD_CARDRD: 1081 if (arg < 0 || arg > 1) 1082 return (EINVAL); 1083 1084 status = AcpiEvaluateObject(sc->handle, 1085 sc->model->crd_set, &acpiargs, NULL); 1086 1087 if (ACPI_SUCCESS(status)) 1088 sc->s_crd = arg; 1089 break; 1090 case ACPI_ASUS_METHOD_WLAN: 1091 if (arg < 0 || arg > 1) 1092 return (EINVAL); 1093 1094 status = AcpiEvaluateObject(sc->handle, 1095 sc->model->wlan_set, &acpiargs, NULL); 1096 1097 if (ACPI_SUCCESS(status)) 1098 sc->s_wlan = arg; 1099 break; 1100 } 1101 1102 return (0); 1103 } 1104 1105 static int 1106 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method) 1107 { 1108 ACPI_STATUS status; 1109 1110 switch (method) { 1111 case ACPI_ASUS_METHOD_BRN: 1112 if (sc->model->brn_get) { 1113 /* GPLV/SPLV models */ 1114 status = acpi_GetInteger(sc->handle, 1115 sc->model->brn_get, &sc->s_brn); 1116 if (ACPI_SUCCESS(status)) 1117 return (TRUE); 1118 } else if (sc->model->brn_up) { 1119 /* Relative models */ 1120 status = AcpiEvaluateObject(sc->handle, 1121 sc->model->brn_up, NULL, NULL); 1122 if (ACPI_FAILURE(status)) 1123 return (FALSE); 1124 1125 status = AcpiEvaluateObject(sc->handle, 1126 sc->model->brn_dn, NULL, NULL); 1127 if (ACPI_FAILURE(status)) 1128 return (FALSE); 1129 1130 return (TRUE); 1131 } 1132 return (FALSE); 1133 case ACPI_ASUS_METHOD_DISP: 1134 if (sc->model->disp_get) { 1135 status = acpi_GetInteger(sc->handle, 1136 sc->model->disp_get, &sc->s_disp); 1137 if (ACPI_SUCCESS(status)) 1138 return (TRUE); 1139 } 1140 return (FALSE); 1141 case ACPI_ASUS_METHOD_LCD: 1142 if (sc->model->lcd_get) { 1143 if (strncmp(sc->model->name, "L3H", 3) == 0) { 1144 ACPI_BUFFER Buf; 1145 ACPI_OBJECT Arg[2], Obj; 1146 ACPI_OBJECT_LIST Args; 1147 1148 /* L3H is a bit special */ 1149 Arg[0].Type = ACPI_TYPE_INTEGER; 1150 Arg[0].Integer.Value = 0x02; 1151 Arg[1].Type = ACPI_TYPE_INTEGER; 1152 Arg[1].Integer.Value = 0x03; 1153 1154 Args.Count = 2; 1155 Args.Pointer = Arg; 1156 1157 Buf.Length = sizeof(Obj); 1158 Buf.Pointer = &Obj; 1159 1160 status = AcpiEvaluateObject(sc->handle, 1161 sc->model->lcd_get, &Args, &Buf); 1162 if (ACPI_SUCCESS(status) && 1163 Obj.Type == ACPI_TYPE_INTEGER) { 1164 sc->s_lcd = Obj.Integer.Value >> 8; 1165 return (TRUE); 1166 } 1167 } else { 1168 status = acpi_GetInteger(sc->handle, 1169 sc->model->lcd_get, &sc->s_lcd); 1170 if (ACPI_SUCCESS(status)) 1171 return (TRUE); 1172 } 1173 } 1174 return (FALSE); 1175 case ACPI_ASUS_METHOD_CAMERA: 1176 if (sc->model->cam_get) { 1177 status = acpi_GetInteger(sc->handle, 1178 sc->model->cam_get, &sc->s_cam); 1179 if (ACPI_SUCCESS(status)) 1180 return (TRUE); 1181 } 1182 return (FALSE); 1183 case ACPI_ASUS_METHOD_CARDRD: 1184 if (sc->model->crd_get) { 1185 status = acpi_GetInteger(sc->handle, 1186 sc->model->crd_get, &sc->s_crd); 1187 if (ACPI_SUCCESS(status)) 1188 return (TRUE); 1189 } 1190 return (FALSE); 1191 case ACPI_ASUS_METHOD_WLAN: 1192 if (sc->model->wlan_get) { 1193 status = acpi_GetInteger(sc->handle, 1194 sc->model->wlan_get, &sc->s_wlan); 1195 if (ACPI_SUCCESS(status)) 1196 return (TRUE); 1197 } 1198 return (FALSE); 1199 } 1200 return (FALSE); 1201 } 1202 1203 static void 1204 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) 1205 { 1206 struct acpi_asus_softc *sc; 1207 struct acpi_softc *acpi_sc; 1208 1209 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 1210 1211 sc = device_get_softc((device_t)context); 1212 acpi_sc = acpi_device_get_parent_softc(sc->dev); 1213 1214 ACPI_SERIAL_BEGIN(asus); 1215 if ((notify & ~0x10) <= 15) { 1216 sc->s_brn = notify & ~0x10; 1217 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 1218 } else if ((notify & ~0x20) <= 15) { 1219 sc->s_brn = notify & ~0x20; 1220 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 1221 } else if (notify == 0x33) { 1222 sc->s_lcd = 1; 1223 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); 1224 } else if (notify == 0x34) { 1225 sc->s_lcd = 0; 1226 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); 1227 } else if (notify == 0x86) { 1228 acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); 1229 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 1230 } else if (notify == 0x87) { 1231 acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); 1232 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 1233 } else { 1234 /* Notify devd(8) */ 1235 acpi_UserNotify("ASUS", h, notify); 1236 } 1237 ACPI_SERIAL_END(asus); 1238 } 1239 1240 static void 1241 acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, void *context) 1242 { 1243 struct acpi_asus_softc *sc; 1244 struct acpi_softc *acpi_sc; 1245 1246 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 1247 1248 sc = device_get_softc((device_t)context); 1249 acpi_sc = acpi_device_get_parent_softc(sc->dev); 1250 1251 ACPI_SERIAL_BEGIN(asus); 1252 switch (notify) { 1253 case 0x87: 1254 acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); 1255 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 1256 break; 1257 case 0x86: 1258 acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); 1259 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 1260 break; 1261 } 1262 ACPI_SERIAL_END(asus); 1263 } 1264 1265 static void 1266 acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context) 1267 { 1268 struct acpi_asus_softc *sc; 1269 struct acpi_softc *acpi_sc; 1270 1271 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 1272 1273 sc = device_get_softc((device_t)context); 1274 acpi_sc = acpi_device_get_parent_softc(sc->dev); 1275 1276 ACPI_SERIAL_BEGIN(asus); 1277 if ((notify & ~0x20) <= 15) { 1278 sc->s_brn = notify & ~0x20; 1279 ACPI_VPRINT(sc->dev, acpi_sc, 1280 "Brightness increased/decreased\n"); 1281 } else { 1282 /* Notify devd(8) */ 1283 acpi_UserNotify("ASUS-Eee", h, notify); 1284 } 1285 ACPI_SERIAL_END(asus); 1286 } 1287