2 * Copyright (c) 2004 Philip Paeps <philip@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
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.
35 * <http://sourceforge.net/projects/acpi4asus/>
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>.
44 #include <sys/param.h>
45 #include <sys/kernel.h>
46 #include <sys/module.h>
51 #include <dev/acpica/acpivar.h>
52 #include <dev/led/led.h>
54 #define _COMPONENT ACPI_OEM
55 ACPI_MODULE_NAME("ASUS")
57 struct acpi_asus_model {
76 struct acpi_asus_led {
86 struct acpi_asus_softc {
90 struct acpi_asus_model *model;
91 struct sysctl_ctx_list sysctl_ctx;
92 struct sysctl_oid *sysctl_tree;
94 struct acpi_asus_led s_mled;
95 struct acpi_asus_led s_tled;
96 struct acpi_asus_led s_wled;
104 * We can identify Asus laptops from the string they return
105 * as a result of calling the ATK0100 'INIT' method.
107 static struct acpi_asus_model acpi_asus_models[] = {
124 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10"
141 .lcd_get = "\\_SB.PCI0.PM.PBC",
143 .disp_get = "\\_SB.INFB",
152 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
153 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
154 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD",
159 /* Only has hotkeys, apparantly */
164 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E",
165 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F",
167 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10"
182 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
183 .lcd_get = "\\_SB.BKLT",
195 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
196 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
197 .disp_get = "\\SSTE",
205 * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
206 * but they can't be probed quite the same way as Asus laptops.
208 static struct acpi_asus_model acpi_samsung_models[] = {
212 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68",
213 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69",
215 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E"
221 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras");
223 /* Function prototypes */
224 static int acpi_asus_probe(device_t dev);
225 static int acpi_asus_attach(device_t dev);
226 static int acpi_asus_detach(device_t dev);
228 static void acpi_asus_led(struct acpi_asus_led *led, int state);
230 static int acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS);
231 static int acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS);
232 static int acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS);
234 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
236 static device_method_t acpi_asus_methods[] = {
237 DEVMETHOD(device_probe, acpi_asus_probe),
238 DEVMETHOD(device_attach, acpi_asus_attach),
239 DEVMETHOD(device_detach, acpi_asus_detach),
244 static driver_t acpi_asus_driver = {
247 sizeof(struct acpi_asus_softc)
250 static devclass_t acpi_asus_devclass;
252 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
253 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
256 acpi_asus_probe(device_t dev)
258 struct acpi_asus_model *model;
259 struct acpi_asus_softc *sc;
262 ACPI_OBJECT Arg, *Obj;
263 ACPI_OBJECT_LIST Args;
264 static char *asus_ids[] = { "ATK0100", NULL };
266 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
268 if (acpi_disabled("asus") ||
269 ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids) == NULL)
272 sc = device_get_softc(dev);
274 sc->handle = acpi_get_handle(dev);
276 Arg.Type = ACPI_TYPE_INTEGER;
277 Arg.Integer.Value = 0;
283 Buf.Length = ACPI_ALLOCATE_BUFFER;
285 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
289 * The Samsung P30 returns a null-pointer from INIT, we
290 * can identify it from the 'ODEM' string in the DSDT.
292 if (Obj->String.Pointer == NULL) {
294 ACPI_TABLE_HEADER th;
296 status = AcpiGetTableHeader(ACPI_TABLE_DSDT, 1, &th);
297 if (ACPI_FAILURE(status)) {
298 device_printf(dev, "Unsupported (Samsung?) laptop\n");
299 AcpiOsFree(Buf.Pointer);
303 if (strncmp("ODEM", th.OemTableId, 4) == 0) {
304 sc->model = &acpi_samsung_models[0];
305 device_set_desc(dev, "Samsung P30 Laptop Extras");
306 AcpiOsFree(Buf.Pointer);
311 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
316 * Asus laptops are simply identified by name, easy!
318 for (model = acpi_asus_models; model->name != NULL; model++)
319 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
320 sbuf_printf(sb, "Asus %s Laptop Extras", model->name);
324 device_set_desc(dev, sbuf_data(sb));
327 AcpiOsFree(Buf.Pointer);
331 sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
334 device_printf(dev, sbuf_data(sb));
337 AcpiOsFree(Buf.Pointer);
343 acpi_asus_attach(device_t dev)
345 struct acpi_asus_softc *sc;
346 struct acpi_softc *acpi_sc;
348 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
350 sc = device_get_softc(dev);
351 acpi_sc = acpi_device_get_parent_softc(dev);
353 /* Build sysctl tree */
354 sysctl_ctx_init(&sc->sysctl_ctx);
355 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
356 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
357 OID_AUTO, "asus", CTLFLAG_RD, 0, "");
360 if (sc->model->mled_set) {
361 sc->s_mled.dev = dev;
362 sc->s_mled.type = ACPI_ASUS_LED_MLED;
364 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
367 if (sc->model->tled_set) {
368 sc->s_tled.dev = dev;
369 sc->s_tled.type = ACPI_ASUS_LED_TLED;
371 led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
374 if (sc->model->wled_set) {
375 sc->s_wled.dev = dev;
376 sc->s_wled.type = ACPI_ASUS_LED_WLED;
378 led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
381 /* Attach brightness for GPLV/SPLV models */
382 if (sc->model->brn_get && ACPI_SUCCESS(acpi_GetInteger(sc->handle,
383 sc->model->brn_get, &sc->s_brn)))
384 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
385 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
386 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
387 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel");
389 /* Attach brightness for other models */
390 if (sc->model->brn_up &&
391 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, sc->model->brn_up,
393 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, sc->model->brn_dn,
395 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
396 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
397 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
398 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel");
400 /* Attach display switching */
401 if (sc->model->disp_get && ACPI_SUCCESS(acpi_GetInteger(sc->handle,
402 sc->model->disp_get, &sc->s_disp)))
403 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
404 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
405 "video_output", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
406 acpi_asus_sysctl_disp, "I", "display output state");
408 /* Attach LCD state, easy for most models... */
409 if (sc->model->lcd_get && strncmp(sc->model->name, "L3H", 3) != 0 &&
410 ACPI_SUCCESS(acpi_GetInteger(sc->handle, sc->model->lcd_get,
412 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
413 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
414 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
415 acpi_asus_sysctl_lcd, "I", "state of the lcd backlight");
416 } else if (sc->model->lcd_get) {
418 ACPI_OBJECT Arg[2], Obj;
419 ACPI_OBJECT_LIST Args;
421 /* ...a nightmare for the L3H */
422 Arg[0].Type = ACPI_TYPE_INTEGER;
423 Arg[0].Integer.Value = 0x02;
424 Arg[1].Type = ACPI_TYPE_INTEGER;
425 Arg[1].Integer.Value = 0x03;
430 Buf.Length = sizeof(Obj);
433 if (ACPI_SUCCESS(AcpiEvaluateObject(sc->handle,
434 sc->model->lcd_get, &Args, &Buf)) &&
435 Obj.Type == ACPI_TYPE_INTEGER) {
436 sc->s_lcd = Obj.Integer.Value >> 8;
438 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
439 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
440 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
441 acpi_asus_sysctl_lcd, "I",
442 "state of the lcd backlight");
446 /* Activate hotkeys */
447 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
449 /* Handle notifies */
450 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
451 acpi_asus_notify, dev);
457 acpi_asus_detach(device_t dev)
459 struct acpi_asus_softc *sc;
461 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
463 sc = device_get_softc(dev);
465 /* Turn the lights off */
466 if (sc->model->mled_set)
467 led_destroy(sc->s_mled.cdev);
469 if (sc->model->tled_set)
470 led_destroy(sc->s_tled.cdev);
472 if (sc->model->wled_set)
473 led_destroy(sc->s_wled.cdev);
475 /* Remove notify handler */
476 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
479 /* Free sysctl tree */
480 sysctl_ctx_free(&sc->sysctl_ctx);
486 acpi_asus_led(struct acpi_asus_led *led, int state)
488 struct acpi_asus_softc *sc;
491 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
493 sc = device_get_softc(led->dev);
496 case ACPI_ASUS_LED_MLED:
497 method = sc->model->mled_set;
502 case ACPI_ASUS_LED_TLED:
503 method = sc->model->tled_set;
505 case ACPI_ASUS_LED_WLED:
506 method = sc->model->wled_set;
509 printf("acpi_asus_led: invalid LED type %d\n",
514 acpi_SetInteger(sc->handle, method, state);
518 acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS)
520 struct acpi_asus_softc *sc;
523 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
525 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
526 ACPI_SERIAL_BEGIN(asus);
530 err = sysctl_handle_int(oidp, &brn, 0, req);
532 if (err != 0 || req->newptr == NULL)
535 if (brn < 0 || brn > 15) {
540 /* Keep track and update */
543 if (sc->model->brn_set)
544 acpi_SetInteger(sc->handle, sc->model->brn_set, brn);
549 AcpiEvaluateObject(sc->handle, (brn > 0) ?
550 sc->model->brn_up : sc->model->brn_dn,
552 (brn > 0) ? brn-- : brn++;
557 ACPI_SERIAL_END(asus);
562 acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS)
564 struct acpi_asus_softc *sc;
567 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
569 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
570 ACPI_SERIAL_BEGIN(asus);
574 err = sysctl_handle_int(oidp, &lcd, 0, req);
576 if (err != 0 || req->newptr == NULL)
579 if (lcd < 0 || lcd > 1) {
584 /* Keep track and update */
587 /* Most models just need a lcd_set evaluated, the L3H is trickier */
588 if (strncmp(sc->model->name, "L3H", 3) != 0)
589 AcpiEvaluateObject(sc->handle, sc->model->lcd_set, NULL, NULL);
591 acpi_SetInteger(sc->handle, sc->model->lcd_set, 0x7);
594 ACPI_SERIAL_END(asus);
599 acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS)
601 struct acpi_asus_softc *sc;
604 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
606 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
609 ACPI_SERIAL_BEGIN(asus);
611 err = sysctl_handle_int(oidp, &disp, 0, req);
613 if (err != 0 || req->newptr == NULL)
616 if (disp < 0 || disp > 7) {
621 /* Keep track and update */
623 acpi_SetInteger(sc->handle, sc->model->disp_set, disp);
626 ACPI_SERIAL_END(asus);
631 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
633 struct acpi_asus_softc *sc;
634 struct acpi_softc *acpi_sc;
636 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
638 sc = device_get_softc((device_t)context);
639 acpi_sc = acpi_device_get_parent_softc(sc->dev);
641 ACPI_SERIAL_BEGIN(asus);
642 if ((notify & ~0x10) <= 15) {
643 sc->s_brn = notify & ~0x10;
644 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
645 } else if ((notify & ~0x20) <= 15) {
646 sc->s_brn = notify & ~0x20;
647 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
648 } else if (notify == 0x33) {
650 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
651 } else if (notify == 0x34) {
653 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
656 acpi_UserNotify("ASUS", h, notify);
658 ACPI_SERIAL_END(asus);