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