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