2 * Copyright (c) 2004, 2005 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
26 * $FreeBSD: src/sys/dev/acpi_support/acpi_asus.c,v 1.24.2.4.2.1 2008/10/02 02:57:24 kensmith Exp $
29 #include <sys/cdefs.h>
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.
36 * <http://sourceforge.net/projects/acpi4asus/>
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>.
45 #include <sys/param.h>
46 #include <sys/kernel.h>
48 #include <machine/cpufunc.h>
49 #include <sys/module.h>
50 #include <sys/sensors.h>
51 #include <sys/sysctl.h>
52 #include <sys/types.h>
55 #include <sys/thread2.h>
56 #include <machine/clock.h>
63 #define ACPI_ASUS_METHOD_BRN 1
64 #define ACPI_ASUS_METHOD_DISP 2
65 #define ACPI_ASUS_METHOD_LCD 3
67 #define _COMPONENT ACPI_OEM
68 ACPI_MODULE_NAME("ASUS")
70 struct acpi_asus_model {
90 struct acpi_asus_led {
91 struct acpi_asus_softc *sc;
103 struct acpi_asus_softc {
107 struct acpi_asus_model *model;
108 struct sysctl_ctx_list sysctl_ctx;
109 struct sysctl_oid *sysctl_tree;
111 struct acpi_asus_led s_bled;
112 struct acpi_asus_led s_mled;
113 struct acpi_asus_led s_tled;
114 struct acpi_asus_led s_wled;
122 * We can identify Asus laptops from the string they return
123 * as a result of calling the ATK0100 'INIT' method.
125 static struct acpi_asus_model acpi_asus_models[] = {
131 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
134 .disp_get = "\\ADVG",
141 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10",
142 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E",
143 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F"
153 .disp_get = "\\INFB",
162 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
166 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD"
171 .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E",
172 .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F",
176 .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10",
177 .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11"
186 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
189 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD",
199 .disp_get = "\\INFB",
218 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10"
235 .lcd_get = "\\_SB.PCI0.PM.PBC",
237 .disp_get = "\\_SB.INFB",
246 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
247 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
248 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD",
259 .disp_get = "\\INFB",
264 /* Only has hotkeys, apparantly */
269 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E",
270 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F",
272 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10"
287 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
288 .lcd_get = "\\_SB.BKLT",
300 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
301 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
302 .disp_get = "\\SSTE",
310 .lcd_set = "\\_SB.PCI0.PX40.Q10",
318 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10",
319 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B",
320 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A"
328 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
331 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD",
338 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
341 .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD",
349 * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
350 * but they can't be probed quite the same way as Asus laptops.
352 static struct acpi_asus_model acpi_samsung_models[] = {
356 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68",
357 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69",
359 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E"
366 * EeePC have an Asus ASUS010 gadget interface,
367 * but they can't be probed quite the same way as Asus laptops.
369 static struct acpi_asus_model acpi_eeepc_models[] = {
372 .brn_get = "\\_SB.ATKD.PBLG",
373 .brn_set = "\\_SB.ATKD.PBLS"
383 } acpi_asus_sysctls[] = {
385 .name = "lcd_backlight",
386 .method = ACPI_ASUS_METHOD_LCD,
387 .description = "state of the lcd backlight"
390 .name = "lcd_brightness",
391 .method = ACPI_ASUS_METHOD_BRN,
392 .description = "brightness of the lcd panel"
395 .name = "video_output",
396 .method = ACPI_ASUS_METHOD_DISP,
397 .description = "display output state"
403 static int acpi_asus_probe(device_t dev);
404 static int acpi_asus_attach(device_t dev);
405 static int acpi_asus_detach(device_t dev);
407 static struct lock asuslock;
409 static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
410 static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
411 static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method);
412 static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val);
414 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
416 static device_method_t acpi_asus_methods[] = {
417 /* Device interface */
418 DEVMETHOD(device_probe, acpi_asus_probe),
419 DEVMETHOD(device_attach, acpi_asus_attach),
420 DEVMETHOD(device_detach, acpi_asus_detach),
424 static driver_t acpi_asus_driver = {
427 sizeof(struct acpi_asus_softc),
430 static devclass_t acpi_asus_devclass;
432 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver,
433 acpi_asus_devclass, 0, 0);
434 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
436 static char *asus_ids[] = { "ATK0100", "ASUS010", NULL };
439 acpi_asus_probe(device_t dev)
441 struct acpi_asus_model *model;
442 struct acpi_asus_softc *sc;
445 ACPI_OBJECT Arg, *Obj;
446 ACPI_OBJECT_LIST Args;
449 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
451 if (acpi_disabled("asus")) return (ENXIO);
452 rstr = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids);
458 sc = device_get_softc(dev);
460 sc->handle = acpi_get_handle(dev);
463 Arg.Type = ACPI_TYPE_INTEGER;
464 Arg.Integer.Value = 0;
470 Buf.Length = ACPI_ALLOCATE_BUFFER;
472 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
476 * The Samsung P30 returns a null-pointer from INIT, we
477 * can identify it from the 'ODEM' string in the DSDT.
479 if (Obj->String.Pointer == NULL) {
481 ACPI_TABLE_HEADER th;
483 status = AcpiGetTableHeader(ACPI_SIG_DSDT, 1, &th);
484 if (ACPI_FAILURE(status)) {
485 device_printf(dev, "Unsupported (Samsung?) laptop\n");
486 AcpiOsFree(Buf.Pointer);
490 if (strncmp("ODEM", th.OemTableId, 4) == 0) {
491 sc->model = &acpi_samsung_models[0];
492 device_set_desc(dev, "Samsung P30 Laptop Extras");
493 AcpiOsFree(Buf.Pointer);
498 if (strncmp("ASUS010", rstr, 7) == 0) {
499 sc->model = &acpi_eeepc_models[0];
500 device_set_desc(dev, "ASUS EeePC");
501 AcpiOsFree(Buf.Pointer);
506 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
511 * Asus laptops are simply identified by name, easy!
513 for (model = acpi_asus_models; model->name != NULL; model++) {
514 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
517 sbuf_printf(sb, "Asus %s Laptop Extras",
518 Obj->String.Pointer);
522 device_set_desc_copy(dev, sbuf_data(sb));
525 AcpiOsFree(Buf.Pointer);
530 * Some models look exactly the same as other models, but have
531 * their own ids. If we spot these, set them up with the same
532 * details as the models they're like, possibly dealing with
535 * XXX: there must be a prettier way to do this!
537 else if (strncmp(model->name, "xxN", 3) == 0 &&
538 (strncmp(Obj->String.Pointer, "M3N", 3) == 0 ||
539 strncmp(Obj->String.Pointer, "S1N", 3) == 0))
541 else if (strncmp(model->name, "A1x", 3) == 0 &&
542 strncmp(Obj->String.Pointer, "A1", 2) == 0)
544 else if (strncmp(model->name, "A2x", 3) == 0 &&
545 strncmp(Obj->String.Pointer, "A2", 2) == 0)
547 else if (strncmp(model->name, "D1x", 3) == 0 &&
548 strncmp(Obj->String.Pointer, "D1", 2) == 0)
550 else if (strncmp(model->name, "L3H", 3) == 0 &&
551 strncmp(Obj->String.Pointer, "L2E", 3) == 0)
553 else if (strncmp(model->name, "L5x", 3) == 0 &&
554 strncmp(Obj->String.Pointer, "L5", 2) == 0)
556 else if (strncmp(model->name, "M2E", 3) == 0 &&
557 (strncmp(Obj->String.Pointer, "M2", 2) == 0 ||
558 strncmp(Obj->String.Pointer, "L4E", 3) == 0))
560 else if (strncmp(model->name, "S1x", 3) == 0 &&
561 (strncmp(Obj->String.Pointer, "L8", 2) == 0 ||
562 strncmp(Obj->String.Pointer, "S1", 2) == 0))
564 else if (strncmp(model->name, "S2x", 3) == 0 &&
565 (strncmp(Obj->String.Pointer, "J1", 2) == 0 ||
566 strncmp(Obj->String.Pointer, "S2", 2) == 0))
569 /* L2B is like L3C but has no lcd_get method */
570 else if (strncmp(model->name, "L3C", 3) == 0 &&
571 strncmp(Obj->String.Pointer, "L2B", 3) == 0) {
572 model->lcd_get = NULL;
576 /* A3G is like M6R but with a different lcd_get method */
577 else if (strncmp(model->name, "M6R", 3) == 0 &&
578 strncmp(Obj->String.Pointer, "A3G", 3) == 0) {
579 model->lcd_get = "\\BLFG";
583 /* M2N and W1N are like xxN with added WLED */
584 else if (strncmp(model->name, "xxN", 3) == 0 &&
585 (strncmp(Obj->String.Pointer, "M2N", 3) == 0 ||
586 strncmp(Obj->String.Pointer, "W1N", 3) == 0)) {
587 model->wled_set = "WLED";
591 /* M5N and S5N are like xxN without MLED */
592 else if (strncmp(model->name, "xxN", 3) == 0 &&
593 (strncmp(Obj->String.Pointer, "M5N", 3) == 0 ||
594 strncmp(Obj->String.Pointer, "S5N", 3) == 0)) {
595 model->mled_set = NULL;
600 sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
603 device_printf(dev, sbuf_data(sb));
606 AcpiOsFree(Buf.Pointer);
612 acpi_asus_detach(device_t dev)
614 struct acpi_asus_softc *sc;
616 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
618 sc = device_get_softc(dev);
621 /* Turn the lights off */
622 /* We don't have LED support, sadly... :( */
623 /* if (sc->model->bled_set)
624 led_destroy(sc->s_bled.cdev);
626 if (sc->model->mled_set)
627 led_destroy(sc->s_mled.cdev);
629 if (sc->model->tled_set)
630 led_destroy(sc->s_tled.cdev);
632 if (sc->model->wled_set)
633 led_destroy(sc->s_wled.cdev);
635 /* Remove notify handler */
636 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
639 /* Free sysctl tree */
640 sysctl_ctx_free(&sc->sysctl_ctx);
646 acpi_asus_attach(device_t dev)
648 struct acpi_asus_softc *sc;
649 struct acpi_softc *acpi_sc;
651 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
653 sc = device_get_softc(dev);
654 acpi_sc = acpi_device_get_parent_softc(dev);
656 /* Build sysctl tree */
657 sysctl_ctx_init(&sc->sysctl_ctx);
658 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
659 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
660 OID_AUTO, "asus", CTLFLAG_RD, 0, "");
663 for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) {
664 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method))
667 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
668 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
669 acpi_asus_sysctls[i].name,
670 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
671 sc, i, acpi_asus_sysctl, "I",
672 acpi_asus_sysctls[i].description);
677 /*Currently we don't have LEDs control. Just comment this for now...*/
679 if (sc->model->bled_set) {
682 sc->s_bled.type = ACPI_ASUS_LED_BLED;
684 led_create((led_t *)acpi_asus_led, &sc->s_bled, "bled");
687 if (sc->model->mled_set) {
690 sc->s_mled.type = ACPI_ASUS_LED_MLED;
692 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
695 if (sc->model->tled_set) {
698 sc->s_tled.type = ACPI_ASUS_LED_TLED;
700 led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
703 if (sc->model->wled_set) {
706 sc->s_wled.type = ACPI_ASUS_LED_WLED;
708 led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
712 /* Activate hotkeys */
713 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
715 /* Handle notifies */
716 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
717 acpi_asus_notify, dev);
719 lockinit(&asuslock, "asus", 0, 0);
725 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
727 struct acpi_asus_softc *sc;
733 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
735 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
736 function = oidp->oid_arg2;
737 method = acpi_asus_sysctls[function].method;
739 lockmgr(&asuslock, LK_EXCLUSIVE);
740 arg = acpi_asus_sysctl_get(sc, method);
741 error = sysctl_handle_int(oidp, &arg, 0, req);
744 if (error != 0 || req->newptr == NULL)
748 error = acpi_asus_sysctl_set(sc, method, arg);
751 lockmgr(&asuslock, LK_RELEASE);
756 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method)
760 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
761 KKASSERT(lockstatus(&asuslock, NULL)!=0); /* was ACPI_SERIAL_ASSERT(asus); may be I lost something? */
764 case ACPI_ASUS_METHOD_BRN:
767 case ACPI_ASUS_METHOD_DISP:
770 case ACPI_ASUS_METHOD_LCD:
779 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg)
781 ACPI_STATUS status = AE_OK;
783 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
786 KKASSERT(lockstatus(&asuslock, NULL)!=0); /* was ACPI_SERIAL_ASSERT(asus); another miss? */
789 case ACPI_ASUS_METHOD_BRN:
790 if (arg < 0 || arg > 15)
793 if (sc->model->brn_set)
794 status = acpi_SetInteger(sc->handle,
795 sc->model->brn_set, arg);
798 status = AcpiEvaluateObject(sc->handle,
799 (arg > 0) ? sc->model->brn_up :
800 sc->model->brn_dn, NULL, NULL);
801 (arg > 0) ? arg-- : arg++;
805 if (ACPI_SUCCESS(status))
809 case ACPI_ASUS_METHOD_DISP:
810 if (arg < 0 || arg > 7)
813 status = acpi_SetInteger(sc->handle,
814 sc->model->disp_set, arg);
816 if (ACPI_SUCCESS(status))
820 case ACPI_ASUS_METHOD_LCD:
821 if (arg < 0 || arg > 1)
824 if (strncmp(sc->model->name, "L3H", 3) != 0)
825 status = AcpiEvaluateObject(sc->handle,
826 sc->model->lcd_set, NULL, NULL);
828 status = acpi_SetInteger(sc->handle,
829 sc->model->lcd_set, 0x7);
831 if (ACPI_SUCCESS(status))
841 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method)
846 case ACPI_ASUS_METHOD_BRN:
847 if (sc->model->brn_get) {
848 /* GPLV/SPLV models */
849 status = acpi_GetInteger(sc->handle,
850 sc->model->brn_get, &sc->s_brn);
851 if (ACPI_SUCCESS(status))
853 } else if (sc->model->brn_up) {
854 /* Relative models */
855 status = AcpiEvaluateObject(sc->handle,
856 sc->model->brn_up, NULL, NULL);
857 if (ACPI_FAILURE(status))
860 status = AcpiEvaluateObject(sc->handle,
861 sc->model->brn_dn, NULL, NULL);
862 if (ACPI_FAILURE(status))
868 case ACPI_ASUS_METHOD_DISP:
869 if (sc->model->disp_get) {
870 status = acpi_GetInteger(sc->handle,
871 sc->model->disp_get, &sc->s_disp);
872 if (ACPI_SUCCESS(status))
876 case ACPI_ASUS_METHOD_LCD:
877 if (sc->model->lcd_get &&
878 strncmp(sc->model->name, "L3H", 3) != 0) {
879 status = acpi_GetInteger(sc->handle,
880 sc->model->lcd_get, &sc->s_lcd);
881 if (ACPI_SUCCESS(status))
884 else if (sc->model->lcd_get) {
886 ACPI_OBJECT Arg[2], Obj;
887 ACPI_OBJECT_LIST Args;
889 /* L3H is a bit special */
890 Arg[0].Type = ACPI_TYPE_INTEGER;
891 Arg[0].Integer.Value = 0x02;
892 Arg[1].Type = ACPI_TYPE_INTEGER;
893 Arg[1].Integer.Value = 0x03;
898 Buf.Length = sizeof(Obj);
901 status = AcpiEvaluateObject(sc->handle,
902 sc->model->lcd_get, &Args, &Buf);
903 if (ACPI_SUCCESS(status) &&
904 Obj.Type == ACPI_TYPE_INTEGER) {
905 sc->s_lcd = Obj.Integer.Value >> 8;
915 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
917 struct acpi_asus_softc *sc;
918 struct acpi_softc *acpi_sc;
920 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
922 sc = device_get_softc((device_t)context);
923 acpi_sc = acpi_device_get_parent_softc(sc->dev);
925 lockmgr(&asuslock, LK_EXCLUSIVE);
926 if ((notify & ~0x10) <= 15) {
927 sc->s_brn = notify & ~0x10;
928 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
929 } else if ((notify & ~0x20) <= 15) {
930 sc->s_brn = notify & ~0x20;
931 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
932 } else if (notify == 0x33) {
934 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
935 } else if (notify == 0x34) {
937 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
940 acpi_UserNotify("ASUS", h, notify);
942 lockmgr(&asuslock, LK_RELEASE);