]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/acpi_support/acpi_asus.c
Canonize the include of acpi.h.
[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
59 #define _COMPONENT      ACPI_OEM
60 ACPI_MODULE_NAME("ASUS")
61
62 struct acpi_asus_model {
63         char    *name;
64
65         char    *bled_set;
66         char    *mled_set;
67         char    *tled_set;
68         char    *wled_set;
69
70         char    *brn_get;
71         char    *brn_set;
72         char    *brn_up;
73         char    *brn_dn;
74
75         char    *lcd_get;
76         char    *lcd_set;
77
78         char    *disp_get;
79         char    *disp_set;
80 };
81
82 struct acpi_asus_led {
83         struct acpi_asus_softc *sc;
84         struct cdev     *cdev;
85         int             busy;
86         int             state;
87         enum {
88                 ACPI_ASUS_LED_BLED,
89                 ACPI_ASUS_LED_MLED,
90                 ACPI_ASUS_LED_TLED,
91                 ACPI_ASUS_LED_WLED,
92         } type;
93 };
94
95 struct acpi_asus_softc {
96         device_t                dev;
97         ACPI_HANDLE             handle;
98
99         struct acpi_asus_model  *model;
100         struct sysctl_ctx_list  sysctl_ctx;
101         struct sysctl_oid       *sysctl_tree;
102
103         struct acpi_asus_led    s_bled;
104         struct acpi_asus_led    s_mled;
105         struct acpi_asus_led    s_tled;
106         struct acpi_asus_led    s_wled;
107
108         int                     s_brn;
109         int                     s_disp;
110         int                     s_lcd;
111 };
112
113 /*
114  * We can identify Asus laptops from the string they return
115  * as a result of calling the ATK0100 'INIT' method.
116  */
117 static struct acpi_asus_model acpi_asus_models[] = {
118         {
119                 .name           = "xxN",
120                 .mled_set       = "MLED",
121                 .wled_set       = "WLED",
122                 .lcd_get        = "\\BKLT",
123                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
124                 .brn_get        = "GPLV",
125                 .brn_set        = "SPLV",
126                 .disp_get       = "\\ADVG",
127                 .disp_set       = "SDSP"
128         },
129         {
130                 .name           = "A1x",
131                 .mled_set       = "MLED",
132                 .lcd_get        = "\\BKLI",
133                 .lcd_set        = "\\_SB.PCI0.ISA.EC0._Q10",
134                 .brn_up         = "\\_SB.PCI0.ISA.EC0._Q0E",
135                 .brn_dn         = "\\_SB.PCI0.ISA.EC0._Q0F"
136         },
137         {
138                 .name           = "A2x",
139                 .mled_set       = "MLED",
140                 .wled_set       = "WLED",
141                 .lcd_get        = "\\BAOF",
142                 .lcd_set        = "\\Q10",
143                 .brn_get        = "GPLV",
144                 .brn_set        = "SPLV",
145                 .disp_get       = "\\INFB",
146                 .disp_set       = "SDSP"
147         },
148         {
149                 .name           = "D1x",
150                 .mled_set       = "MLED",
151                 .lcd_get        = "\\GP11",
152                 .lcd_set        = "\\Q0D",
153                 .brn_up         = "\\Q0C",
154                 .brn_dn         = "\\Q0B",
155                 .disp_get       = "\\INFB",
156                 .disp_set       = "SDSP"
157         },
158         {
159                 .name           = "L2D",
160                 .mled_set       = "MLED",
161                 .wled_set       = "WLED",
162                 .brn_up         = "\\Q0E",
163                 .brn_dn         = "\\Q0F",
164                 .lcd_get        = "\\SGP0",
165                 .lcd_set        = "\\Q10"
166         },
167         {
168                 .name           = "L3C",
169                 .mled_set       = "MLED",
170                 .wled_set       = "WLED",
171                 .brn_get        = "GPLV",
172                 .brn_set        = "SPLV",
173                 .lcd_get        = "\\GL32",
174                 .lcd_set        = "\\_SB.PCI0.PX40.ECD0._Q10"
175         },
176         {
177                 .name           = "L3D",
178                 .mled_set       = "MLED",
179                 .wled_set       = "WLED",
180                 .brn_get        = "GPLV",
181                 .brn_set        = "SPLV",
182                 .lcd_get        = "\\BKLG",
183                 .lcd_set        = "\\Q10"
184         },
185         {
186                 .name           = "L3H",
187                 .mled_set       = "MLED",
188                 .wled_set       = "WLED",
189                 .brn_get        = "GPLV",
190                 .brn_set        = "SPLV",
191                 .lcd_get        = "\\_SB.PCI0.PM.PBC",
192                 .lcd_set        = "EHK",
193                 .disp_get       = "\\_SB.INFB",
194                 .disp_set       = "SDSP"
195         },
196         {
197                 .name           = "L4R",
198                 .mled_set       = "MLED",
199                 .wled_set       = "WLED",
200                 .brn_get        = "GPLV",
201                 .brn_set        = "SPLV",
202                 .lcd_get        = "\\_SB.PCI0.SBSM.SEO4",
203                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
204                 .disp_get       = "\\_SB.PCI0.P0P1.VGA.GETD",
205                 .disp_set       = "SDSP"
206         },
207         {
208                 .name           = "L5x",
209                 .mled_set       = "MLED",
210                 .tled_set       = "TLED",
211                 .lcd_get        = "\\BAOF",
212                 .lcd_set        = "\\Q0D",
213                 .brn_get        = "GPLV",
214                 .brn_set        = "SPLV",
215                 .disp_get       = "\\INFB",
216                 .disp_set       = "SDSP"
217         },
218         {
219                 .name           = "L8L"
220                 /* Only has hotkeys, apparantly */
221         },
222         {
223                 .name           = "M1A",
224                 .mled_set       = "MLED",
225                 .brn_up         = "\\_SB.PCI0.PX40.EC0.Q0E",
226                 .brn_dn         = "\\_SB.PCI0.PX40.EC0.Q0F",
227                 .lcd_get        = "\\PNOF",
228                 .lcd_set        = "\\_SB.PCI0.PX40.EC0.Q10"
229         },
230         {
231                 .name           = "M2E",
232                 .mled_set       = "MLED",
233                 .wled_set       = "WLED",
234                 .brn_get        = "GPLV",
235                 .brn_set        = "SPLV",
236                 .lcd_get        = "\\GP06",
237                 .lcd_set        = "\\Q10"
238         },
239         {
240                 .name           = "M6N",
241                 .mled_set       = "MLED",
242                 .wled_set       = "WLED",
243                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
244                 .lcd_get        = "\\_SB.BKLT",
245                 .brn_set        = "SPLV",
246                 .brn_get        = "GPLV",
247                 .disp_set       = "SDSP",
248                 .disp_get       = "\\SSTE"
249         },
250         {
251                 .name           = "M6R",
252                 .mled_set       = "MLED",
253                 .wled_set       = "WLED",
254                 .brn_get        = "GPLV",
255                 .brn_set        = "SPLV",
256                 .lcd_get        = "\\_SB.PCI0.SBSM.SEO4",
257                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
258                 .disp_get       = "\\SSTE",
259                 .disp_set       = "SDSP"
260         },
261         {
262                 .name           = "S1x",
263                 .mled_set       = "MLED",
264                 .wled_set       = "WLED",
265                 .lcd_get        = "\\PNOF",
266                 .lcd_set        = "\\_SB.PCI0.PX40.Q10",
267                 .brn_get        = "GPLV",
268                 .brn_set        = "SPLV"
269         },
270         {
271                 .name           = "S2x",
272                 .mled_set       = "MLED",
273                 .lcd_get        = "\\BKLI",
274                 .lcd_set        = "\\_SB.PCI0.ISA.EC0._Q10",
275                 .brn_up         = "\\_SB.PCI0.ISA.EC0._Q0B",
276                 .brn_dn         = "\\_SB.PCI0.ISA.EC0._Q0A"
277         },
278         {
279                 .name           = "V6V",
280                 .bled_set       = "BLED",
281                 .tled_set       = "TLED",
282                 .wled_set       = "WLED",
283                 .lcd_get        = "\\BKLT",
284                 .lcd_set        = "\\_SB.PCI0.SBRG.EC0._Q10",
285                 .brn_get        = "GPLV",
286                 .brn_set        = "SPLV",
287                 .disp_get       = "\\_SB.PCI0.P0P1.VGA.GETD",
288                 .disp_set       = "SDSP"
289         },
290
291         { .name = NULL }
292 };
293
294 /*
295  * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
296  * but they can't be probed quite the same way as Asus laptops.
297  */
298 static struct acpi_asus_model acpi_samsung_models[] = {
299         {
300                 .name           = "P30",
301                 .wled_set       = "WLED",
302                 .brn_up         = "\\_SB.PCI0.LPCB.EC0._Q68",
303                 .brn_dn         = "\\_SB.PCI0.LPCB.EC0._Q69",
304                 .lcd_get        = "\\BKLT",
305                 .lcd_set        = "\\_SB.PCI0.LPCB.EC0._Q0E"
306         },
307
308         { .name = NULL }
309 };
310
311 static struct {
312         char    *name;
313         char    *description;
314         int     method;
315 } acpi_asus_sysctls[] = {
316         {
317                 .name           = "lcd_backlight",
318                 .method         = ACPI_ASUS_METHOD_LCD,
319                 .description    = "state of the lcd backlight"
320         },
321         {
322                 .name           = "lcd_brightness",
323                 .method         = ACPI_ASUS_METHOD_BRN,
324                 .description    = "brightness of the lcd panel"
325         },
326         {
327                 .name           = "video_output",
328                 .method         = ACPI_ASUS_METHOD_DISP,
329                 .description    = "display output state"
330         },
331
332         { .name = NULL }
333 };
334
335 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras");
336
337 /* Function prototypes */
338 static int      acpi_asus_probe(device_t dev);
339 static int      acpi_asus_attach(device_t dev);
340 static int      acpi_asus_detach(device_t dev);
341
342 static void     acpi_asus_led(struct acpi_asus_led *led, int state);
343 static void     acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused);
344
345 static int      acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
346 static int      acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
347 static int      acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method);
348 static int      acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val);
349
350 static void     acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
351
352 static device_method_t acpi_asus_methods[] = {
353         DEVMETHOD(device_probe,  acpi_asus_probe),
354         DEVMETHOD(device_attach, acpi_asus_attach),
355         DEVMETHOD(device_detach, acpi_asus_detach),
356
357         { 0, 0 }
358 };
359
360 static driver_t acpi_asus_driver = {
361         "acpi_asus",
362         acpi_asus_methods,
363         sizeof(struct acpi_asus_softc)
364 };
365
366 static devclass_t acpi_asus_devclass;
367
368 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
369 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
370
371 static int
372 acpi_asus_probe(device_t dev)
373 {
374         struct acpi_asus_model  *model;
375         struct acpi_asus_softc  *sc;
376         struct sbuf             *sb;
377         ACPI_BUFFER             Buf;
378         ACPI_OBJECT             Arg, *Obj;
379         ACPI_OBJECT_LIST        Args;
380         static char             *asus_ids[] = { "ATK0100", NULL };
381
382         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
383
384         if (acpi_disabled("asus") ||
385             ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids) == NULL)
386                 return (ENXIO);
387
388         sc = device_get_softc(dev);
389         sc->dev = dev;
390         sc->handle = acpi_get_handle(dev);
391
392         Arg.Type = ACPI_TYPE_INTEGER;
393         Arg.Integer.Value = 0;
394
395         Args.Count = 1;
396         Args.Pointer = &Arg;
397
398         Buf.Pointer = NULL;
399         Buf.Length = ACPI_ALLOCATE_BUFFER;
400
401         AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
402         Obj = Buf.Pointer;
403
404         /*
405          * The Samsung P30 returns a null-pointer from INIT, we
406          * can identify it from the 'ODEM' string in the DSDT.
407          */
408         if (Obj->String.Pointer == NULL) {
409                 ACPI_STATUS             status;
410                 ACPI_TABLE_HEADER       th;
411
412                 status = AcpiGetTableHeader(ACPI_TABLE_DSDT, 1, &th);
413                 if (ACPI_FAILURE(status)) {
414                         device_printf(dev, "Unsupported (Samsung?) laptop\n");
415                         AcpiOsFree(Buf.Pointer);
416                         return (ENXIO);
417                 }
418
419                 if (strncmp("ODEM", th.OemTableId, 4) == 0) {
420                         sc->model = &acpi_samsung_models[0];
421                         device_set_desc(dev, "Samsung P30 Laptop Extras");
422                         AcpiOsFree(Buf.Pointer);
423                         return (0);
424                 }
425         }
426
427         sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
428         if (sb == NULL)
429                 return (ENOMEM);
430
431         /*
432          * Asus laptops are simply identified by name, easy!
433          */
434         for (model = acpi_asus_models; model->name != NULL; model++) {
435                 if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
436
437 good:
438                         sbuf_printf(sb, "Asus %s Laptop Extras",
439                             Obj->String.Pointer);
440                         sbuf_finish(sb);
441
442                         sc->model = model;
443                         device_set_desc_copy(dev, sbuf_data(sb));
444
445                         sbuf_delete(sb);
446                         AcpiOsFree(Buf.Pointer);
447                         return (0);
448                 }
449                 
450                 /*
451                  * Some models look exactly the same as other models, but have
452                  * their own ids.  If we spot these, set them up with the same
453                  * details as the models they're like, possibly dealing with
454                  * small differences.
455                  *
456                  * XXX: there must be a prettier way to do this!
457                  */
458                 else if (strncmp(model->name, "xxN", 3) == 0 &&
459                     (strncmp(Obj->String.Pointer, "M3N", 3) == 0 ||
460                      strncmp(Obj->String.Pointer, "S1N", 3) == 0))
461                         goto good;
462                 else if (strncmp(model->name, "A1x", 3) == 0 &&
463                     strncmp(Obj->String.Pointer, "A1", 2) == 0)
464                         goto good;
465                 else if (strncmp(model->name, "A2x", 3) == 0 &&
466                     strncmp(Obj->String.Pointer, "A2", 2) == 0)
467                         goto good;
468                 else if (strncmp(model->name, "D1x", 3) == 0 &&
469                     strncmp(Obj->String.Pointer, "D1", 2) == 0)
470                         goto good;
471                 else if (strncmp(model->name, "L3H", 3) == 0 &&
472                     strncmp(Obj->String.Pointer, "L2E", 3) == 0)
473                         goto good;
474                 else if (strncmp(model->name, "L5x", 3) == 0 &&
475                     strncmp(Obj->String.Pointer, "L5", 2) == 0)
476                         goto good;
477                 else if (strncmp(model->name, "M2E", 3) == 0 &&
478                     (strncmp(Obj->String.Pointer, "M2", 2) == 0 ||
479                      strncmp(Obj->String.Pointer, "L4E", 3) == 0))
480                         goto good;
481                 else if (strncmp(model->name, "S1x", 3) == 0 &&
482                     (strncmp(Obj->String.Pointer, "L8", 2) == 0 ||
483                      strncmp(Obj->String.Pointer, "S1", 2) == 0))
484                         goto good;
485                 else if (strncmp(model->name, "S2x", 3) == 0 &&
486                     (strncmp(Obj->String.Pointer, "J1", 2) == 0 ||
487                      strncmp(Obj->String.Pointer, "S2", 2) == 0))
488                         goto good;
489
490                 /* L2B is like L3C but has no lcd_get method */
491                 else if (strncmp(model->name, "L3C", 3) == 0 &&
492                     strncmp(Obj->String.Pointer, "L2B", 3) == 0) {
493                         model->lcd_get = NULL;
494                         goto good;
495                 }
496
497                 /* A3G is like M6R but with a different lcd_get method */
498                 else if (strncmp(model->name, "M6R", 3) == 0 &&
499                     strncmp(Obj->String.Pointer, "A3G", 3) == 0) {
500                         model->lcd_get = "\\BLFG";
501                         goto good;
502                 }
503
504                 /* M2N and W1N are like xxN with added WLED */
505                 else if (strncmp(model->name, "xxN", 3) == 0 &&
506                     (strncmp(Obj->String.Pointer, "M2N", 3) == 0 ||
507                      strncmp(Obj->String.Pointer, "W1N", 3) == 0)) {
508                         model->wled_set = "WLED";
509                         goto good;
510                 }
511
512                 /* M5N and S5N are like xxN without MLED */
513                 else if (strncmp(model->name, "xxN", 3) == 0 &&
514                     (strncmp(Obj->String.Pointer, "M5N", 3) == 0 ||
515                      strncmp(Obj->String.Pointer, "S5N", 3) == 0)) {
516                         model->mled_set = NULL;
517                         goto good;
518                 }
519         }
520
521         sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
522         sbuf_finish(sb);
523
524         device_printf(dev, sbuf_data(sb));
525
526         sbuf_delete(sb);
527         AcpiOsFree(Buf.Pointer);
528
529         return (ENXIO);
530 }
531
532 static int
533 acpi_asus_attach(device_t dev)
534 {
535         struct acpi_asus_softc  *sc;
536         struct acpi_softc       *acpi_sc;
537
538         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
539
540         sc = device_get_softc(dev);
541         acpi_sc = acpi_device_get_parent_softc(dev);
542
543         /* Build sysctl tree */
544         sysctl_ctx_init(&sc->sysctl_ctx);
545         sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
546             SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
547             OID_AUTO, "asus", CTLFLAG_RD, 0, "");
548
549         /* Hook up nodes */
550         for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) {
551                 if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method))
552                         continue;
553
554                 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
555                     SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
556                     acpi_asus_sysctls[i].name,
557                     CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
558                     sc, i, acpi_asus_sysctl, "I",
559                     acpi_asus_sysctls[i].description);
560         }
561
562         /* Attach leds */
563         if (sc->model->bled_set) {
564                 sc->s_bled.busy = 0;
565                 sc->s_bled.sc = sc;
566                 sc->s_bled.type = ACPI_ASUS_LED_BLED;
567                 sc->s_bled.cdev =
568                     led_create((led_t *)acpi_asus_led, &sc->s_bled, "bled");
569         }
570
571         if (sc->model->mled_set) {
572                 sc->s_mled.busy = 0;
573                 sc->s_mled.sc = sc;
574                 sc->s_mled.type = ACPI_ASUS_LED_MLED;
575                 sc->s_mled.cdev =
576                     led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
577         }
578
579         if (sc->model->tled_set) {
580                 sc->s_tled.busy = 0;
581                 sc->s_tled.sc = sc;
582                 sc->s_tled.type = ACPI_ASUS_LED_TLED;
583                 sc->s_tled.cdev =
584                     led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
585         }
586
587         if (sc->model->wled_set) {
588                 sc->s_wled.busy = 0;
589                 sc->s_wled.sc = sc;
590                 sc->s_wled.type = ACPI_ASUS_LED_WLED;
591                 sc->s_wled.cdev =
592                     led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
593         }
594
595         /* Activate hotkeys */
596         AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
597
598         /* Handle notifies */
599         AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
600             acpi_asus_notify, dev);
601
602         return (0);
603 }
604
605 static int
606 acpi_asus_detach(device_t dev)
607 {
608         struct acpi_asus_softc  *sc;
609
610         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
611
612         sc = device_get_softc(dev);
613
614         /* Turn the lights off */
615         if (sc->model->bled_set)
616                 led_destroy(sc->s_bled.cdev);
617
618         if (sc->model->mled_set)
619                 led_destroy(sc->s_mled.cdev);
620
621         if (sc->model->tled_set)
622                 led_destroy(sc->s_tled.cdev);
623
624         if (sc->model->wled_set)
625                 led_destroy(sc->s_wled.cdev);
626
627         /* Remove notify handler */
628         AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
629             acpi_asus_notify);
630
631         /* Free sysctl tree */
632         sysctl_ctx_free(&sc->sysctl_ctx);
633
634         return (0);
635 }
636
637 static void
638 acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused)
639 {
640         struct acpi_asus_softc  *sc;
641         char                    *method;
642         int                     state;
643         
644         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
645
646         sc = led->sc;
647
648         switch (led->type) {
649         case ACPI_ASUS_LED_BLED:
650                 method = sc->model->bled_set;
651                 state = led->state;
652                 break;
653         case ACPI_ASUS_LED_MLED:
654                 method = sc->model->mled_set;
655
656                 /* Note: inverted */
657                 state = !led->state;
658                 break;
659         case ACPI_ASUS_LED_TLED:
660                 method = sc->model->tled_set;
661                 state = led->state;
662                 break;
663         case ACPI_ASUS_LED_WLED:
664                 method = sc->model->wled_set;
665                 state = led->state;
666                 break;
667         default:
668                 printf("acpi_asus_led: invalid LED type %d\n",
669                     (int)led->type);
670                 return;
671         }
672
673         acpi_SetInteger(sc->handle, method, state);
674         led->busy = 0;
675 }
676         
677 static void
678 acpi_asus_led(struct acpi_asus_led *led, int state)
679 {
680
681         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
682
683         if (led->busy)
684                 return;
685
686         led->busy = 1;
687         led->state = state;
688
689         AcpiOsQueueForExecution(OSD_PRIORITY_LO,
690             (void *)acpi_asus_led_task, led);
691 }
692
693 static int
694 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
695 {
696         struct acpi_asus_softc  *sc;
697         int                     arg;
698         int                     error = 0;
699         int                     function;
700         int                     method;
701         
702         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
703
704         sc = (struct acpi_asus_softc *)oidp->oid_arg1;
705         function = oidp->oid_arg2;
706         method = acpi_asus_sysctls[function].method;
707
708         ACPI_SERIAL_BEGIN(asus);
709         arg = acpi_asus_sysctl_get(sc, method);
710         error = sysctl_handle_int(oidp, &arg, 0, req);
711
712         /* Sanity check */
713         if (error != 0 || req->newptr == NULL)
714                 goto out;
715
716         /* Update */
717         error = acpi_asus_sysctl_set(sc, method, arg);
718
719 out:
720         ACPI_SERIAL_END(asus);
721         return (error);
722 }
723
724 static int
725 acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method)
726 {
727         int val = 0;
728
729         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
730         ACPI_SERIAL_ASSERT(asus);
731
732         switch (method) {
733         case ACPI_ASUS_METHOD_BRN:
734                 val = sc->s_brn;
735                 break;
736         case ACPI_ASUS_METHOD_DISP:
737                 val = sc->s_disp;
738                 break;
739         case ACPI_ASUS_METHOD_LCD:
740                 val = sc->s_lcd;
741                 break;
742         }
743
744         return (val);
745 }
746
747 static int
748 acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg)
749 {
750         ACPI_STATUS     status = AE_OK;
751
752         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
753         ACPI_SERIAL_ASSERT(asus);
754
755         switch (method) {
756         case ACPI_ASUS_METHOD_BRN:
757                 if (arg < 0 || arg > 15)
758                         return (EINVAL);
759
760                 if (sc->model->brn_set)
761                         status = acpi_SetInteger(sc->handle,
762                             sc->model->brn_set, arg);
763                 else {
764                         while (arg != 0) {
765                                 status = AcpiEvaluateObject(sc->handle,
766                                     (arg > 0) ?  sc->model->brn_up :
767                                     sc->model->brn_dn, NULL, NULL);
768                                 (arg > 0) ? arg-- : arg++;
769                         }
770                 }
771
772                 if (ACPI_SUCCESS(status))
773                         sc->s_brn = arg;
774
775                 break;
776         case ACPI_ASUS_METHOD_DISP:
777                 if (arg < 0 || arg > 7)
778                         return (EINVAL);
779
780                 status = acpi_SetInteger(sc->handle,
781                     sc->model->disp_set, arg);
782
783                 if (ACPI_SUCCESS(status))
784                         sc->s_disp = arg;
785
786                 break;
787         case ACPI_ASUS_METHOD_LCD:
788                 if (arg < 0 || arg > 1)
789                         return (EINVAL);
790
791                 if (strncmp(sc->model->name, "L3H", 3) != 0)
792                         status = AcpiEvaluateObject(sc->handle,
793                             sc->model->lcd_set, NULL, NULL);
794                 else
795                         status = acpi_SetInteger(sc->handle,
796                             sc->model->lcd_set, 0x7);
797
798                 if (ACPI_SUCCESS(status))
799                         sc->s_lcd = arg;
800
801                 break;
802         }
803
804         return (0);
805 }
806
807 static int
808 acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method)
809 {
810         ACPI_STATUS     status;
811
812         switch (method) {
813         case ACPI_ASUS_METHOD_BRN:
814                 if (sc->model->brn_get) {
815                         /* GPLV/SPLV models */
816                         status = acpi_GetInteger(sc->handle,
817                             sc->model->brn_get, &sc->s_brn);
818                         if (ACPI_SUCCESS(status))
819                                 return (TRUE);
820                 } else if (sc->model->brn_up) {
821                         /* Relative models */
822                         status = AcpiEvaluateObject(sc->handle,
823                             sc->model->brn_up, NULL, NULL);
824                         if (ACPI_FAILURE(status))
825                                 return (FALSE);
826
827                         status = AcpiEvaluateObject(sc->handle,
828                             sc->model->brn_dn, NULL, NULL);
829                         if (ACPI_FAILURE(status))
830                                 return (FALSE);
831
832                         return (TRUE);
833                 }
834                 return (FALSE);
835         case ACPI_ASUS_METHOD_DISP:
836                 if (sc->model->disp_get) {
837                         status = acpi_GetInteger(sc->handle,
838                             sc->model->disp_get, &sc->s_disp);
839                         if (ACPI_SUCCESS(status))
840                                 return (TRUE);
841                 }
842                 return (FALSE);
843         case ACPI_ASUS_METHOD_LCD:
844                 if (sc->model->lcd_get &&
845                     strncmp(sc->model->name, "L3H", 3) != 0) {
846                         status = acpi_GetInteger(sc->handle,
847                             sc->model->lcd_get, &sc->s_lcd);
848                         if (ACPI_SUCCESS(status))
849                                 return (TRUE);
850                 }
851                 else if (sc->model->lcd_get) {
852                         ACPI_BUFFER             Buf;
853                         ACPI_OBJECT             Arg[2], Obj;
854                         ACPI_OBJECT_LIST        Args;
855
856                         /* L3H is a bit special */
857                         Arg[0].Type = ACPI_TYPE_INTEGER;
858                         Arg[0].Integer.Value = 0x02;
859                         Arg[1].Type = ACPI_TYPE_INTEGER;
860                         Arg[1].Integer.Value = 0x03;
861
862                         Args.Count = 2;
863                         Args.Pointer = Arg;
864
865                         Buf.Length = sizeof(Obj);
866                         Buf.Pointer = &Obj;
867
868                         status = AcpiEvaluateObject(sc->handle,
869                             sc->model->lcd_get, &Args, &Buf);
870                         if (ACPI_SUCCESS(status) &&
871                             Obj.Type == ACPI_TYPE_INTEGER) {
872                                 sc->s_lcd = Obj.Integer.Value >> 8;
873                                 return (TRUE);
874                         }
875                 }
876                 return (FALSE);
877         }
878         return (FALSE);
879 }
880
881 static void
882 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
883 {
884         struct acpi_asus_softc  *sc;
885         struct acpi_softc       *acpi_sc;
886
887         ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
888
889         sc = device_get_softc((device_t)context);
890         acpi_sc = acpi_device_get_parent_softc(sc->dev);
891
892         ACPI_SERIAL_BEGIN(asus);
893         if ((notify & ~0x10) <= 15) {
894                 sc->s_brn = notify & ~0x10;
895                 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
896         } else if ((notify & ~0x20) <= 15) {
897                 sc->s_brn = notify & ~0x20;
898                 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
899         } else if (notify == 0x33) {
900                 sc->s_lcd = 1;
901                 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
902         } else if (notify == 0x34) {
903                 sc->s_lcd = 0;
904                 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
905         } else {
906                 /* Notify devd(8) */
907                 acpi_UserNotify("ASUS", h, notify);
908         }
909         ACPI_SERIAL_END(asus);
910 }