]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/acpi_support/acpi_asus.c
add -n option to suppress clearing the build tree and add -DNO_CLEAN
[FreeBSD/FreeBSD.git] / sys / dev / acpi_support / acpi_asus.c
1 /*-
2  * Copyright (c) 2004, 2005 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 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29
30 /*
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.
34  *
35  *   <http://sourceforge.net/projects/acpi4asus/>
36  *
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>.
41  */
42
43 #include "opt_acpi.h"
44 #include <sys/param.h>
45 #include <sys/kernel.h>
46 #include <sys/module.h>
47 #include <sys/bus.h>
48 #include <sys/sbuf.h>
49
50 #include <contrib/dev/acpica/acpi.h>
51 #include <dev/acpica/acpivar.h>
52 #include <dev/led/led.h>
53
54 /* Methods */
55 #define ACPI_ASUS_METHOD_BRN    1
56 #define ACPI_ASUS_METHOD_DISP   2
57 #define ACPI_ASUS_METHOD_LCD    3
58 #define ACPI_ASUS_METHOD_CAMERA 4
59 #define ACPI_ASUS_METHOD_CARDRD 5
60 #define ACPI_ASUS_METHOD_WLAN   6
61
62 #define _COMPONENT      ACPI_OEM
63 ACPI_MODULE_NAME("ASUS")
64
65 struct acpi_asus_model {
66         char    *name;
67
68         char    *bled_set;
69         char    *dled_set;
70         char    *gled_set;
71         char    *mled_set;
72         char    *tled_set;
73         char    *wled_set;
74
75         char    *brn_get;
76         char    *brn_set;
77         char    *brn_up;
78         char    *brn_dn;
79
80         char    *lcd_get;
81         char    *lcd_set;
82
83         char    *disp_get;
84         char    *disp_set;
85
86         char    *cam_get;
87         char    *cam_set;
88
89         char    *crd_get;
90         char    *crd_set;
91
92         char    *wlan_get;
93         char    *wlan_set;
94
95         void    (*n_func)(ACPI_HANDLE, UINT32, void *);
96 };
97
98 struct acpi_asus_led {
99         struct acpi_asus_softc *sc;
100         struct cdev     *cdev;
101         int             busy;
102         int             state;
103         enum {
104                 ACPI_ASUS_LED_BLED,
105                 ACPI_ASUS_LED_DLED,
106                 ACPI_ASUS_LED_GLED,
107                 ACPI_ASUS_LED_MLED,
108                 ACPI_ASUS_LED_TLED,
109                 ACPI_ASUS_LED_WLED,
110         } type;
111 };
112
113 struct acpi_asus_softc {
114         device_t                dev;
115         ACPI_HANDLE             handle;
116
117         struct acpi_asus_model  *model;
118         struct sysctl_ctx_list  sysctl_ctx;
119         struct sysctl_oid       *sysctl_tree;
120
121         struct acpi_asus_led    s_bled;
122         struct acpi_asus_led    s_dled;
123         struct acpi_asus_led    s_gled;
124         struct acpi_asus_led    s_mled;
125         struct acpi_asus_led    s_tled;
126         struct acpi_asus_led    s_wled;
127
128         int                     s_brn;
129         int                     s_disp;
130         int                     s_lcd;
131         int                     s_cam;
132         int                     s_crd;
133         int                     s_wlan;
134 };
135
136 /*
137  * We can identify Asus laptops from the string they return
138  * as a result of calling the ATK0100 'INIT' method.
139  */
140 static struct acpi_asus_model acpi_asus_models[] = {
141         {
142                 .name           = "xxN",
143                 .mled_set       = "MLED",
144                 .wled_set       = "WLED",
145                 .lcd_get        = "\\BKLT",
146                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
147                 .brn_get        = "GPLV",
148                 .brn_set        = "SPLV",
149                 .disp_get       = "\\ADVG",
150                 .disp_set       = "SDSP"
151         },
152         {
153                 .name           = "A1x",
154                 .mled_set       = "MLED",
155                 .lcd_get        = "\\BKLI",
156                 .lcd_set        = "\\_SB.PCI0.ISA.EC0._Q10",
157                 .brn_up         = "\\_SB.PCI0.ISA.EC0._Q0E",
158                 .brn_dn         = "\\_SB.PCI0.ISA.EC0._Q0F"
159         },
160         {
161                 .name           = "A2x",
162                 .mled_set       = "MLED",
163                 .wled_set       = "WLED",
164                 .lcd_get        = "\\BAOF",
165                 .lcd_set        = "\\Q10",
166                 .brn_get        = "GPLV",
167                 .brn_set        = "SPLV",
168                 .disp_get       = "\\INFB",
169                 .disp_set       = "SDSP"
170         },
171         {
172                 .name           = "A3N",
173                 .mled_set       = "MLED",
174                 .bled_set       = "BLED",
175                 .wled_set       = "WLED",
176                 .lcd_get        = NULL,
177                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
178                 .brn_set        = "SPLV",
179                 .brn_get        = "SDSP",
180                 .disp_set       = "SDSP",
181                 .disp_get       = "\\_SB.PCI0.P0P3.VGA.GETD"
182         },
183         {
184                 .name           = "A4D",
185                 .mled_set       = "MLED",
186                 .brn_up         = "\\_SB_.PCI0.SBRG.EC0._Q0E",
187                 .brn_dn         = "\\_SB_.PCI0.SBRG.EC0._Q0F",
188                 .brn_get        = "GPLV",
189                 .brn_set        = "SPLV",
190 #ifdef notyet
191                 .disp_get       = "\\_SB_.PCI0.SBRG.EC0._Q10",
192                 .disp_set       = "\\_SB_.PCI0.SBRG.EC0._Q11"
193 #endif
194         },
195         {
196                 .name           = "A6V",
197                 .bled_set       = "BLED",
198                 .mled_set       = "MLED",
199                 .wled_set       = "WLED",
200                 .lcd_get        = NULL,
201                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
202                 .brn_get        = "GPLV",
203                 .brn_set        = "SPLV",
204                 .disp_get       = "\\_SB.PCI0.P0P3.VGA.GETD",
205                 .disp_set       = "SDSP"
206         },
207         {
208                 .name           = "D1x",
209                 .mled_set       = "MLED",
210                 .lcd_get        = "\\GP11",
211                 .lcd_set        = "\\Q0D",
212                 .brn_up         = "\\Q0C",
213                 .brn_dn         = "\\Q0B",
214                 .disp_get       = "\\INFB",
215                 .disp_set       = "SDSP"
216         },
217         {
218                 .name           = "G2K",
219                 .bled_set       = "BLED",
220                 .dled_set       = "DLED",
221                 .gled_set       = "GLED",
222                 .mled_set       = "MLED",
223                 .tled_set       = "TLED",
224                 .wled_set       = "WLED",
225                 .brn_get        = "GPLV",
226                 .brn_set        = "SPLV",
227                 .lcd_get        = "\\_SB.PCI0.SBRG.EC0.RPIN",
228                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
229                 .disp_get       = "\\_SB.PCI0.PCE2.VGA.GETD",
230                 .disp_set       = "SDSP",
231         },
232         {
233                 .name           = "L2D",
234                 .mled_set       = "MLED",
235                 .wled_set       = "WLED",
236                 .brn_up         = "\\Q0E",
237                 .brn_dn         = "\\Q0F",
238                 .lcd_get        = "\\SGP0",
239                 .lcd_set        = "\\Q10"
240         },
241         {
242                 .name           = "L3C",
243                 .mled_set       = "MLED",
244                 .wled_set       = "WLED",
245                 .brn_get        = "GPLV",
246                 .brn_set        = "SPLV",
247                 .lcd_get        = "\\GL32",
248                 .lcd_set        = "\\_SB.PCI0.PX40.ECD0._Q10"
249         },
250         {
251                 .name           = "L3D",
252                 .mled_set       = "MLED",
253                 .wled_set       = "WLED",
254                 .brn_get        = "GPLV",
255                 .brn_set        = "SPLV",
256                 .lcd_get        = "\\BKLG",
257                 .lcd_set        = "\\Q10"
258         },
259         {
260                 .name           = "L3H",
261                 .mled_set       = "MLED",
262                 .wled_set       = "WLED",
263                 .brn_get        = "GPLV",
264                 .brn_set        = "SPLV",
265                 .lcd_get        = "\\_SB.PCI0.PM.PBC",
266                 .lcd_set        = "EHK",
267                 .disp_get       = "\\_SB.INFB",
268                 .disp_set       = "SDSP"
269         },
270         {
271                 .name           = "L4R",
272                 .mled_set       = "MLED",
273                 .wled_set       = "WLED",
274                 .brn_get        = "GPLV",
275                 .brn_set        = "SPLV",
276                 .lcd_get        = "\\_SB.PCI0.SBSM.SEO4",
277                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
278                 .disp_get       = "\\_SB.PCI0.P0P1.VGA.GETD",
279                 .disp_set       = "SDSP"
280         },
281         {
282                 .name           = "L5x",
283                 .mled_set       = "MLED",
284                 .tled_set       = "TLED",
285                 .lcd_get        = "\\BAOF",
286                 .lcd_set        = "\\Q0D",
287                 .brn_get        = "GPLV",
288                 .brn_set        = "SPLV",
289                 .disp_get       = "\\INFB",
290                 .disp_set       = "SDSP"
291         },
292         {
293                 .name           = "L8L"
294                 /* Only has hotkeys, apparently */
295         },
296         {
297                 .name           = "M1A",
298                 .mled_set       = "MLED",
299                 .brn_up         = "\\_SB.PCI0.PX40.EC0.Q0E",
300                 .brn_dn         = "\\_SB.PCI0.PX40.EC0.Q0F",
301                 .lcd_get        = "\\PNOF",
302                 .lcd_set        = "\\_SB.PCI0.PX40.EC0.Q10"
303         },
304         {
305                 .name           = "M2E",
306                 .mled_set       = "MLED",
307                 .wled_set       = "WLED",
308                 .brn_get        = "GPLV",
309                 .brn_set        = "SPLV",
310                 .lcd_get        = "\\GP06",
311                 .lcd_set        = "\\Q10"
312         },
313         {
314                 .name           = "M6N",
315                 .mled_set       = "MLED",
316                 .wled_set       = "WLED",
317                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
318                 .lcd_get        = "\\_SB.BKLT",
319                 .brn_set        = "SPLV",
320                 .brn_get        = "GPLV",
321                 .disp_set       = "SDSP",
322                 .disp_get       = "\\SSTE"
323         },
324         {
325                 .name           = "M6R",
326                 .mled_set       = "MLED",
327                 .wled_set       = "WLED",
328                 .brn_get        = "GPLV",
329                 .brn_set        = "SPLV",
330                 .lcd_get        = "\\_SB.PCI0.SBSM.SEO4",
331                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
332                 .disp_get       = "\\SSTE",
333                 .disp_set       = "SDSP"
334         },
335         {
336                 .name           = "S1x",
337                 .mled_set       = "MLED",
338                 .wled_set       = "WLED",
339                 .lcd_get        = "\\PNOF",
340                 .lcd_set        = "\\_SB.PCI0.PX40.Q10",
341                 .brn_get        = "GPLV",
342                 .brn_set        = "SPLV"
343         },
344         {
345                 .name           = "S2x",
346                 .mled_set       = "MLED",
347                 .lcd_get        = "\\BKLI",
348                 .lcd_set        = "\\_SB.PCI0.ISA.EC0._Q10",
349                 .brn_up         = "\\_SB.PCI0.ISA.EC0._Q0B",
350                 .brn_dn         = "\\_SB.PCI0.ISA.EC0._Q0A"
351         },
352         {
353                 .name           = "V6V",
354                 .bled_set       = "BLED",
355                 .tled_set       = "TLED",
356                 .wled_set       = "WLED",
357                 .lcd_get        = "\\BKLT",
358                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
359                 .brn_get        = "GPLV",
360                 .brn_set        = "SPLV",
361                 .disp_get       = "\\_SB.PCI0.P0P1.VGA.GETD",
362                 .disp_set       = "SDSP"
363         },
364         {
365                 .name           = "W5A",
366                 .bled_set       = "BLED",
367                 .lcd_get        = "\\BKLT",
368                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
369                 .brn_get        = "GPLV",
370                 .brn_set        = "SPLV",
371                 .disp_get       = "\\_SB.PCI0.P0P2.VGA.GETD",
372                 .disp_set       = "SDSP"
373         },
374
375         { .name = NULL }
376 };
377
378 /*
379  * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
380  * but they can't be probed quite the same way as Asus laptops.
381  */
382 static struct acpi_asus_model acpi_samsung_models[] = {
383         {
384                 .name           = "P30",
385                 .wled_set       = "WLED",
386                 .brn_up         = "\\_SB.PCI0.LPCB.EC0._Q68",
387                 .brn_dn         = "\\_SB.PCI0.LPCB.EC0._Q69",
388                 .lcd_get        = "\\BKLT",
389                 .lcd_set        = "\\_SB.PCI0.LPCB.EC0._Q0E"
390         },
391
392         { .name = NULL }
393 };
394
395 static void     acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context);
396
397 /*
398  * EeePC have an Asus ASUS010 gadget interface,
399  * but they can't be probed quite the same way as Asus laptops.
400  */
401 static struct acpi_asus_model acpi_eeepc_models[] = {
402         {
403                 .name           = "EEE",
404                 .brn_get        = "\\_SB.ATKD.PBLG",
405                 .brn_set        = "\\_SB.ATKD.PBLS",
406                 .cam_get        = "\\_SB.ATKD.CAMG",
407                 .cam_set        = "\\_SB.ATKD.CAMS",
408                 .crd_set        = "\\_SB.ATKD.CRDS",
409                 .crd_get        = "\\_SB.ATKD.CRDG",
410                 .wlan_get       = "\\_SB.ATKD.WLDG",
411                 .wlan_set       = "\\_SB.ATKD.WLDS",
412                 .n_func         = acpi_asus_eeepc_notify
413         },
414
415         { .name = NULL }
416 };
417
418 static struct {
419         char    *name;
420         char    *description;
421         int     method;
422         int     flags;
423 } acpi_asus_sysctls[] = {
424         {
425                 .name           = "lcd_backlight",
426                 .method         = ACPI_ASUS_METHOD_LCD,
427                 .description    = "state of the lcd backlight",
428                 .flags          = CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY
429         },
430         {
431                 .name           = "lcd_brightness",
432                 .method         = ACPI_ASUS_METHOD_BRN,
433                 .description    = "brightness of the lcd panel",
434                 .flags          = CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY
435         },
436         {
437                 .name           = "video_output",
438                 .method         = ACPI_ASUS_METHOD_DISP,
439                 .description    = "display output state",
440                 .flags          = CTLTYPE_INT | CTLFLAG_RW
441         },
442         {
443                 .name           = "camera",
444                 .method         = ACPI_ASUS_METHOD_CAMERA,
445                 .description    = "internal camera state",  
446                 .flags          = CTLTYPE_INT | CTLFLAG_RW
447         },
448         {
449                 .name           = "cardreader",
450                 .method         = ACPI_ASUS_METHOD_CARDRD,
451                 .description    = "internal card reader state",
452                 .flags          = CTLTYPE_INT | CTLFLAG_RW
453         },
454         {
455                 .name           = "wlan",
456                 .method         = ACPI_ASUS_METHOD_WLAN,
457                 .description    = "wireless lan state",
458                 .flags          = CTLTYPE_INT | CTLFLAG_RW
459         },
460
461         { .name = NULL }
462 };
463
464 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras");
465
466 /* Function prototypes */
467 static int      acpi_asus_probe(device_t dev);
468 static int      acpi_asus_attach(device_t dev);
469 static int      acpi_asus_detach(device_t dev);
470
471 static void     acpi_asus_led(struct acpi_asus_led *led, int state);
472 static void     acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused);
473
474 static int      acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
475 static int      acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
476 static int      acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method);
477 static int      acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val);
478
479 static void     acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
480
481 static device_method_t acpi_asus_methods[] = {
482         DEVMETHOD(device_probe,  acpi_asus_probe),
483         DEVMETHOD(device_attach, acpi_asus_attach),
484         DEVMETHOD(device_detach, acpi_asus_detach),
485
486         { 0, 0 }
487 };
488
489 static driver_t acpi_asus_driver = {
490         "acpi_asus",
491         acpi_asus_methods,
492         sizeof(struct acpi_asus_softc)
493 };
494
495 static devclass_t acpi_asus_devclass;
496
497 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
498 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
499
500 static int
501 acpi_asus_probe(device_t dev)
502 {
503         struct acpi_asus_model  *model;
504         struct acpi_asus_softc  *sc;
505         struct sbuf             *sb;
506         ACPI_BUFFER             Buf;
507         ACPI_OBJECT             Arg, *Obj;
508         ACPI_OBJECT_LIST        Args;
509         static char             *asus_ids[] = { "ATK0100", "ASUS010", NULL };
510         char *rstr;
511
512         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
513
514         if (acpi_disabled("asus"))
515                 return (ENXIO);
516         rstr = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids);
517         if (rstr == NULL) {
518                 return (ENXIO);
519         }
520
521         sc = device_get_softc(dev);
522         sc->dev = dev;
523         sc->handle = acpi_get_handle(dev);
524
525         Arg.Type = ACPI_TYPE_INTEGER;
526         Arg.Integer.Value = 0;
527
528         Args.Count = 1;
529         Args.Pointer = &Arg;
530
531         Buf.Pointer = NULL;
532         Buf.Length = ACPI_ALLOCATE_BUFFER;
533
534         AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
535         Obj = Buf.Pointer;
536
537         /*
538          * The Samsung P30 returns a null-pointer from INIT, we
539          * can identify it from the 'ODEM' string in the DSDT.
540          */
541         if (Obj->String.Pointer == NULL) {
542                 ACPI_STATUS             status;
543                 ACPI_TABLE_HEADER       th;
544
545                 status = AcpiGetTableHeader(ACPI_SIG_DSDT, 0, &th);
546                 if (ACPI_FAILURE(status)) {
547                         device_printf(dev, "Unsupported (Samsung?) laptop\n");
548                         AcpiOsFree(Buf.Pointer);
549                         return (ENXIO);
550                 }
551
552                 if (strncmp("ODEM", th.OemTableId, 4) == 0) {
553                         sc->model = &acpi_samsung_models[0];
554                         device_set_desc(dev, "Samsung P30 Laptop Extras");
555                         AcpiOsFree(Buf.Pointer);
556                         return (0);
557                 }
558
559                 /* if EeePC */
560                 if (strncmp("ASUS010", rstr, 7) == 0) {
561                         sc->model = &acpi_eeepc_models[0];
562                         device_set_desc(dev, "ASUS EeePC");
563                         AcpiOsFree(Buf.Pointer);
564                         return (0);
565                 }
566         }
567
568         sb = sbuf_new_auto();
569         if (sb == NULL)
570                 return (ENOMEM);
571
572         /*
573          * Asus laptops are simply identified by name, easy!
574          */
575         for (model = acpi_asus_models; model->name != NULL; model++) {
576                 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
577
578 good:
579                         sbuf_printf(sb, "Asus %s Laptop Extras",
580                             Obj->String.Pointer);
581                         sbuf_finish(sb);
582
583                         sc->model = model;
584                         device_set_desc_copy(dev, sbuf_data(sb));
585
586                         sbuf_delete(sb);
587                         AcpiOsFree(Buf.Pointer);
588                         return (0);
589                 }
590                 
591                 /*
592                  * Some models look exactly the same as other models, but have
593                  * their own ids.  If we spot these, set them up with the same
594                  * details as the models they're like, possibly dealing with
595                  * small differences.
596                  *
597                  * XXX: there must be a prettier way to do this!
598                  */
599                 else if (strncmp(model->name, "xxN", 3) == 0 &&
600                     (strncmp(Obj->String.Pointer, "M3N", 3) == 0 ||
601                      strncmp(Obj->String.Pointer, "S1N", 3) == 0))
602                         goto good;
603                 else if (strncmp(model->name, "A1x", 3) == 0 &&
604                     strncmp(Obj->String.Pointer, "A1", 2) == 0)
605                         goto good;
606                 else if (strncmp(model->name, "A2x", 3) == 0 &&
607                     strncmp(Obj->String.Pointer, "A2", 2) == 0)
608                         goto good;
609                 else if (strncmp(model->name, "D1x", 3) == 0 &&
610                     strncmp(Obj->String.Pointer, "D1", 2) == 0)
611                         goto good;
612                 else if (strncmp(model->name, "L3H", 3) == 0 &&
613                     strncmp(Obj->String.Pointer, "L2E", 3) == 0)
614                         goto good;
615                 else if (strncmp(model->name, "L5x", 3) == 0 &&
616                     strncmp(Obj->String.Pointer, "L5", 2) == 0)
617                         goto good;
618                 else if (strncmp(model->name, "M2E", 3) == 0 &&
619                     (strncmp(Obj->String.Pointer, "M2", 2) == 0 ||
620                      strncmp(Obj->String.Pointer, "L4E", 3) == 0))
621                         goto good;
622                 else if (strncmp(model->name, "S1x", 3) == 0 &&
623                     (strncmp(Obj->String.Pointer, "L8", 2) == 0 ||
624                      strncmp(Obj->String.Pointer, "S1", 2) == 0))
625                         goto good;
626                 else if (strncmp(model->name, "S2x", 3) == 0 &&
627                     (strncmp(Obj->String.Pointer, "J1", 2) == 0 ||
628                      strncmp(Obj->String.Pointer, "S2", 2) == 0))
629                         goto good;
630
631                 /* L2B is like L3C but has no lcd_get method */
632                 else if (strncmp(model->name, "L3C", 3) == 0 &&
633                     strncmp(Obj->String.Pointer, "L2B", 3) == 0) {
634                         model->lcd_get = NULL;
635                         goto good;
636                 }
637
638                 /* A3G is like M6R but with a different lcd_get method */
639                 else if (strncmp(model->name, "M6R", 3) == 0 &&
640                     strncmp(Obj->String.Pointer, "A3G", 3) == 0) {
641                         model->lcd_get = "\\BLFG";
642                         goto good;
643                 }
644
645                 /* M2N and W1N are like xxN with added WLED */
646                 else if (strncmp(model->name, "xxN", 3) == 0 &&
647                     (strncmp(Obj->String.Pointer, "M2N", 3) == 0 ||
648                      strncmp(Obj->String.Pointer, "W1N", 3) == 0)) {
649                         model->wled_set = "WLED";
650                         goto good;
651                 }
652
653                 /* M5N and S5N are like xxN without MLED */
654                 else if (strncmp(model->name, "xxN", 3) == 0 &&
655                     (strncmp(Obj->String.Pointer, "M5N", 3) == 0 ||
656                      strncmp(Obj->String.Pointer, "S5N", 3) == 0)) {
657                         model->mled_set = NULL;
658                         goto good;
659                 }
660         }
661
662         sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
663         sbuf_finish(sb);
664
665         device_printf(dev, sbuf_data(sb));
666
667         sbuf_delete(sb);
668         AcpiOsFree(Buf.Pointer);
669
670         return (ENXIO);
671 }
672
673 static int
674 acpi_asus_attach(device_t dev)
675 {
676         struct acpi_asus_softc  *sc;
677         struct acpi_softc       *acpi_sc;
678
679         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
680
681         sc = device_get_softc(dev);
682         acpi_sc = acpi_device_get_parent_softc(dev);
683
684         /* Build sysctl tree */
685         sysctl_ctx_init(&sc->sysctl_ctx);
686         sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
687             SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
688             OID_AUTO, "asus", CTLFLAG_RD, 0, "");
689
690         /* Hook up nodes */
691         for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) {
692                 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method))
693                         continue;
694
695                 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
696                     SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
697                     acpi_asus_sysctls[i].name,
698                     acpi_asus_sysctls[i].flags,
699                     sc, i, acpi_asus_sysctl, "I",
700                     acpi_asus_sysctls[i].description);
701         }
702
703         /* Attach leds */
704         if (sc->model->bled_set) {
705                 sc->s_bled.busy = 0;
706                 sc->s_bled.sc = sc;
707                 sc->s_bled.type = ACPI_ASUS_LED_BLED;
708                 sc->s_bled.cdev =
709                     led_create_state((led_t *)acpi_asus_led, &sc->s_bled,
710                         "bled", 1);
711         }
712
713         if (sc->model->dled_set) {
714                 sc->s_dled.busy = 0;
715                 sc->s_dled.sc = sc;
716                 sc->s_dled.type = ACPI_ASUS_LED_DLED;
717                 sc->s_dled.cdev =
718                     led_create((led_t *)acpi_asus_led, &sc->s_dled, "dled");
719         }
720
721         if (sc->model->gled_set) {
722                 sc->s_gled.busy = 0;
723                 sc->s_gled.sc = sc;
724                 sc->s_gled.type = ACPI_ASUS_LED_GLED;
725                 sc->s_gled.cdev =
726                     led_create((led_t *)acpi_asus_led, &sc->s_gled, "gled");
727         }
728
729         if (sc->model->mled_set) {
730                 sc->s_mled.busy = 0;
731                 sc->s_mled.sc = sc;
732                 sc->s_mled.type = ACPI_ASUS_LED_MLED;
733                 sc->s_mled.cdev =
734                     led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
735         }
736
737         if (sc->model->tled_set) {
738                 sc->s_tled.busy = 0;
739                 sc->s_tled.sc = sc;
740                 sc->s_tled.type = ACPI_ASUS_LED_TLED;
741                 sc->s_tled.cdev =
742                     led_create_state((led_t *)acpi_asus_led, &sc->s_tled,
743                         "tled", 1);
744         }
745
746         if (sc->model->wled_set) {
747                 sc->s_wled.busy = 0;
748                 sc->s_wled.sc = sc;
749                 sc->s_wled.type = ACPI_ASUS_LED_WLED;
750                 sc->s_wled.cdev =
751                     led_create_state((led_t *)acpi_asus_led, &sc->s_wled,
752                         "wled", 1);
753         }
754
755         /* Activate hotkeys */
756         AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
757
758         /* Handle notifies */
759         if (sc->model->n_func == NULL)
760                 sc->model->n_func = acpi_asus_notify;
761
762         AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
763             sc->model->n_func, dev);
764
765         return (0);
766 }
767
768 static int
769 acpi_asus_detach(device_t dev)
770 {
771         struct acpi_asus_softc  *sc;
772
773         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
774
775         sc = device_get_softc(dev);
776
777         /* Turn the lights off */
778         if (sc->model->bled_set)
779                 led_destroy(sc->s_bled.cdev);
780
781         if (sc->model->dled_set)
782                 led_destroy(sc->s_dled.cdev);
783
784         if (sc->model->gled_set)
785                 led_destroy(sc->s_gled.cdev);
786
787         if (sc->model->mled_set)
788                 led_destroy(sc->s_mled.cdev);
789
790         if (sc->model->tled_set)
791                 led_destroy(sc->s_tled.cdev);
792
793         if (sc->model->wled_set)
794                 led_destroy(sc->s_wled.cdev);
795
796         /* Remove notify handler */
797         AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
798             acpi_asus_notify);
799
800         /* Free sysctl tree */
801         sysctl_ctx_free(&sc->sysctl_ctx);
802
803         return (0);
804 }
805
806 static void
807 acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused)
808 {
809         struct acpi_asus_softc  *sc;
810         char                    *method;
811         int                     state;
812         
813         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
814
815         sc = led->sc;
816
817         switch (led->type) {
818         case ACPI_ASUS_LED_BLED:
819                 method = sc->model->bled_set;
820                 state = led->state;
821                 break;
822         case ACPI_ASUS_LED_DLED:
823                 method = sc->model->dled_set;
824                 state = led->state;
825                 break;
826         case ACPI_ASUS_LED_GLED:
827                 method = sc->model->gled_set;
828                 state = led->state + 1; /* 1: off, 2: on */
829                 break;
830         case ACPI_ASUS_LED_MLED:
831                 method = sc->model->mled_set;
832                 state = !led->state;    /* inverted */
833                 break;
834         case ACPI_ASUS_LED_TLED:
835                 method = sc->model->tled_set;
836                 state = led->state;
837                 break;
838         case ACPI_ASUS_LED_WLED:
839                 method = sc->model->wled_set;
840                 state = led->state;
841                 break;
842         default:
843                 printf("acpi_asus_led: invalid LED type %d\n",
844                     (int)led->type);
845                 return;
846         }
847
848         acpi_SetInteger(sc->handle, method, state);
849         led->busy = 0;
850 }
851         
852 static void
853 acpi_asus_led(struct acpi_asus_led *led, int state)
854 {
855
856         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
857
858         if (led->busy)
859                 return;
860
861         led->busy = 1;
862         led->state = state;
863
864         AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led);
865 }
866
867 static int
868 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
869 {
870         struct acpi_asus_softc  *sc;
871         int                     arg;
872         int                     error = 0;
873         int                     function;
874         int                     method;
875         
876         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
877
878         sc = (struct acpi_asus_softc *)oidp->oid_arg1;
879         function = oidp->oid_arg2;
880         method = acpi_asus_sysctls[function].method;
881
882         ACPI_SERIAL_BEGIN(asus);
883         arg = acpi_asus_sysctl_get(sc, method);
884         error = sysctl_handle_int(oidp, &arg, 0, req);
885
886         /* Sanity check */
887         if (error != 0 || req->newptr == NULL)
888                 goto out;
889
890         /* Update */
891         error = acpi_asus_sysctl_set(sc, method, arg);
892
893 out:
894         ACPI_SERIAL_END(asus);
895         return (error);
896 }
897
898 static int
899 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method)
900 {
901         int val = 0;
902
903         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
904         ACPI_SERIAL_ASSERT(asus);
905
906         switch (method) {
907         case ACPI_ASUS_METHOD_BRN:
908                 val = sc->s_brn;
909                 break;
910         case ACPI_ASUS_METHOD_DISP:
911                 val = sc->s_disp;
912                 break;
913         case ACPI_ASUS_METHOD_LCD:
914                 val = sc->s_lcd;
915                 break;
916         case ACPI_ASUS_METHOD_CAMERA:
917                 val = sc->s_cam;
918                 break;
919         case ACPI_ASUS_METHOD_CARDRD:
920                 val = sc->s_crd;
921                 break;
922         case ACPI_ASUS_METHOD_WLAN:
923                 val = sc->s_wlan;
924                 break;
925         }
926
927         return (val);
928 }
929
930 static int
931 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg)
932 {
933         ACPI_STATUS             status = AE_OK;
934         ACPI_OBJECT_LIST        acpiargs;
935         ACPI_OBJECT             acpiarg[0];
936
937         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
938         ACPI_SERIAL_ASSERT(asus);
939
940         acpiargs.Count = 1;
941         acpiargs.Pointer = acpiarg;
942         acpiarg[0].Type = ACPI_TYPE_INTEGER;
943         acpiarg[0].Integer.Value = arg;
944
945         switch (method) {
946         case ACPI_ASUS_METHOD_BRN:
947                 if (arg < 0 || arg > 15)
948                         return (EINVAL);
949
950                 if (sc->model->brn_set)
951                         status = acpi_SetInteger(sc->handle,
952                             sc->model->brn_set, arg);
953                 else {
954                         while (arg != 0) {
955                                 status = AcpiEvaluateObject(sc->handle,
956                                     (arg > 0) ?  sc->model->brn_up :
957                                     sc->model->brn_dn, NULL, NULL);
958                                 (arg > 0) ? arg-- : arg++;
959                         }
960                 }
961
962                 if (ACPI_SUCCESS(status))
963                         sc->s_brn = arg;
964
965                 break;
966         case ACPI_ASUS_METHOD_DISP:
967                 if (arg < 0 || arg > 7)
968                         return (EINVAL);
969
970                 status = acpi_SetInteger(sc->handle,
971                     sc->model->disp_set, arg);
972
973                 if (ACPI_SUCCESS(status))
974                         sc->s_disp = arg;
975
976                 break;
977         case ACPI_ASUS_METHOD_LCD:
978                 if (arg < 0 || arg > 1)
979                         return (EINVAL);
980
981                 if (strncmp(sc->model->name, "L3H", 3) != 0)
982                         status = AcpiEvaluateObject(sc->handle,
983                             sc->model->lcd_set, NULL, NULL);
984                 else
985                         status = acpi_SetInteger(sc->handle,
986                             sc->model->lcd_set, 0x7);
987
988                 if (ACPI_SUCCESS(status))
989                         sc->s_lcd = arg;
990
991                 break;
992         case ACPI_ASUS_METHOD_CAMERA:
993                 if (arg < 0 || arg > 1)
994                         return (EINVAL);
995
996                 status = AcpiEvaluateObject(sc->handle,
997                     sc->model->cam_set, &acpiargs, NULL);
998
999                 if (ACPI_SUCCESS(status))
1000                         sc->s_cam = arg;
1001                 break;
1002         case ACPI_ASUS_METHOD_CARDRD:
1003                 if (arg < 0 || arg > 1)
1004                         return (EINVAL);
1005
1006                 status = AcpiEvaluateObject(sc->handle,
1007                     sc->model->crd_set, &acpiargs, NULL);
1008
1009                 if (ACPI_SUCCESS(status))
1010                         sc->s_crd = arg;
1011                 break;
1012         case ACPI_ASUS_METHOD_WLAN:
1013                 if (arg < 0 || arg > 1)
1014                         return (EINVAL);
1015
1016                 status = AcpiEvaluateObject(sc->handle,
1017                     sc->model->wlan_set, &acpiargs, NULL);
1018
1019                 if (ACPI_SUCCESS(status))
1020                         sc->s_wlan = arg;
1021                 break;
1022         }
1023
1024         return (0);
1025 }
1026
1027 static int
1028 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method)
1029 {
1030         ACPI_STATUS     status;
1031
1032         switch (method) {
1033         case ACPI_ASUS_METHOD_BRN:
1034                 if (sc->model->brn_get) {
1035                         /* GPLV/SPLV models */
1036                         status = acpi_GetInteger(sc->handle,
1037                             sc->model->brn_get, &sc->s_brn);
1038                         if (ACPI_SUCCESS(status))
1039                                 return (TRUE);
1040                 } else if (sc->model->brn_up) {
1041                         /* Relative models */
1042                         status = AcpiEvaluateObject(sc->handle,
1043                             sc->model->brn_up, NULL, NULL);
1044                         if (ACPI_FAILURE(status))
1045                                 return (FALSE);
1046
1047                         status = AcpiEvaluateObject(sc->handle,
1048                             sc->model->brn_dn, NULL, NULL);
1049                         if (ACPI_FAILURE(status))
1050                                 return (FALSE);
1051
1052                         return (TRUE);
1053                 }
1054                 return (FALSE);
1055         case ACPI_ASUS_METHOD_DISP:
1056                 if (sc->model->disp_get) {
1057                         status = acpi_GetInteger(sc->handle,
1058                             sc->model->disp_get, &sc->s_disp);
1059                         if (ACPI_SUCCESS(status))
1060                                 return (TRUE);
1061                 }
1062                 return (FALSE);
1063         case ACPI_ASUS_METHOD_LCD:
1064                 if (sc->model->lcd_get) {
1065                         if (strncmp(sc->model->name, "G2K", 3) == 0) {
1066                                 ACPI_BUFFER             Buf;
1067                                 ACPI_OBJECT             Arg, Obj;
1068                                 ACPI_OBJECT_LIST        Args;
1069
1070                                 Arg.Type = ACPI_TYPE_INTEGER;
1071                                 Arg.Integer.Value = 0x11;
1072                                 Args.Count = 1;
1073                                 Args.Pointer = &Arg;
1074                                 Buf.Length = sizeof(Obj);
1075                                 Buf.Pointer = &Obj;
1076
1077                                 status = AcpiEvaluateObject(sc->handle,
1078                                     sc->model->lcd_get, &Args, &Buf);
1079                                 if (ACPI_SUCCESS(status) &&
1080                                     Obj.Type == ACPI_TYPE_INTEGER) {
1081                                         sc->s_lcd = Obj.Integer.Value;
1082                                         return (TRUE);
1083                                 }
1084                         } else if (strncmp(sc->model->name, "L3H", 3) == 0) {
1085                                 ACPI_BUFFER             Buf;
1086                                 ACPI_OBJECT             Arg[2], Obj;
1087                                 ACPI_OBJECT_LIST        Args;
1088
1089                                 /* L3H is a bit special */
1090                                 Arg[0].Type = ACPI_TYPE_INTEGER;
1091                                 Arg[0].Integer.Value = 0x02;
1092                                 Arg[1].Type = ACPI_TYPE_INTEGER;
1093                                 Arg[1].Integer.Value = 0x03;
1094
1095                                 Args.Count = 2;
1096                                 Args.Pointer = Arg;
1097
1098                                 Buf.Length = sizeof(Obj);
1099                                 Buf.Pointer = &Obj;
1100
1101                                 status = AcpiEvaluateObject(sc->handle,
1102                                     sc->model->lcd_get, &Args, &Buf);
1103                                 if (ACPI_SUCCESS(status) &&
1104                                     Obj.Type == ACPI_TYPE_INTEGER) {
1105                                         sc->s_lcd = Obj.Integer.Value >> 8;
1106                                         return (TRUE);
1107                                 }
1108                         } else {
1109                                 status = acpi_GetInteger(sc->handle,
1110                                     sc->model->lcd_get, &sc->s_lcd);
1111                                 if (ACPI_SUCCESS(status))
1112                                         return (TRUE);
1113                         }
1114                 }
1115                 return (FALSE);
1116         case ACPI_ASUS_METHOD_CAMERA:
1117                 if (sc->model->cam_get) {
1118                         status = acpi_GetInteger(sc->handle,
1119                             sc->model->cam_get, &sc->s_cam);
1120                         if (ACPI_SUCCESS(status))
1121                                 return (TRUE);
1122                 }
1123                 return (FALSE);
1124         case ACPI_ASUS_METHOD_CARDRD:
1125                 if (sc->model->crd_get) {
1126                         status = acpi_GetInteger(sc->handle,
1127                             sc->model->crd_get, &sc->s_crd);
1128                         if (ACPI_SUCCESS(status))
1129                                 return (TRUE);
1130                 }
1131                 return (FALSE);
1132         case ACPI_ASUS_METHOD_WLAN:
1133                 if (sc->model->wlan_get) {
1134                         status = acpi_GetInteger(sc->handle,
1135                             sc->model->wlan_get, &sc->s_wlan);
1136                         if (ACPI_SUCCESS(status))
1137                                 return (TRUE);
1138                 }
1139                 return (FALSE);
1140         }
1141         return (FALSE);
1142 }
1143
1144 static void
1145 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
1146 {
1147         struct acpi_asus_softc  *sc;
1148         struct acpi_softc       *acpi_sc;
1149
1150         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
1151
1152         sc = device_get_softc((device_t)context);
1153         acpi_sc = acpi_device_get_parent_softc(sc->dev);
1154
1155         ACPI_SERIAL_BEGIN(asus);
1156         if ((notify & ~0x10) <= 15) {
1157                 sc->s_brn = notify & ~0x10;
1158                 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
1159         } else if ((notify & ~0x20) <= 15) {
1160                 sc->s_brn = notify & ~0x20;
1161                 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
1162         } else if (notify == 0x33) {
1163                 sc->s_lcd = 1;
1164                 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
1165         } else if (notify == 0x34) {
1166                 sc->s_lcd = 0;
1167                 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
1168         } else {
1169                 /* Notify devd(8) */
1170                 acpi_UserNotify("ASUS", h, notify);
1171         }
1172         ACPI_SERIAL_END(asus);
1173 }
1174
1175 static void
1176 acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context)
1177 {
1178         struct acpi_asus_softc  *sc;
1179         struct acpi_softc       *acpi_sc;
1180
1181         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
1182
1183         sc = device_get_softc((device_t)context);
1184         acpi_sc = acpi_device_get_parent_softc(sc->dev);
1185
1186         ACPI_SERIAL_BEGIN(asus);
1187         if ((notify & ~0x20) <= 15) {
1188                 sc->s_brn = notify & ~0x20;
1189                 ACPI_VPRINT(sc->dev, acpi_sc,
1190                     "Brightness increased/decreased\n");
1191         } else {
1192                 /* Notify devd(8) */
1193                 acpi_UserNotify("ASUS-Eee", h, notify);
1194         }
1195         ACPI_SERIAL_END(asus);
1196 }