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 59 #define _COMPONENT ACPI_OEM 60 ACPI_MODULE_NAME("ASUS") 61 62 struct acpi_asus_model { 63 char *name; 64 65 char *bled_set; 66 char *mled_set; 67 char *tled_set; 68 char *wled_set; 69 70 char *brn_get; 71 char *brn_set; 72 char *brn_up; 73 char *brn_dn; 74 75 char *lcd_get; 76 char *lcd_set; 77 78 char *disp_get; 79 char *disp_set; 80 }; 81 82 struct acpi_asus_led { 83 struct acpi_asus_softc *sc; 84 struct cdev *cdev; 85 int busy; 86 int state; 87 enum { 88 ACPI_ASUS_LED_BLED, 89 ACPI_ASUS_LED_MLED, 90 ACPI_ASUS_LED_TLED, 91 ACPI_ASUS_LED_WLED, 92 } type; 93 }; 94 95 struct acpi_asus_softc { 96 device_t dev; 97 ACPI_HANDLE handle; 98 99 struct acpi_asus_model *model; 100 struct sysctl_ctx_list sysctl_ctx; 101 struct sysctl_oid *sysctl_tree; 102 103 struct acpi_asus_led s_bled; 104 struct acpi_asus_led s_mled; 105 struct acpi_asus_led s_tled; 106 struct acpi_asus_led s_wled; 107 108 int s_brn; 109 int s_disp; 110 int s_lcd; 111 }; 112 113 /* 114 * We can identify Asus laptops from the string they return 115 * as a result of calling the ATK0100 'INIT' method. 116 */ 117 static struct acpi_asus_model acpi_asus_models[] = { 118 { 119 .name = "xxN", 120 .mled_set = "MLED", 121 .wled_set = "WLED", 122 .lcd_get = "\\BKLT", 123 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 124 .brn_get = "GPLV", 125 .brn_set = "SPLV", 126 .disp_get = "\\ADVG", 127 .disp_set = "SDSP" 128 }, 129 { 130 .name = "A1x", 131 .mled_set = "MLED", 132 .lcd_get = "\\BKLI", 133 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", 134 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E", 135 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F" 136 }, 137 { 138 .name = "A2x", 139 .mled_set = "MLED", 140 .wled_set = "WLED", 141 .lcd_get = "\\BAOF", 142 .lcd_set = "\\Q10", 143 .brn_get = "GPLV", 144 .brn_set = "SPLV", 145 .disp_get = "\\INFB", 146 .disp_set = "SDSP" 147 }, 148 { 149 .name = "A3N", 150 .mled_set = "MLED", 151 .bled_set = "BLED", 152 .wled_set = "WLED", 153 .lcd_get = NULL, 154 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 155 .brn_set = "SPLV", 156 .brn_get = "SDSP", 157 .disp_set = "SDSP", 158 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD" 159 }, 160 { 161 .name = "A4D", 162 .mled_set = "MLED", 163 .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E", 164 .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F", 165 .brn_get = "GPLV", 166 .brn_set = "SPLV", 167 #ifdef notyet 168 .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10", 169 .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11" 170 #endif 171 }, 172 { 173 .name = "A6V", 174 .bled_set = "BLED", 175 .mled_set = "MLED", 176 .wled_set = "WLED", 177 .lcd_get = NULL, 178 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 179 .brn_get = "GPLV", 180 .brn_set = "SPLV", 181 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", 182 .disp_set = "SDSP" 183 }, 184 { 185 .name = "D1x", 186 .mled_set = "MLED", 187 .lcd_get = "\\GP11", 188 .lcd_set = "\\Q0D", 189 .brn_up = "\\Q0C", 190 .brn_dn = "\\Q0B", 191 .disp_get = "\\INFB", 192 .disp_set = "SDSP" 193 }, 194 { 195 .name = "L2D", 196 .mled_set = "MLED", 197 .wled_set = "WLED", 198 .brn_up = "\\Q0E", 199 .brn_dn = "\\Q0F", 200 .lcd_get = "\\SGP0", 201 .lcd_set = "\\Q10" 202 }, 203 { 204 .name = "L3C", 205 .mled_set = "MLED", 206 .wled_set = "WLED", 207 .brn_get = "GPLV", 208 .brn_set = "SPLV", 209 .lcd_get = "\\GL32", 210 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10" 211 }, 212 { 213 .name = "L3D", 214 .mled_set = "MLED", 215 .wled_set = "WLED", 216 .brn_get = "GPLV", 217 .brn_set = "SPLV", 218 .lcd_get = "\\BKLG", 219 .lcd_set = "\\Q10" 220 }, 221 { 222 .name = "L3H", 223 .mled_set = "MLED", 224 .wled_set = "WLED", 225 .brn_get = "GPLV", 226 .brn_set = "SPLV", 227 .lcd_get = "\\_SB.PCI0.PM.PBC", 228 .lcd_set = "EHK", 229 .disp_get = "\\_SB.INFB", 230 .disp_set = "SDSP" 231 }, 232 { 233 .name = "L4R", 234 .mled_set = "MLED", 235 .wled_set = "WLED", 236 .brn_get = "GPLV", 237 .brn_set = "SPLV", 238 .lcd_get = "\\_SB.PCI0.SBSM.SEO4", 239 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 240 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", 241 .disp_set = "SDSP" 242 }, 243 { 244 .name = "L5x", 245 .mled_set = "MLED", 246 .tled_set = "TLED", 247 .lcd_get = "\\BAOF", 248 .lcd_set = "\\Q0D", 249 .brn_get = "GPLV", 250 .brn_set = "SPLV", 251 .disp_get = "\\INFB", 252 .disp_set = "SDSP" 253 }, 254 { 255 .name = "L8L" 256 /* Only has hotkeys, apparantly */ 257 }, 258 { 259 .name = "M1A", 260 .mled_set = "MLED", 261 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E", 262 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F", 263 .lcd_get = "\\PNOF", 264 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10" 265 }, 266 { 267 .name = "M2E", 268 .mled_set = "MLED", 269 .wled_set = "WLED", 270 .brn_get = "GPLV", 271 .brn_set = "SPLV", 272 .lcd_get = "\\GP06", 273 .lcd_set = "\\Q10" 274 }, 275 { 276 .name = "M6N", 277 .mled_set = "MLED", 278 .wled_set = "WLED", 279 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 280 .lcd_get = "\\_SB.BKLT", 281 .brn_set = "SPLV", 282 .brn_get = "GPLV", 283 .disp_set = "SDSP", 284 .disp_get = "\\SSTE" 285 }, 286 { 287 .name = "M6R", 288 .mled_set = "MLED", 289 .wled_set = "WLED", 290 .brn_get = "GPLV", 291 .brn_set = "SPLV", 292 .lcd_get = "\\_SB.PCI0.SBSM.SEO4", 293 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 294 .disp_get = "\\SSTE", 295 .disp_set = "SDSP" 296 }, 297 { 298 .name = "S1x", 299 .mled_set = "MLED", 300 .wled_set = "WLED", 301 .lcd_get = "\\PNOF", 302 .lcd_set = "\\_SB.PCI0.PX40.Q10", 303 .brn_get = "GPLV", 304 .brn_set = "SPLV" 305 }, 306 { 307 .name = "S2x", 308 .mled_set = "MLED", 309 .lcd_get = "\\BKLI", 310 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", 311 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B", 312 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A" 313 }, 314 { 315 .name = "V6V", 316 .bled_set = "BLED", 317 .tled_set = "TLED", 318 .wled_set = "WLED", 319 .lcd_get = "\\BKLT", 320 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 321 .brn_get = "GPLV", 322 .brn_set = "SPLV", 323 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", 324 .disp_set = "SDSP" 325 }, 326 { 327 .name = "W5A", 328 .bled_set = "BLED", 329 .lcd_get = "\\BKLT", 330 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", 331 .brn_get = "GPLV", 332 .brn_set = "SPLV", 333 .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", 334 .disp_set = "SDSP" 335 }, 336 337 { .name = NULL } 338 }; 339 340 /* 341 * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface, 342 * but they can't be probed quite the same way as Asus laptops. 343 */ 344 static struct acpi_asus_model acpi_samsung_models[] = { 345 { 346 .name = "P30", 347 .wled_set = "WLED", 348 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68", 349 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69", 350 .lcd_get = "\\BKLT", 351 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E" 352 }, 353 354 { .name = NULL } 355 }; 356 357 static struct { 358 char *name; 359 char *description; 360 int method; 361 } acpi_asus_sysctls[] = { 362 { 363 .name = "lcd_backlight", 364 .method = ACPI_ASUS_METHOD_LCD, 365 .description = "state of the lcd backlight" 366 }, 367 { 368 .name = "lcd_brightness", 369 .method = ACPI_ASUS_METHOD_BRN, 370 .description = "brightness of the lcd panel" 371 }, 372 { 373 .name = "video_output", 374 .method = ACPI_ASUS_METHOD_DISP, 375 .description = "display output state" 376 }, 377 378 { .name = NULL } 379 }; 380 381 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras"); 382 383 /* Function prototypes */ 384 static int acpi_asus_probe(device_t dev); 385 static int acpi_asus_attach(device_t dev); 386 static int acpi_asus_detach(device_t dev); 387 388 static void acpi_asus_led(struct acpi_asus_led *led, int state); 389 static void acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused); 390 391 static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS); 392 static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method); 393 static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method); 394 static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val); 395 396 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context); 397 398 static device_method_t acpi_asus_methods[] = { 399 DEVMETHOD(device_probe, acpi_asus_probe), 400 DEVMETHOD(device_attach, acpi_asus_attach), 401 DEVMETHOD(device_detach, acpi_asus_detach), 402 403 { 0, 0 } 404 }; 405 406 static driver_t acpi_asus_driver = { 407 "acpi_asus", 408 acpi_asus_methods, 409 sizeof(struct acpi_asus_softc) 410 }; 411 412 static devclass_t acpi_asus_devclass; 413 414 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0); 415 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1); 416 417 static int 418 acpi_asus_probe(device_t dev) 419 { 420 struct acpi_asus_model *model; 421 struct acpi_asus_softc *sc; 422 struct sbuf *sb; 423 ACPI_BUFFER Buf; 424 ACPI_OBJECT Arg, *Obj; 425 ACPI_OBJECT_LIST Args; 426 static char *asus_ids[] = { "ATK0100", NULL }; 427 428 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 429 430 if (acpi_disabled("asus") || 431 ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids) == NULL) 432 return (ENXIO); 433 434 sc = device_get_softc(dev); 435 sc->dev = dev; 436 sc->handle = acpi_get_handle(dev); 437 438 Arg.Type = ACPI_TYPE_INTEGER; 439 Arg.Integer.Value = 0; 440 441 Args.Count = 1; 442 Args.Pointer = &Arg; 443 444 Buf.Pointer = NULL; 445 Buf.Length = ACPI_ALLOCATE_BUFFER; 446 447 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf); 448 Obj = Buf.Pointer; 449 450 /* 451 * The Samsung P30 returns a null-pointer from INIT, we 452 * can identify it from the 'ODEM' string in the DSDT. 453 */ 454 if (Obj->String.Pointer == NULL) { 455 ACPI_STATUS status; 456 ACPI_TABLE_HEADER th; 457 458 status = AcpiGetTableHeader(ACPI_SIG_DSDT, 0, &th); 459 if (ACPI_FAILURE(status)) { 460 device_printf(dev, "Unsupported (Samsung?) laptop\n"); 461 AcpiOsFree(Buf.Pointer); 462 return (ENXIO); 463 } 464 465 if (strncmp("ODEM", th.OemTableId, 4) == 0) { 466 sc->model = &acpi_samsung_models[0]; 467 device_set_desc(dev, "Samsung P30 Laptop Extras"); 468 AcpiOsFree(Buf.Pointer); 469 return (0); 470 } 471 } 472 473 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND); 474 if (sb == NULL) 475 return (ENOMEM); 476 477 /* 478 * Asus laptops are simply identified by name, easy! 479 */ 480 for (model = acpi_asus_models; model->name != NULL; model++) { 481 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) { 482 483 good: 484 sbuf_printf(sb, "Asus %s Laptop Extras", 485 Obj->String.Pointer); 486 sbuf_finish(sb); 487 488 sc->model = model; 489 device_set_desc_copy(dev, sbuf_data(sb)); 490 491 sbuf_delete(sb); 492 AcpiOsFree(Buf.Pointer); 493 return (0); 494 } 495 496 /* 497 * Some models look exactly the same as other models, but have 498 * their own ids. If we spot these, set them up with the same 499 * details as the models they're like, possibly dealing with 500 * small differences. 501 * 502 * XXX: there must be a prettier way to do this! 503 */ 504 else if (strncmp(model->name, "xxN", 3) == 0 && 505 (strncmp(Obj->String.Pointer, "M3N", 3) == 0 || 506 strncmp(Obj->String.Pointer, "S1N", 3) == 0)) 507 goto good; 508 else if (strncmp(model->name, "A1x", 3) == 0 && 509 strncmp(Obj->String.Pointer, "A1", 2) == 0) 510 goto good; 511 else if (strncmp(model->name, "A2x", 3) == 0 && 512 strncmp(Obj->String.Pointer, "A2", 2) == 0) 513 goto good; 514 else if (strncmp(model->name, "D1x", 3) == 0 && 515 strncmp(Obj->String.Pointer, "D1", 2) == 0) 516 goto good; 517 else if (strncmp(model->name, "L3H", 3) == 0 && 518 strncmp(Obj->String.Pointer, "L2E", 3) == 0) 519 goto good; 520 else if (strncmp(model->name, "L5x", 3) == 0 && 521 strncmp(Obj->String.Pointer, "L5", 2) == 0) 522 goto good; 523 else if (strncmp(model->name, "M2E", 3) == 0 && 524 (strncmp(Obj->String.Pointer, "M2", 2) == 0 || 525 strncmp(Obj->String.Pointer, "L4E", 3) == 0)) 526 goto good; 527 else if (strncmp(model->name, "S1x", 3) == 0 && 528 (strncmp(Obj->String.Pointer, "L8", 2) == 0 || 529 strncmp(Obj->String.Pointer, "S1", 2) == 0)) 530 goto good; 531 else if (strncmp(model->name, "S2x", 3) == 0 && 532 (strncmp(Obj->String.Pointer, "J1", 2) == 0 || 533 strncmp(Obj->String.Pointer, "S2", 2) == 0)) 534 goto good; 535 536 /* L2B is like L3C but has no lcd_get method */ 537 else if (strncmp(model->name, "L3C", 3) == 0 && 538 strncmp(Obj->String.Pointer, "L2B", 3) == 0) { 539 model->lcd_get = NULL; 540 goto good; 541 } 542 543 /* A3G is like M6R but with a different lcd_get method */ 544 else if (strncmp(model->name, "M6R", 3) == 0 && 545 strncmp(Obj->String.Pointer, "A3G", 3) == 0) { 546 model->lcd_get = "\\BLFG"; 547 goto good; 548 } 549 550 /* M2N and W1N are like xxN with added WLED */ 551 else if (strncmp(model->name, "xxN", 3) == 0 && 552 (strncmp(Obj->String.Pointer, "M2N", 3) == 0 || 553 strncmp(Obj->String.Pointer, "W1N", 3) == 0)) { 554 model->wled_set = "WLED"; 555 goto good; 556 } 557 558 /* M5N and S5N are like xxN without MLED */ 559 else if (strncmp(model->name, "xxN", 3) == 0 && 560 (strncmp(Obj->String.Pointer, "M5N", 3) == 0 || 561 strncmp(Obj->String.Pointer, "S5N", 3) == 0)) { 562 model->mled_set = NULL; 563 goto good; 564 } 565 } 566 567 sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer); 568 sbuf_finish(sb); 569 570 device_printf(dev, sbuf_data(sb)); 571 572 sbuf_delete(sb); 573 AcpiOsFree(Buf.Pointer); 574 575 return (ENXIO); 576 } 577 578 static int 579 acpi_asus_attach(device_t dev) 580 { 581 struct acpi_asus_softc *sc; 582 struct acpi_softc *acpi_sc; 583 584 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 585 586 sc = device_get_softc(dev); 587 acpi_sc = acpi_device_get_parent_softc(dev); 588 589 /* Build sysctl tree */ 590 sysctl_ctx_init(&sc->sysctl_ctx); 591 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 592 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), 593 OID_AUTO, "asus", CTLFLAG_RD, 0, ""); 594 595 /* Hook up nodes */ 596 for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) { 597 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method)) 598 continue; 599 600 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 601 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 602 acpi_asus_sysctls[i].name, 603 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, 604 sc, i, acpi_asus_sysctl, "I", 605 acpi_asus_sysctls[i].description); 606 } 607 608 /* Attach leds */ 609 if (sc->model->bled_set) { 610 sc->s_bled.busy = 0; 611 sc->s_bled.sc = sc; 612 sc->s_bled.type = ACPI_ASUS_LED_BLED; 613 sc->s_bled.cdev = 614 led_create((led_t *)acpi_asus_led, &sc->s_bled, "bled"); 615 } 616 617 if (sc->model->mled_set) { 618 sc->s_mled.busy = 0; 619 sc->s_mled.sc = sc; 620 sc->s_mled.type = ACPI_ASUS_LED_MLED; 621 sc->s_mled.cdev = 622 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled"); 623 } 624 625 if (sc->model->tled_set) { 626 sc->s_tled.busy = 0; 627 sc->s_tled.sc = sc; 628 sc->s_tled.type = ACPI_ASUS_LED_TLED; 629 sc->s_tled.cdev = 630 led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled"); 631 } 632 633 if (sc->model->wled_set) { 634 sc->s_wled.busy = 0; 635 sc->s_wled.sc = sc; 636 sc->s_wled.type = ACPI_ASUS_LED_WLED; 637 sc->s_wled.cdev = 638 led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled"); 639 } 640 641 /* Activate hotkeys */ 642 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); 643 644 /* Handle notifies */ 645 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 646 acpi_asus_notify, dev); 647 648 return (0); 649 } 650 651 static int 652 acpi_asus_detach(device_t dev) 653 { 654 struct acpi_asus_softc *sc; 655 656 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 657 658 sc = device_get_softc(dev); 659 660 /* Turn the lights off */ 661 if (sc->model->bled_set) 662 led_destroy(sc->s_bled.cdev); 663 664 if (sc->model->mled_set) 665 led_destroy(sc->s_mled.cdev); 666 667 if (sc->model->tled_set) 668 led_destroy(sc->s_tled.cdev); 669 670 if (sc->model->wled_set) 671 led_destroy(sc->s_wled.cdev); 672 673 /* Remove notify handler */ 674 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 675 acpi_asus_notify); 676 677 /* Free sysctl tree */ 678 sysctl_ctx_free(&sc->sysctl_ctx); 679 680 return (0); 681 } 682 683 static void 684 acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused) 685 { 686 struct acpi_asus_softc *sc; 687 char *method; 688 int state; 689 690 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 691 692 sc = led->sc; 693 694 switch (led->type) { 695 case ACPI_ASUS_LED_BLED: 696 method = sc->model->bled_set; 697 state = led->state; 698 break; 699 case ACPI_ASUS_LED_MLED: 700 method = sc->model->mled_set; 701 702 /* Note: inverted */ 703 state = !led->state; 704 break; 705 case ACPI_ASUS_LED_TLED: 706 method = sc->model->tled_set; 707 state = led->state; 708 break; 709 case ACPI_ASUS_LED_WLED: 710 method = sc->model->wled_set; 711 state = led->state; 712 break; 713 default: 714 printf("acpi_asus_led: invalid LED type %d\n", 715 (int)led->type); 716 return; 717 } 718 719 acpi_SetInteger(sc->handle, method, state); 720 led->busy = 0; 721 } 722 723 static void 724 acpi_asus_led(struct acpi_asus_led *led, int state) 725 { 726 727 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 728 729 if (led->busy) 730 return; 731 732 led->busy = 1; 733 led->state = state; 734 735 AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led); 736 } 737 738 static int 739 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS) 740 { 741 struct acpi_asus_softc *sc; 742 int arg; 743 int error = 0; 744 int function; 745 int method; 746 747 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 748 749 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 750 function = oidp->oid_arg2; 751 method = acpi_asus_sysctls[function].method; 752 753 ACPI_SERIAL_BEGIN(asus); 754 arg = acpi_asus_sysctl_get(sc, method); 755 error = sysctl_handle_int(oidp, &arg, 0, req); 756 757 /* Sanity check */ 758 if (error != 0 || req->newptr == NULL) 759 goto out; 760 761 /* Update */ 762 error = acpi_asus_sysctl_set(sc, method, arg); 763 764 out: 765 ACPI_SERIAL_END(asus); 766 return (error); 767 } 768 769 static int 770 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method) 771 { 772 int val = 0; 773 774 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 775 ACPI_SERIAL_ASSERT(asus); 776 777 switch (method) { 778 case ACPI_ASUS_METHOD_BRN: 779 val = sc->s_brn; 780 break; 781 case ACPI_ASUS_METHOD_DISP: 782 val = sc->s_disp; 783 break; 784 case ACPI_ASUS_METHOD_LCD: 785 val = sc->s_lcd; 786 break; 787 } 788 789 return (val); 790 } 791 792 static int 793 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg) 794 { 795 ACPI_STATUS status = AE_OK; 796 797 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 798 ACPI_SERIAL_ASSERT(asus); 799 800 switch (method) { 801 case ACPI_ASUS_METHOD_BRN: 802 if (arg < 0 || arg > 15) 803 return (EINVAL); 804 805 if (sc->model->brn_set) 806 status = acpi_SetInteger(sc->handle, 807 sc->model->brn_set, arg); 808 else { 809 while (arg != 0) { 810 status = AcpiEvaluateObject(sc->handle, 811 (arg > 0) ? sc->model->brn_up : 812 sc->model->brn_dn, NULL, NULL); 813 (arg > 0) ? arg-- : arg++; 814 } 815 } 816 817 if (ACPI_SUCCESS(status)) 818 sc->s_brn = arg; 819 820 break; 821 case ACPI_ASUS_METHOD_DISP: 822 if (arg < 0 || arg > 7) 823 return (EINVAL); 824 825 status = acpi_SetInteger(sc->handle, 826 sc->model->disp_set, arg); 827 828 if (ACPI_SUCCESS(status)) 829 sc->s_disp = arg; 830 831 break; 832 case ACPI_ASUS_METHOD_LCD: 833 if (arg < 0 || arg > 1) 834 return (EINVAL); 835 836 if (strncmp(sc->model->name, "L3H", 3) != 0) 837 status = AcpiEvaluateObject(sc->handle, 838 sc->model->lcd_set, NULL, NULL); 839 else 840 status = acpi_SetInteger(sc->handle, 841 sc->model->lcd_set, 0x7); 842 843 if (ACPI_SUCCESS(status)) 844 sc->s_lcd = arg; 845 846 break; 847 } 848 849 return (0); 850 } 851 852 static int 853 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method) 854 { 855 ACPI_STATUS status; 856 857 switch (method) { 858 case ACPI_ASUS_METHOD_BRN: 859 if (sc->model->brn_get) { 860 /* GPLV/SPLV models */ 861 status = acpi_GetInteger(sc->handle, 862 sc->model->brn_get, &sc->s_brn); 863 if (ACPI_SUCCESS(status)) 864 return (TRUE); 865 } else if (sc->model->brn_up) { 866 /* Relative models */ 867 status = AcpiEvaluateObject(sc->handle, 868 sc->model->brn_up, NULL, NULL); 869 if (ACPI_FAILURE(status)) 870 return (FALSE); 871 872 status = AcpiEvaluateObject(sc->handle, 873 sc->model->brn_dn, NULL, NULL); 874 if (ACPI_FAILURE(status)) 875 return (FALSE); 876 877 return (TRUE); 878 } 879 return (FALSE); 880 case ACPI_ASUS_METHOD_DISP: 881 if (sc->model->disp_get) { 882 status = acpi_GetInteger(sc->handle, 883 sc->model->disp_get, &sc->s_disp); 884 if (ACPI_SUCCESS(status)) 885 return (TRUE); 886 } 887 return (FALSE); 888 case ACPI_ASUS_METHOD_LCD: 889 if (sc->model->lcd_get && 890 strncmp(sc->model->name, "L3H", 3) != 0) { 891 status = acpi_GetInteger(sc->handle, 892 sc->model->lcd_get, &sc->s_lcd); 893 if (ACPI_SUCCESS(status)) 894 return (TRUE); 895 } 896 else if (sc->model->lcd_get) { 897 ACPI_BUFFER Buf; 898 ACPI_OBJECT Arg[2], Obj; 899 ACPI_OBJECT_LIST Args; 900 901 /* L3H is a bit special */ 902 Arg[0].Type = ACPI_TYPE_INTEGER; 903 Arg[0].Integer.Value = 0x02; 904 Arg[1].Type = ACPI_TYPE_INTEGER; 905 Arg[1].Integer.Value = 0x03; 906 907 Args.Count = 2; 908 Args.Pointer = Arg; 909 910 Buf.Length = sizeof(Obj); 911 Buf.Pointer = &Obj; 912 913 status = AcpiEvaluateObject(sc->handle, 914 sc->model->lcd_get, &Args, &Buf); 915 if (ACPI_SUCCESS(status) && 916 Obj.Type == ACPI_TYPE_INTEGER) { 917 sc->s_lcd = Obj.Integer.Value >> 8; 918 return (TRUE); 919 } 920 } 921 return (FALSE); 922 } 923 return (FALSE); 924 } 925 926 static void 927 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) 928 { 929 struct acpi_asus_softc *sc; 930 struct acpi_softc *acpi_sc; 931 932 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 933 934 sc = device_get_softc((device_t)context); 935 acpi_sc = acpi_device_get_parent_softc(sc->dev); 936 937 ACPI_SERIAL_BEGIN(asus); 938 if ((notify & ~0x10) <= 15) { 939 sc->s_brn = notify & ~0x10; 940 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 941 } else if ((notify & ~0x20) <= 15) { 942 sc->s_brn = notify & ~0x20; 943 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 944 } else if (notify == 0x33) { 945 sc->s_lcd = 1; 946 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); 947 } else if (notify == 0x34) { 948 sc->s_lcd = 0; 949 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); 950 } else { 951 /* Notify devd(8) */ 952 acpi_UserNotify("ASUS", h, notify); 953 } 954 ACPI_SERIAL_END(asus); 955 } 956