1 /*- 2 * Copyright (c) 2004 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 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 /* 32 * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on 33 * recent Asus (and Medion) laptops. Inspired by the Acpi4Asus project which 34 * implements these features in the Linux kernel. 35 * 36 * <http://sourceforge.net/projects/acpi4asus/> 37 * 38 * Currently should support most features, but could use some more testing. 39 * Particularly the display-switching stuff is a bit hairy. If you have an 40 * Asus laptop which doesn't appear to be supported, or strange things happen 41 * when using this driver, please report to <acpi@FreeBSD.org>. 42 * 43 */ 44 45 #include "opt_acpi.h" 46 #include <sys/param.h> 47 #include <sys/kernel.h> 48 #include <sys/bus.h> 49 #include <sys/sbuf.h> 50 51 #include "acpi.h" 52 #include <dev/acpica/acpivar.h> 53 #include <dev/led/led.h> 54 55 #define _COMPONENT ACPI_ASUS 56 ACPI_MODULE_NAME("ASUS") 57 58 struct acpi_asus_model { 59 char *name; 60 61 char *mled_set; 62 char *tled_set; 63 char *wled_set; 64 65 char *brn_get; 66 char *brn_set; 67 char *brn_up; 68 char *brn_dn; 69 70 char *lcd_get; 71 char *lcd_set; 72 73 char *disp_get; 74 char *disp_set; 75 }; 76 77 struct acpi_asus_softc { 78 device_t dev; 79 ACPI_HANDLE handle; 80 81 struct acpi_asus_model *model; 82 struct sysctl_ctx_list sysctl_ctx; 83 struct sysctl_oid *sysctl_tree; 84 85 dev_t s_mled; 86 dev_t s_tled; 87 dev_t s_wled; 88 89 int s_brn; 90 int s_disp; 91 int s_lcd; 92 }; 93 94 /* Models we know about */ 95 static struct acpi_asus_model acpi_asus_models[] = { 96 { 97 .name = "L2D", 98 .mled_set = "MLED", 99 .wled_set = "WLED", 100 .brn_up = "\\Q0E", 101 .brn_dn = "\\Q0F", 102 .lcd_get = "\\SGP0", 103 .lcd_set = "\\Q10" 104 }, 105 { 106 .name = "L3C", 107 .mled_set = "MLED", 108 .wled_set = "WLED", 109 .brn_get = "GPLV", 110 .brn_set = "SPLV", 111 .lcd_get = "\\GL32", 112 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10" 113 }, 114 { 115 .name = "L3D", 116 .mled_set = "MLED", 117 .wled_set = "WLED", 118 .brn_get = "GPLV", 119 .brn_set = "SPLV", 120 .lcd_get = "\\BKLG", 121 .lcd_set = "\\Q10" 122 }, 123 { 124 .name = "L3H", 125 .mled_set = "MLED", 126 .wled_set = "WLED", 127 .brn_get = "GPLV", 128 .brn_set = "SPLV", 129 .lcd_get = "\\_SB.PCI0.PM.PBC", 130 .lcd_set = "EHK", 131 .disp_get = "\\_SB.INFB", 132 .disp_set = "SDSP" 133 }, 134 { 135 .name = "L8L" 136 /* Only has hotkeys, apparantly */ 137 }, 138 { 139 .name = "M1A", 140 .mled_set = "MLED", 141 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E", 142 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F", 143 .lcd_get = "\\PNOF", 144 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10" 145 }, 146 { 147 .name = "M2E", 148 .mled_set = "MLED", 149 .wled_set = "WLED", 150 .brn_get = "GPLV", 151 .brn_set = "SPLV", 152 .lcd_get = "\\GP06", 153 .lcd_set = "\\Q10" 154 }, 155 { 156 .name = "P30", 157 .wled_set = "WLED", 158 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68", 159 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69", 160 .lcd_get = "\\BKLT", 161 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E" 162 }, 163 164 { .name = NULL } 165 }; 166 167 /* Function prototypes */ 168 static int acpi_asus_probe(device_t dev); 169 static int acpi_asus_attach(device_t dev); 170 static int acpi_asus_detach(device_t dev); 171 172 static void acpi_asus_mled(device_t dev, int state); 173 static void acpi_asus_tled(device_t dev, int state); 174 static void acpi_asus_wled(device_t dev, int state); 175 176 static int acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS); 177 static int acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS); 178 static int acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS); 179 180 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context); 181 182 static device_method_t acpi_asus_methods[] = { 183 DEVMETHOD(device_probe, acpi_asus_probe), 184 DEVMETHOD(device_attach, acpi_asus_attach), 185 DEVMETHOD(device_detach, acpi_asus_detach), 186 187 { 0, 0 } 188 }; 189 190 static driver_t acpi_asus_driver = { 191 "acpi_asus", 192 acpi_asus_methods, 193 sizeof(struct acpi_asus_softc) 194 }; 195 196 static devclass_t acpi_asus_devclass; 197 198 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0); 199 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1); 200 201 static int 202 acpi_asus_probe(device_t dev) 203 { 204 struct acpi_asus_model *model; 205 struct acpi_asus_softc *sc; 206 struct sbuf *sb; 207 ACPI_BUFFER Buf; 208 ACPI_OBJECT Arg, *Obj; 209 ACPI_OBJECT_LIST Args; 210 211 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 212 213 if (!acpi_disabled("asus") && 214 acpi_get_type(dev) == ACPI_TYPE_DEVICE && 215 acpi_MatchHid(dev, "ATK0100")) { 216 sc = device_get_softc(dev); 217 sc->dev = dev; 218 sc->handle = acpi_get_handle(dev); 219 220 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND); 221 222 if (sb == NULL) 223 return (ENOMEM); 224 225 Arg.Type = ACPI_TYPE_INTEGER; 226 Arg.Integer.Value = 0; 227 228 Args.Count = 1; 229 Args.Pointer = &Arg; 230 231 Buf.Pointer = NULL; 232 Buf.Length = ACPI_ALLOCATE_BUFFER; 233 234 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf); 235 236 Obj = Buf.Pointer; 237 238 for (model = acpi_asus_models; model->name != NULL; model++) 239 if (strcmp(Obj->String.Pointer, model->name) == 0) { 240 sbuf_printf(sb, "Asus %s Laptop Extras", 241 Obj->String.Pointer); 242 sbuf_finish(sb); 243 244 sc->model = model; 245 device_set_desc(dev, sbuf_data(sb)); 246 247 sbuf_delete(sb); 248 AcpiOsFree(Buf.Pointer); 249 return (0); 250 } 251 252 sbuf_printf(sb, "Unsupported Asus laptop detected: %s\n", 253 Obj->String.Pointer); 254 sbuf_finish(sb); 255 256 device_printf(dev, sbuf_data(sb)); 257 258 sbuf_delete(sb); 259 AcpiOsFree(Buf.Pointer); 260 } 261 262 return (ENXIO); 263 } 264 265 static int 266 acpi_asus_attach(device_t dev) 267 { 268 struct acpi_asus_softc *sc; 269 struct acpi_softc *acpi_sc; 270 271 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 272 273 sc = device_get_softc(dev); 274 acpi_sc = acpi_device_get_parent_softc(dev); 275 276 /* Build sysctl tree */ 277 sysctl_ctx_init(&sc->sysctl_ctx); 278 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 279 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), 280 OID_AUTO, "asus", CTLFLAG_RD, 0, ""); 281 282 /* Attach leds */ 283 if (sc->model->mled_set) 284 sc->s_mled = led_create((led_t *)acpi_asus_mled, dev, "mled"); 285 286 if (sc->model->tled_set) 287 sc->s_tled = led_create((led_t *)acpi_asus_tled, dev, "tled"); 288 289 if (sc->model->wled_set) 290 sc->s_wled = led_create((led_t *)acpi_asus_wled, dev, "wled"); 291 292 /* Attach brightness for GPLV/SPLV models */ 293 if (sc->model->brn_get && 294 ACPI_SUCCESS(acpi_GetInteger(sc->handle, 295 sc->model->brn_get, &sc->s_brn))) 296 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 297 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 298 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 299 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel"); 300 301 /* Attach brightness for other models */ 302 if (sc->model->brn_up && 303 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, 304 sc->model->brn_up, NULL, NULL)) && 305 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, 306 sc->model->brn_dn, NULL, NULL))) 307 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 308 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 309 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 310 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel"); 311 312 /* Attach display switching */ 313 if (sc->model->disp_get && 314 ACPI_SUCCESS(acpi_GetInteger(sc->handle, 315 sc->model->disp_get, &sc->s_disp))) 316 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 317 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 318 "video_output", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 319 acpi_asus_sysctl_disp, "I", "display output state"); 320 321 /* Attach LCD state, easy for most models... */ 322 if (sc->model->lcd_get && 323 strncmp(sc->model->name, "L3H", 3) != 0 && 324 ACPI_SUCCESS(acpi_GetInteger(sc->handle, 325 sc->model->lcd_get, &sc->s_lcd))) 326 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 327 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 328 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 329 acpi_asus_sysctl_lcd, "I", "state of the lcd backlight"); 330 331 /* ...a nightmare for the L3H */ 332 else if (sc->model->lcd_get) { 333 ACPI_BUFFER Buf; 334 ACPI_OBJECT Arg[2], Obj; 335 ACPI_OBJECT_LIST Args; 336 337 Arg[0].Type = ACPI_TYPE_INTEGER; 338 Arg[0].Integer.Value = 0x02; 339 Arg[1].Type = ACPI_TYPE_INTEGER; 340 Arg[1].Integer.Value = 0x03; 341 342 Args.Count = 2; 343 Args.Pointer = Arg; 344 345 Buf.Length = sizeof(Obj); 346 Buf.Pointer = &Obj; 347 348 if (ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, 349 sc->model->lcd_get, &Args, &Buf)) && 350 Obj.Type == ACPI_TYPE_INTEGER) { 351 sc->s_lcd = Obj.Integer.Value >> 8; 352 353 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 354 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 355 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 356 acpi_asus_sysctl_lcd, "I", 357 "state of the lcd backlight"); 358 } 359 } 360 361 /* Activate hotkeys */ 362 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); 363 364 /* Handle notifies */ 365 AcpiInstallNotifyHandler(sc->handle, 366 ACPI_SYSTEM_NOTIFY, acpi_asus_notify, dev); 367 368 return (0); 369 } 370 371 static int 372 acpi_asus_detach(device_t dev) 373 { 374 struct acpi_asus_softc *sc; 375 376 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 377 378 sc = device_get_softc(dev); 379 380 /* Turn the lights off */ 381 if (sc->model->mled_set) 382 led_destroy(sc->s_mled); 383 384 if (sc->model->tled_set) 385 led_destroy(sc->s_tled); 386 387 if (sc->model->wled_set) 388 led_destroy(sc->s_wled); 389 390 /* Remove notify handler */ 391 AcpiRemoveNotifyHandler(sc->handle, 392 ACPI_SYSTEM_NOTIFY, acpi_asus_notify); 393 394 /* Free sysctl tree */ 395 sysctl_ctx_free(&sc->sysctl_ctx); 396 397 return (0); 398 } 399 400 static void 401 acpi_asus_mled(device_t dev, int state) 402 { 403 struct acpi_asus_softc *sc; 404 ACPI_OBJECT Arg; 405 ACPI_OBJECT_LIST Args; 406 407 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 408 409 sc = device_get_softc(dev); 410 411 Arg.Type = ACPI_TYPE_INTEGER; 412 Arg.Integer.Value = !state; /* Inverted, yes! */ 413 414 Args.Count = 1; 415 Args.Pointer = &Arg; 416 417 AcpiEvaluateObject(sc->handle, sc->model->mled_set, &Args, NULL); 418 } 419 420 static void 421 acpi_asus_tled(device_t dev, int state) 422 { 423 struct acpi_asus_softc *sc; 424 ACPI_OBJECT Arg; 425 ACPI_OBJECT_LIST Args; 426 427 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 428 429 sc = device_get_softc(dev); 430 431 Arg.Type = ACPI_TYPE_INTEGER; 432 Arg.Integer.Value = state; 433 434 Args.Count = 1; 435 Args.Pointer = &Arg; 436 437 AcpiEvaluateObject(sc->handle, sc->model->tled_set, &Args, NULL); 438 } 439 440 static void 441 acpi_asus_wled(device_t dev, int state) 442 { 443 struct acpi_asus_softc *sc; 444 ACPI_OBJECT Arg; 445 ACPI_OBJECT_LIST Args; 446 447 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 448 449 sc = device_get_softc(dev); 450 451 Arg.Type = ACPI_TYPE_INTEGER; 452 Arg.Integer.Value = state; 453 454 Args.Count = 1; 455 Args.Pointer = &Arg; 456 457 AcpiEvaluateObject(sc->handle, sc->model->wled_set, &Args, NULL); 458 } 459 460 static int 461 acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS) 462 { 463 struct acpi_asus_softc *sc; 464 ACPI_OBJECT Arg; 465 ACPI_OBJECT_LIST Args; 466 int brn, err; 467 468 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 469 470 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 471 472 /* Sanity check */ 473 brn = sc->s_brn; 474 err = sysctl_handle_int(oidp, &brn, 0, req); 475 476 if ((err != 0) || (req->newptr == NULL)) 477 return (err); 478 479 if ((brn < 0) || (brn > 15)) 480 return (EINVAL); 481 482 /* Keep track and update */ 483 sc->s_brn = brn; 484 485 Arg.Type = ACPI_TYPE_INTEGER; 486 Arg.Integer.Value = brn; 487 488 Args.Count = 1; 489 Args.Pointer = &Arg; 490 491 if (sc->model->brn_set) 492 AcpiEvaluateObject(sc->handle, 493 sc->model->brn_set, &Args, NULL); 494 else { 495 brn -= sc->s_brn; 496 497 while (brn != 0) { 498 AcpiEvaluateObject(sc->handle,(brn > 0) ? 499 sc->model->brn_up : sc->model->brn_dn, 500 NULL, NULL); 501 502 (brn > 0) ? brn-- : brn++; 503 } 504 } 505 506 return (0); 507 } 508 509 static int 510 acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS) 511 { 512 struct acpi_asus_softc *sc; 513 int lcd, err; 514 515 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 516 517 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 518 519 /* Sanity check */ 520 lcd = sc->s_lcd; 521 err = sysctl_handle_int(oidp, &lcd, 0, req); 522 523 if ((err != 0) || (req->newptr == NULL)) 524 return (err); 525 526 if ((lcd < 0) || (lcd > 1)) 527 return (EINVAL); 528 529 /* Keep track and update */ 530 sc->s_lcd = lcd; 531 532 /* Most models just need a lcd_set evaluated, the L3H is trickier */ 533 if (strncmp(sc->model->name, "L3H", 3) != 0) 534 AcpiEvaluateObject(sc->handle, 535 sc->model->lcd_set, NULL, NULL); 536 else { 537 ACPI_OBJECT Arg; 538 ACPI_OBJECT_LIST Args; 539 540 Arg.Type = ACPI_TYPE_INTEGER; 541 Arg.Integer.Value = 0x07; 542 543 Args.Count = 1; 544 Args.Pointer = &Arg; 545 546 AcpiEvaluateObject(sc->handle, 547 sc->model->lcd_set, &Args, NULL); 548 } 549 550 return (0); 551 } 552 553 static int 554 acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS) 555 { 556 struct acpi_asus_softc *sc; 557 ACPI_OBJECT Arg; 558 ACPI_OBJECT_LIST Args; 559 int disp, err; 560 561 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 562 563 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 564 565 /* Sanity check */ 566 disp = sc->s_disp; 567 err = sysctl_handle_int(oidp, &disp, 0, req); 568 569 if ((err != 0) || (req->newptr == NULL)) 570 return (err); 571 572 if ((disp < 0) || (disp > 7)) 573 return (EINVAL); 574 575 /* Keep track and update */ 576 sc->s_disp = disp; 577 578 Arg.Type = ACPI_TYPE_INTEGER; 579 Arg.Integer.Value = disp; 580 581 Args.Count = 1; 582 Args.Pointer = &Arg; 583 584 AcpiEvaluateObject(sc->handle, 585 sc->model->disp_set, &Args, NULL); 586 587 return (0); 588 } 589 590 static void 591 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) 592 { 593 struct acpi_asus_softc *sc; 594 struct acpi_softc *acpi_sc; 595 596 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 597 598 sc = device_get_softc((device_t)context); 599 acpi_sc = acpi_device_get_parent_softc(sc->dev); 600 601 if ((notify & ~0x10) <= 15) { 602 sc->s_brn = (notify & ~0x10); 603 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 604 } else if ((notify & ~0x20) <= 15) { 605 sc->s_brn = (notify & ~0x20); 606 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 607 } else if (notify == 0x33) { 608 sc->s_lcd = 1; 609 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); 610 } else if (notify == 0x34) { 611 sc->s_lcd = 0; 612 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); 613 } else { 614 /* Notify devd(8) */ 615 acpi_UserNotify("ASUS", h, notify); 616 } 617 } 618