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