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 && 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, sc->model->brn_up, 304 NULL, NULL)) && 305 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, sc->model->brn_dn, 306 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 && ACPI_SUCCESS(acpi_GetInteger(sc->handle, 314 sc->model->disp_get, &sc->s_disp))) 315 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 316 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 317 "video_output", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 318 acpi_asus_sysctl_disp, "I", "display output state"); 319 320 /* Attach LCD state, easy for most models... */ 321 if (sc->model->lcd_get && strncmp(sc->model->name, "L3H", 3) != 0 && 322 ACPI_SUCCESS(acpi_GetInteger(sc->handle, sc->model->lcd_get, 323 &sc->s_lcd))) 324 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 325 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 326 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 327 acpi_asus_sysctl_lcd, "I", "state of the lcd backlight"); 328 329 /* ...a nightmare for the L3H */ 330 else if (sc->model->lcd_get) { 331 ACPI_BUFFER Buf; 332 ACPI_OBJECT Arg[2], Obj; 333 ACPI_OBJECT_LIST Args; 334 335 Arg[0].Type = ACPI_TYPE_INTEGER; 336 Arg[0].Integer.Value = 0x02; 337 Arg[1].Type = ACPI_TYPE_INTEGER; 338 Arg[1].Integer.Value = 0x03; 339 340 Args.Count = 2; 341 Args.Pointer = Arg; 342 343 Buf.Length = sizeof(Obj); 344 Buf.Pointer = &Obj; 345 346 if (ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, 347 sc->model->lcd_get, &Args, &Buf)) && 348 Obj.Type == ACPI_TYPE_INTEGER) { 349 sc->s_lcd = Obj.Integer.Value >> 8; 350 351 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 352 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 353 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 354 acpi_asus_sysctl_lcd, "I", 355 "state of the lcd backlight"); 356 } 357 } 358 359 /* Activate hotkeys */ 360 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); 361 362 /* Handle notifies */ 363 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 364 acpi_asus_notify, dev); 365 366 return (0); 367 } 368 369 static int 370 acpi_asus_detach(device_t dev) 371 { 372 struct acpi_asus_softc *sc; 373 374 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 375 376 sc = device_get_softc(dev); 377 378 /* Turn the lights off */ 379 if (sc->model->mled_set) 380 led_destroy(sc->s_mled); 381 382 if (sc->model->tled_set) 383 led_destroy(sc->s_tled); 384 385 if (sc->model->wled_set) 386 led_destroy(sc->s_wled); 387 388 /* Remove notify handler */ 389 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 390 acpi_asus_notify); 391 392 /* Free sysctl tree */ 393 sysctl_ctx_free(&sc->sysctl_ctx); 394 395 return (0); 396 } 397 398 static void 399 acpi_asus_mled(device_t dev, int state) 400 { 401 struct acpi_asus_softc *sc; 402 ACPI_OBJECT Arg; 403 ACPI_OBJECT_LIST Args; 404 405 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 406 407 sc = device_get_softc(dev); 408 409 Arg.Type = ACPI_TYPE_INTEGER; 410 Arg.Integer.Value = !state; /* Inverted, yes! */ 411 412 Args.Count = 1; 413 Args.Pointer = &Arg; 414 415 AcpiEvaluateObject(sc->handle, sc->model->mled_set, &Args, NULL); 416 } 417 418 static void 419 acpi_asus_tled(device_t dev, int state) 420 { 421 struct acpi_asus_softc *sc; 422 ACPI_OBJECT Arg; 423 ACPI_OBJECT_LIST Args; 424 425 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 426 427 sc = device_get_softc(dev); 428 429 Arg.Type = ACPI_TYPE_INTEGER; 430 Arg.Integer.Value = state; 431 432 Args.Count = 1; 433 Args.Pointer = &Arg; 434 435 AcpiEvaluateObject(sc->handle, sc->model->tled_set, &Args, NULL); 436 } 437 438 static void 439 acpi_asus_wled(device_t dev, int state) 440 { 441 struct acpi_asus_softc *sc; 442 ACPI_OBJECT Arg; 443 ACPI_OBJECT_LIST Args; 444 445 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 446 447 sc = device_get_softc(dev); 448 449 Arg.Type = ACPI_TYPE_INTEGER; 450 Arg.Integer.Value = state; 451 452 Args.Count = 1; 453 Args.Pointer = &Arg; 454 455 AcpiEvaluateObject(sc->handle, sc->model->wled_set, &Args, NULL); 456 } 457 458 static int 459 acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS) 460 { 461 struct acpi_asus_softc *sc; 462 ACPI_OBJECT Arg; 463 ACPI_OBJECT_LIST Args; 464 int brn, err; 465 466 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 467 468 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 469 470 /* Sanity check */ 471 brn = sc->s_brn; 472 err = sysctl_handle_int(oidp, &brn, 0, req); 473 474 if (err != 0 || req->newptr == NULL) 475 return (err); 476 477 if (brn < 0 || brn > 15) 478 return (EINVAL); 479 480 /* Keep track and update */ 481 sc->s_brn = brn; 482 483 Arg.Type = ACPI_TYPE_INTEGER; 484 Arg.Integer.Value = brn; 485 486 Args.Count = 1; 487 Args.Pointer = &Arg; 488 489 if (sc->model->brn_set) 490 AcpiEvaluateObject(sc->handle, sc->model->brn_set, &Args, NULL); 491 else { 492 brn -= sc->s_brn; 493 494 while (brn != 0) { 495 AcpiEvaluateObject(sc->handle, (brn > 0) ? 496 sc->model->brn_up : sc->model->brn_dn, 497 NULL, NULL); 498 499 (brn > 0) ? brn-- : brn++; 500 } 501 } 502 503 return (0); 504 } 505 506 static int 507 acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS) 508 { 509 struct acpi_asus_softc *sc; 510 int lcd, err; 511 512 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 513 514 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 515 516 /* Sanity check */ 517 lcd = sc->s_lcd; 518 err = sysctl_handle_int(oidp, &lcd, 0, req); 519 520 if (err != 0 || req->newptr == NULL) 521 return (err); 522 523 if (lcd < 0 || lcd > 1) 524 return (EINVAL); 525 526 /* Keep track and update */ 527 sc->s_lcd = lcd; 528 529 /* Most models just need a lcd_set evaluated, the L3H is trickier */ 530 if (strncmp(sc->model->name, "L3H", 3) != 0) 531 AcpiEvaluateObject(sc->handle, sc->model->lcd_set, NULL, NULL); 532 else { 533 ACPI_OBJECT Arg; 534 ACPI_OBJECT_LIST Args; 535 536 Arg.Type = ACPI_TYPE_INTEGER; 537 Arg.Integer.Value = 0x07; 538 539 Args.Count = 1; 540 Args.Pointer = &Arg; 541 542 AcpiEvaluateObject(sc->handle, sc->model->lcd_set, &Args, NULL); 543 } 544 545 return (0); 546 } 547 548 static int 549 acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS) 550 { 551 struct acpi_asus_softc *sc; 552 ACPI_OBJECT Arg; 553 ACPI_OBJECT_LIST Args; 554 int disp, err; 555 556 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 557 558 sc = (struct acpi_asus_softc *)oidp->oid_arg1; 559 560 /* Sanity check */ 561 disp = sc->s_disp; 562 err = sysctl_handle_int(oidp, &disp, 0, req); 563 564 if (err != 0 || req->newptr == NULL) 565 return (err); 566 567 if (disp < 0 || disp > 7) 568 return (EINVAL); 569 570 /* Keep track and update */ 571 sc->s_disp = disp; 572 573 Arg.Type = ACPI_TYPE_INTEGER; 574 Arg.Integer.Value = disp; 575 576 Args.Count = 1; 577 Args.Pointer = &Arg; 578 579 AcpiEvaluateObject(sc->handle, sc->model->disp_set, &Args, NULL); 580 581 return (0); 582 } 583 584 static void 585 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) 586 { 587 struct acpi_asus_softc *sc; 588 struct acpi_softc *acpi_sc; 589 590 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 591 592 sc = device_get_softc((device_t)context); 593 acpi_sc = acpi_device_get_parent_softc(sc->dev); 594 595 if ((notify & ~0x10) <= 15) { 596 sc->s_brn = notify & ~0x10; 597 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); 598 } else if ((notify & ~0x20) <= 15) { 599 sc->s_brn = notify & ~0x20; 600 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); 601 } else if (notify == 0x33) { 602 sc->s_lcd = 1; 603 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); 604 } else if (notify == 0x34) { 605 sc->s_lcd = 0; 606 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); 607 } else { 608 /* Notify devd(8) */ 609 acpi_UserNotify("ASUS", h, notify); 610 } 611 } 612