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>
64 #define ACPI_ASUS_METHOD_BRN 1
65 #define ACPI_ASUS_METHOD_DISP 2
66 #define ACPI_ASUS_METHOD_LCD 3
68 #define _COMPONENT ACPI_OEM
69 ACPI_MODULE_NAME("ASUS")
71 struct acpi_asus_model {
91 struct acpi_asus_led {
92 struct acpi_asus_softc *sc;
104 struct acpi_asus_softc {
108 struct acpi_asus_model *model;
109 struct sysctl_ctx_list sysctl_ctx;
110 struct sysctl_oid *sysctl_tree;
112 struct acpi_asus_led s_bled;
113 struct acpi_asus_led s_mled;
114 struct acpi_asus_led s_tled;
115 struct acpi_asus_led s_wled;
123 * We can identify Asus laptops from the string they return
124 * as a result of calling the ATK0100 'INIT' method.
126 static struct acpi_asus_model acpi_asus_models[] = {
132 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
135 .disp_get = "\\ADVG",
142 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10",
143 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E",
144 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F"
154 .disp_get = "\\INFB",
163 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
167 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD"
172 .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E",
173 .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F",
177 .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10",
178 .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11"
187 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
190 .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD",
200 .disp_get = "\\INFB",
219 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10"
236 .lcd_get = "\\_SB.PCI0.PM.PBC",
238 .disp_get = "\\_SB.INFB",
247 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
248 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
249 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD",
260 .disp_get = "\\INFB",
265 /* Only has hotkeys, apparantly */
270 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E",
271 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F",
273 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10"
288 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
289 .lcd_get = "\\_SB.BKLT",
301 .lcd_get = "\\_SB.PCI0.SBSM.SEO4",
302 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
303 .disp_get = "\\SSTE",
311 .lcd_set = "\\_SB.PCI0.PX40.Q10",
319 .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10",
320 .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B",
321 .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A"
329 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
332 .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD",
339 .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10",
342 .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD",
350 * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
351 * but they can't be probed quite the same way as Asus laptops.
353 static struct acpi_asus_model acpi_samsung_models[] = {
357 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68",
358 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69",
360 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E"
367 * EeePC have an Asus ASUS010 gadget interface,
368 * but they can't be probed quite the same way as Asus laptops.
370 static struct acpi_asus_model acpi_eeepc_models[] = {
373 .brn_get = "\\_SB.ATKD.PBLG",
374 .brn_set = "\\_SB.ATKD.PBLS"
384 } acpi_asus_sysctls[] = {
386 .name = "lcd_backlight",
387 .method = ACPI_ASUS_METHOD_LCD,
388 .description = "state of the lcd backlight"
391 .name = "lcd_brightness",
392 .method = ACPI_ASUS_METHOD_BRN,
393 .description = "brightness of the lcd panel"
396 .name = "video_output",
397 .method = ACPI_ASUS_METHOD_DISP,
398 .description = "display output state"
404 static int acpi_asus_probe(device_t dev);
405 static int acpi_asus_attach(device_t dev);
406 static int acpi_asus_detach(device_t dev);
408 static struct lock asuslock;
410 static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
411 static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
412 static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method);
413 static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val);
415 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
417 static device_method_t acpi_asus_methods[] = {
418 /* Device interface */
419 DEVMETHOD(device_probe, acpi_asus_probe),
420 DEVMETHOD(device_attach, acpi_asus_attach),
421 DEVMETHOD(device_detach, acpi_asus_detach),
425 static driver_t acpi_asus_driver = {
428 sizeof(struct acpi_asus_softc),
431 static devclass_t acpi_asus_devclass;
433 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver,
434 acpi_asus_devclass, 0, 0);
435 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
437 static char *asus_ids[] = { "ATK0100", "ASUS010", NULL };
440 acpi_asus_probe(device_t dev)
442 struct acpi_asus_model *model;
443 struct acpi_asus_softc *sc;
446 ACPI_OBJECT Arg, *Obj;
447 ACPI_OBJECT_LIST Args;
450 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
452 if (acpi_disabled("asus")) return (ENXIO);
453 rstr = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids);
459 sc = device_get_softc(dev);
461 sc->handle = acpi_get_handle(dev);
464 Arg.Type = ACPI_TYPE_INTEGER;
465 Arg.Integer.Value = 0;
471 Buf.Length = ACPI_ALLOCATE_BUFFER;
473 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
477 * The Samsung P30 returns a null-pointer from INIT, we
478 * can identify it from the 'ODEM' string in the DSDT.
480 if (Obj->String.Pointer == NULL) {
482 ACPI_TABLE_HEADER th;
484 status = AcpiGetTableHeader(ACPI_SIG_DSDT, 1, &th);
485 if (ACPI_FAILURE(status)) {
486 device_printf(dev, "Unsupported (Samsung?) laptop\n");
487 AcpiOsFree(Buf.Pointer);
491 if (strncmp("ODEM", th.OemTableId, 4) == 0) {
492 sc->model = &acpi_samsung_models[0];
493 device_set_desc(dev, "Samsung P30 Laptop Extras");
494 AcpiOsFree(Buf.Pointer);
499 if (strncmp("ASUS010", rstr, 7) == 0) {
500 sc->model = &acpi_eeepc_models[0];
501 device_set_desc(dev, "ASUS EeePC");
502 AcpiOsFree(Buf.Pointer);
507 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
512 * Asus laptops are simply identified by name, easy!
514 for (model = acpi_asus_models; model->name != NULL; model++) {
515 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
518 sbuf_printf(sb, "Asus %s Laptop Extras",
519 Obj->String.Pointer);
523 device_set_desc_copy(dev, sbuf_data(sb));
526 AcpiOsFree(Buf.Pointer);
531 * Some models look exactly the same as other models, but have
532 * their own ids. If we spot these, set them up with the same
533 * details as the models they're like, possibly dealing with
536 * XXX: there must be a prettier way to do this!
538 else if (strncmp(model->name, "xxN", 3) == 0 &&
539 (strncmp(Obj->String.Pointer, "M3N", 3) == 0 ||
540 strncmp(Obj->String.Pointer, "S1N", 3) == 0))
542 else if (strncmp(model->name, "A1x", 3) == 0 &&
543 strncmp(Obj->String.Pointer, "A1", 2) == 0)
545 else if (strncmp(model->name, "A2x", 3) == 0 &&
546 strncmp(Obj->String.Pointer, "A2", 2) == 0)
548 else if (strncmp(model->name, "D1x", 3) == 0 &&
549 strncmp(Obj->String.Pointer, "D1", 2) == 0)
551 else if (strncmp(model->name, "L3H", 3) == 0 &&
552 strncmp(Obj->String.Pointer, "L2E", 3) == 0)
554 else if (strncmp(model->name, "L5x", 3) == 0 &&
555 strncmp(Obj->String.Pointer, "L5", 2) == 0)
557 else if (strncmp(model->name, "M2E", 3) == 0 &&
558 (strncmp(Obj->String.Pointer, "M2", 2) == 0 ||
559 strncmp(Obj->String.Pointer, "L4E", 3) == 0))
561 else if (strncmp(model->name, "S1x", 3) == 0 &&
562 (strncmp(Obj->String.Pointer, "L8", 2) == 0 ||
563 strncmp(Obj->String.Pointer, "S1", 2) == 0))
565 else if (strncmp(model->name, "S2x", 3) == 0 &&
566 (strncmp(Obj->String.Pointer, "J1", 2) == 0 ||
567 strncmp(Obj->String.Pointer, "S2", 2) == 0))
570 /* L2B is like L3C but has no lcd_get method */
571 else if (strncmp(model->name, "L3C", 3) == 0 &&
572 strncmp(Obj->String.Pointer, "L2B", 3) == 0) {
573 model->lcd_get = NULL;
577 /* A3G is like M6R but with a different lcd_get method */
578 else if (strncmp(model->name, "M6R", 3) == 0 &&
579 strncmp(Obj->String.Pointer, "A3G", 3) == 0) {
580 model->lcd_get = "\\BLFG";
584 /* M2N and W1N are like xxN with added WLED */
585 else if (strncmp(model->name, "xxN", 3) == 0 &&
586 (strncmp(Obj->String.Pointer, "M2N", 3) == 0 ||
587 strncmp(Obj->String.Pointer, "W1N", 3) == 0)) {
588 model->wled_set = "WLED";
592 /* M5N and S5N are like xxN without MLED */
593 else if (strncmp(model->name, "xxN", 3) == 0 &&
594 (strncmp(Obj->String.Pointer, "M5N", 3) == 0 ||
595 strncmp(Obj->String.Pointer, "S5N", 3) == 0)) {
596 model->mled_set = NULL;
601 sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
604 device_printf(dev, sbuf_data(sb));
607 AcpiOsFree(Buf.Pointer);
613 acpi_asus_detach(device_t dev)
615 struct acpi_asus_softc *sc;
617 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
619 sc = device_get_softc(dev);
622 /* Turn the lights off */
623 /* We don't have LED support, sadly... :( */
624 /* if (sc->model->bled_set)
625 led_destroy(sc->s_bled.cdev);
627 if (sc->model->mled_set)
628 led_destroy(sc->s_mled.cdev);
630 if (sc->model->tled_set)
631 led_destroy(sc->s_tled.cdev);
633 if (sc->model->wled_set)
634 led_destroy(sc->s_wled.cdev);
636 /* Remove notify handler */
637 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
640 /* Free sysctl tree */
641 sysctl_ctx_free(&sc->sysctl_ctx);
647 acpi_asus_attach(device_t dev)
649 struct acpi_asus_softc *sc;
650 struct acpi_softc *acpi_sc;
652 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
654 sc = device_get_softc(dev);
655 acpi_sc = acpi_device_get_parent_softc(dev);
657 /* Build sysctl tree */
658 sysctl_ctx_init(&sc->sysctl_ctx);
659 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
660 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
661 OID_AUTO, "asus", CTLFLAG_RD, 0, "");
664 for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) {
665 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method))
668 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
669 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
670 acpi_asus_sysctls[i].name,
671 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
672 sc, i, acpi_asus_sysctl, "I",
673 acpi_asus_sysctls[i].description);
678 /*Currently we don't have LEDs control. Just comment this for now...*/
680 if (sc->model->bled_set) {
683 sc->s_bled.type = ACPI_ASUS_LED_BLED;
685 led_create((led_t *)acpi_asus_led, &sc->s_bled, "bled");
688 if (sc->model->mled_set) {
691 sc->s_mled.type = ACPI_ASUS_LED_MLED;
693 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
696 if (sc->model->tled_set) {
699 sc->s_tled.type = ACPI_ASUS_LED_TLED;
701 led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
704 if (sc->model->wled_set) {
707 sc->s_wled.type = ACPI_ASUS_LED_WLED;
709 led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
713 /* Activate hotkeys */
714 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
716 /* Handle notifies */
717 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
718 acpi_asus_notify, dev);
720 lockinit(&asuslock, "asus", 0, 0);
726 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
728 struct acpi_asus_softc *sc;
734 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
736 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
737 function = oidp->oid_arg2;
738 method = acpi_asus_sysctls[function].method;
740 lockmgr(&asuslock, LK_EXCLUSIVE);
741 arg = acpi_asus_sysctl_get(sc, method);
742 error = sysctl_handle_int(oidp, &arg, 0, req);
745 if (error != 0 || req->newptr == NULL)
749 error = acpi_asus_sysctl_set(sc, method, arg);
752 lockmgr(&asuslock, LK_RELEASE);
757 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method)
761 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
762 KKASSERT(lockstatus(&asuslock, NULL)!=0); /* was ACPI_SERIAL_ASSERT(asus); may be I lost something? */
765 case ACPI_ASUS_METHOD_BRN:
768 case ACPI_ASUS_METHOD_DISP:
771 case ACPI_ASUS_METHOD_LCD:
780 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg)
782 ACPI_STATUS status = AE_OK;
784 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
787 KKASSERT(lockstatus(&asuslock, NULL)!=0); /* was ACPI_SERIAL_ASSERT(asus); another miss? */
790 case ACPI_ASUS_METHOD_BRN:
791 if (arg < 0 || arg > 15)
794 if (sc->model->brn_set)
795 status = acpi_SetInteger(sc->handle,
796 sc->model->brn_set, arg);
799 status = AcpiEvaluateObject(sc->handle,
800 (arg > 0) ? sc->model->brn_up :
801 sc->model->brn_dn, NULL, NULL);
802 (arg > 0) ? arg-- : arg++;
806 if (ACPI_SUCCESS(status))
810 case ACPI_ASUS_METHOD_DISP:
811 if (arg < 0 || arg > 7)
814 status = acpi_SetInteger(sc->handle,
815 sc->model->disp_set, arg);
817 if (ACPI_SUCCESS(status))
821 case ACPI_ASUS_METHOD_LCD:
822 if (arg < 0 || arg > 1)
825 if (strncmp(sc->model->name, "L3H", 3) != 0)
826 status = AcpiEvaluateObject(sc->handle,
827 sc->model->lcd_set, NULL, NULL);
829 status = acpi_SetInteger(sc->handle,
830 sc->model->lcd_set, 0x7);
832 if (ACPI_SUCCESS(status))
842 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method)
847 case ACPI_ASUS_METHOD_BRN:
848 if (sc->model->brn_get) {
849 /* GPLV/SPLV models */
850 status = acpi_GetInteger(sc->handle,
851 sc->model->brn_get, &sc->s_brn);
852 if (ACPI_SUCCESS(status))
854 } else if (sc->model->brn_up) {
855 /* Relative models */
856 status = AcpiEvaluateObject(sc->handle,
857 sc->model->brn_up, NULL, NULL);
858 if (ACPI_FAILURE(status))
861 status = AcpiEvaluateObject(sc->handle,
862 sc->model->brn_dn, NULL, NULL);
863 if (ACPI_FAILURE(status))
869 case ACPI_ASUS_METHOD_DISP:
870 if (sc->model->disp_get) {
871 status = acpi_GetInteger(sc->handle,
872 sc->model->disp_get, &sc->s_disp);
873 if (ACPI_SUCCESS(status))
877 case ACPI_ASUS_METHOD_LCD:
878 if (sc->model->lcd_get &&
879 strncmp(sc->model->name, "L3H", 3) != 0) {
880 status = acpi_GetInteger(sc->handle,
881 sc->model->lcd_get, &sc->s_lcd);
882 if (ACPI_SUCCESS(status))
885 else if (sc->model->lcd_get) {
887 ACPI_OBJECT Arg[2], Obj;
888 ACPI_OBJECT_LIST Args;
890 /* L3H is a bit special */
891 Arg[0].Type = ACPI_TYPE_INTEGER;
892 Arg[0].Integer.Value = 0x02;
893 Arg[1].Type = ACPI_TYPE_INTEGER;
894 Arg[1].Integer.Value = 0x03;
899 Buf.Length = sizeof(Obj);
902 status = AcpiEvaluateObject(sc->handle,
903 sc->model->lcd_get, &Args, &Buf);
904 if (ACPI_SUCCESS(status) &&
905 Obj.Type == ACPI_TYPE_INTEGER) {
906 sc->s_lcd = Obj.Integer.Value >> 8;
916 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
918 struct acpi_asus_softc *sc;
919 struct acpi_softc *acpi_sc;
921 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
923 sc = device_get_softc((device_t)context);
924 acpi_sc = acpi_device_get_parent_softc(sc->dev);
926 lockmgr(&asuslock, LK_EXCLUSIVE);
927 if ((notify & ~0x10) <= 15) {
928 sc->s_brn = notify & ~0x10;
929 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
930 } else if ((notify & ~0x20) <= 15) {
931 sc->s_brn = notify & ~0x20;
932 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
933 } else if (notify == 0x33) {
935 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
936 } else if (notify == 0x34) {
938 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
941 acpi_UserNotify("ASUS", h, notify);
943 lockmgr(&asuslock, LK_RELEASE);