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