acpi_asus.c revision 143937
1128561Sphilip/*-
2128561Sphilip * Copyright (c) 2004 Philip Paeps <philip@FreeBSD.org>
3128561Sphilip * All rights reserved.
4128561Sphilip *
5128561Sphilip * Redistribution and use in source and binary forms, with or without
6128561Sphilip * modification, are permitted provided that the following conditions
7128561Sphilip * are met:
8128561Sphilip * 1. Redistributions of source code must retain the above copyright
9128561Sphilip *    notice, this list of conditions and the following disclaimer.
10128561Sphilip * 2. Redistributions in binary form must reproduce the above copyright
11128561Sphilip *    notice, this list of conditions and the following disclaimer in the
12128561Sphilip *    documentation and/or other materials provided with the distribution.
13128561Sphilip *
14128561Sphilip * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15128561Sphilip * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16128561Sphilip * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17128561Sphilip * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18128561Sphilip * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19128561Sphilip * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20128561Sphilip * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21128561Sphilip * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22128561Sphilip * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23128561Sphilip * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24128561Sphilip * SUCH DAMAGE.
25128561Sphilip */
26128561Sphilip
27128561Sphilip#include <sys/cdefs.h>
28128561Sphilip__FBSDID("$FreeBSD: head/sys/dev/acpi_support/acpi_asus.c 143937 2005-03-21 18:11:50Z philip $");
29128561Sphilip
30128561Sphilip/*
31128561Sphilip * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on
32133095Sphilip * recent Asus (and Medion) laptops.  Inspired by the acpi4asus project which
33128561Sphilip * implements these features in the Linux kernel.
34128561Sphilip *
35128561Sphilip *   <http://sourceforge.net/projects/acpi4asus/>
36128561Sphilip *
37128561Sphilip * Currently should support most features, but could use some more testing.
38128561Sphilip * Particularly the display-switching stuff is a bit hairy.  If you have an
39128561Sphilip * Asus laptop which doesn't appear to be supported, or strange things happen
40128561Sphilip * when using this driver, please report to <acpi@FreeBSD.org>.
41128561Sphilip */
42128561Sphilip
43128561Sphilip#include "opt_acpi.h"
44128561Sphilip#include <sys/param.h>
45128561Sphilip#include <sys/kernel.h>
46129882Sphk#include <sys/module.h>
47128561Sphilip#include <sys/bus.h>
48128561Sphilip#include <sys/sbuf.h>
49128561Sphilip
50128561Sphilip#include "acpi.h"
51128561Sphilip#include <dev/acpica/acpivar.h>
52128561Sphilip#include <dev/led/led.h>
53128561Sphilip
54143894Sphilip/* Methods */
55143894Sphilip#define ACPI_ASUS_METHOD_BRN	1
56143894Sphilip#define ACPI_ASUS_METHOD_DISP	2
57143894Sphilip#define ACPI_ASUS_METHOD_LCD	3
58143894Sphilip
59138825Snjl#define _COMPONENT	ACPI_OEM
60128561SphilipACPI_MODULE_NAME("ASUS")
61128561Sphilip
62128561Sphilipstruct acpi_asus_model {
63128561Sphilip	char	*name;
64128561Sphilip
65128561Sphilip	char	*mled_set;
66128561Sphilip	char	*tled_set;
67128561Sphilip	char	*wled_set;
68128561Sphilip
69128561Sphilip	char	*brn_get;
70128561Sphilip	char	*brn_set;
71128561Sphilip	char	*brn_up;
72128561Sphilip	char	*brn_dn;
73128561Sphilip
74128561Sphilip	char	*lcd_get;
75128561Sphilip	char	*lcd_set;
76128561Sphilip
77128561Sphilip	char	*disp_get;
78128561Sphilip	char	*disp_set;
79128561Sphilip};
80128561Sphilip
81133095Sphilipstruct acpi_asus_led {
82133095Sphilip	struct cdev	*cdev;
83133095Sphilip	device_t	dev;
84133095Sphilip	enum {
85133095Sphilip		ACPI_ASUS_LED_MLED,
86133095Sphilip		ACPI_ASUS_LED_TLED,
87133095Sphilip		ACPI_ASUS_LED_WLED,
88133095Sphilip	} type;
89133095Sphilip};
90133095Sphilip
91128561Sphilipstruct acpi_asus_softc {
92128561Sphilip	device_t		dev;
93128561Sphilip	ACPI_HANDLE		handle;
94128561Sphilip
95128561Sphilip	struct acpi_asus_model	*model;
96128561Sphilip	struct sysctl_ctx_list	sysctl_ctx;
97128561Sphilip	struct sysctl_oid	*sysctl_tree;
98128561Sphilip
99133095Sphilip	struct acpi_asus_led	s_mled;
100133095Sphilip	struct acpi_asus_led	s_tled;
101133095Sphilip	struct acpi_asus_led	s_wled;
102128561Sphilip
103128561Sphilip	int			s_brn;
104128561Sphilip	int			s_disp;
105128561Sphilip	int			s_lcd;
106128561Sphilip};
107128561Sphilip
108137245Sphilip/*
109137245Sphilip * We can identify Asus laptops from the string they return
110137245Sphilip * as a result of calling the ATK0100 'INIT' method.
111137245Sphilip */
112128561Sphilipstatic struct acpi_asus_model acpi_asus_models[] = {
113128561Sphilip	{
114128561Sphilip		.name		= "L2D",
115128561Sphilip		.mled_set	= "MLED",
116128561Sphilip		.wled_set	= "WLED",
117128561Sphilip		.brn_up		= "\\Q0E",
118128561Sphilip		.brn_dn		= "\\Q0F",
119128561Sphilip		.lcd_get	= "\\SGP0",
120128561Sphilip		.lcd_set	= "\\Q10"
121128561Sphilip	},
122128561Sphilip	{
123128561Sphilip		.name		= "L3C",
124128561Sphilip		.mled_set	= "MLED",
125128561Sphilip		.wled_set	= "WLED",
126128561Sphilip		.brn_get	= "GPLV",
127128561Sphilip		.brn_set	= "SPLV",
128128561Sphilip		.lcd_get	= "\\GL32",
129128561Sphilip		.lcd_set	= "\\_SB.PCI0.PX40.ECD0._Q10"
130128561Sphilip	},
131128561Sphilip	{
132128561Sphilip		.name		= "L3D",
133128561Sphilip		.mled_set	= "MLED",
134128561Sphilip		.wled_set	= "WLED",
135128561Sphilip		.brn_get	= "GPLV",
136128561Sphilip		.brn_set	= "SPLV",
137128561Sphilip		.lcd_get	= "\\BKLG",
138128561Sphilip		.lcd_set	= "\\Q10"
139128561Sphilip	},
140128561Sphilip	{
141128561Sphilip		.name		= "L3H",
142128561Sphilip		.mled_set	= "MLED",
143128561Sphilip		.wled_set	= "WLED",
144128561Sphilip		.brn_get	= "GPLV",
145128561Sphilip		.brn_set	= "SPLV",
146128561Sphilip		.lcd_get	= "\\_SB.PCI0.PM.PBC",
147128561Sphilip		.lcd_set	= "EHK",
148128561Sphilip		.disp_get	= "\\_SB.INFB",
149128561Sphilip		.disp_set	= "SDSP"
150128561Sphilip	},
151128561Sphilip	{
152137388Sphilip		.name		= "L4R",
153137388Sphilip		.mled_set	= "MLED",
154137388Sphilip		.wled_set	= "WLED",
155137388Sphilip		.brn_get	= "GPLV",
156137388Sphilip		.brn_set	= "SPLV",
157137388Sphilip		.lcd_get	= "\\_SB.PCI0.SBSM.SEO4",
158137388Sphilip		.lcd_set	= "\\_SB.PCI0.SBRG.EC0._Q10",
159137388Sphilip		.disp_get	= "\\_SB.PCI0.P0P1.VGA.GETD",
160137388Sphilip		.disp_set	= "SDSP"
161137388Sphilip	},
162137388Sphilip	{
163128561Sphilip		.name		= "L8L"
164128561Sphilip		/* Only has hotkeys, apparantly */
165128561Sphilip	},
166128561Sphilip	{
167128561Sphilip		.name		= "M1A",
168128561Sphilip		.mled_set	= "MLED",
169128561Sphilip		.brn_up		= "\\_SB.PCI0.PX40.EC0.Q0E",
170128561Sphilip		.brn_dn		= "\\_SB.PCI0.PX40.EC0.Q0F",
171128561Sphilip		.lcd_get	= "\\PNOF",
172128561Sphilip		.lcd_set	= "\\_SB.PCI0.PX40.EC0.Q10"
173128561Sphilip	},
174128561Sphilip	{
175128561Sphilip		.name		= "M2E",
176128561Sphilip		.mled_set	= "MLED",
177128561Sphilip		.wled_set	= "WLED",
178128561Sphilip		.brn_get	= "GPLV",
179128561Sphilip		.brn_set	= "SPLV",
180128561Sphilip		.lcd_get	= "\\GP06",
181128561Sphilip		.lcd_set	= "\\Q10"
182128561Sphilip	},
183128561Sphilip	{
184137127Sphilip		.name		= "M6N",
185137127Sphilip		.mled_set	= "MLED",
186137127Sphilip		.wled_set	= "WLED",
187137127Sphilip		.lcd_set	= "\\_SB.PCI0.SBRG.EC0._Q10",
188137127Sphilip		.lcd_get	= "\\_SB.BKLT",
189137127Sphilip		.brn_set	= "SPLV",
190137127Sphilip		.brn_get	= "GPLV",
191137127Sphilip		.disp_set	= "SDSP",
192137127Sphilip		.disp_get	= "\\SSTE"
193137127Sphilip	},
194137388Sphilip	{
195137388Sphilip		.name		= "M6R",
196137388Sphilip		.mled_set	= "MLED",
197137388Sphilip		.wled_set	= "WLED",
198137388Sphilip		.brn_get	= "GPLV",
199137388Sphilip		.brn_set	= "SPLV",
200137388Sphilip		.lcd_get	= "\\_SB.PCI0.SBSM.SEO4",
201137388Sphilip		.lcd_set	= "\\_SB.PCI0.SBRG.EC0._Q10",
202137388Sphilip		.disp_get	= "\\SSTE",
203137388Sphilip		.disp_set	= "SDSP"
204137388Sphilip	},
205137245Sphilip
206137245Sphilip	{ .name = NULL }
207137245Sphilip};
208137245Sphilip
209137245Sphilip/*
210137245Sphilip * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface,
211137245Sphilip * but they can't be probed quite the same way as Asus laptops.
212137245Sphilip */
213137245Sphilipstatic struct acpi_asus_model acpi_samsung_models[] = {
214137127Sphilip	{
215128561Sphilip		.name		= "P30",
216128561Sphilip		.wled_set	= "WLED",
217128561Sphilip		.brn_up		= "\\_SB.PCI0.LPCB.EC0._Q68",
218128561Sphilip		.brn_dn		= "\\_SB.PCI0.LPCB.EC0._Q69",
219128561Sphilip		.lcd_get	= "\\BKLT",
220128561Sphilip		.lcd_set	= "\\_SB.PCI0.LPCB.EC0._Q0E"
221128561Sphilip	},
222128561Sphilip
223128561Sphilip	{ .name = NULL }
224128561Sphilip};
225128561Sphilip
226143894Sphilipstatic struct {
227143894Sphilip	char	*name;
228143894Sphilip	char	*description;
229143894Sphilip	int	method;
230143894Sphilip} acpi_asus_sysctls[] = {
231143894Sphilip	{
232143894Sphilip		.name		= "lcd_backlight",
233143894Sphilip		.method		= ACPI_ASUS_METHOD_LCD,
234143894Sphilip		.description	= "state of the lcd backlight"
235143894Sphilip	},
236143894Sphilip	{
237143894Sphilip		.name		= "lcd_brightness",
238143894Sphilip		.method		= ACPI_ASUS_METHOD_BRN,
239143894Sphilip		.description	= "brightness of the lcd panel"
240143894Sphilip	},
241143894Sphilip	{
242143894Sphilip		.name		= "video_output",
243143894Sphilip		.method		= ACPI_ASUS_METHOD_DISP,
244143894Sphilip		.description	= "display output state"
245143894Sphilip	},
246143894Sphilip
247143894Sphilip	{ .name = NULL }
248143894Sphilip};
249143894Sphilip
250133628SnjlACPI_SERIAL_DECL(asus, "ACPI ASUS extras");
251133628Snjl
252128561Sphilip/* Function prototypes */
253128561Sphilipstatic int	acpi_asus_probe(device_t dev);
254128561Sphilipstatic int	acpi_asus_attach(device_t dev);
255128561Sphilipstatic int	acpi_asus_detach(device_t dev);
256128561Sphilip
257133095Sphilipstatic void	acpi_asus_led(struct acpi_asus_led *led, int state);
258128561Sphilip
259143894Sphilipstatic int	acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
260143894Sphilipstatic int	acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
261143894Sphilipstatic int	acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method);
262143894Sphilipstatic int	acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val);
263128561Sphilip
264128561Sphilipstatic void	acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
265128561Sphilip
266128561Sphilipstatic device_method_t acpi_asus_methods[] = {
267137631Sphilip	DEVMETHOD(device_probe,  acpi_asus_probe),
268128561Sphilip	DEVMETHOD(device_attach, acpi_asus_attach),
269128561Sphilip	DEVMETHOD(device_detach, acpi_asus_detach),
270128561Sphilip
271128561Sphilip	{ 0, 0 }
272128561Sphilip};
273128561Sphilip
274128561Sphilipstatic driver_t acpi_asus_driver = {
275128561Sphilip	"acpi_asus",
276128561Sphilip	acpi_asus_methods,
277128561Sphilip	sizeof(struct acpi_asus_softc)
278128561Sphilip};
279128561Sphilip
280128561Sphilipstatic devclass_t acpi_asus_devclass;
281128561Sphilip
282128561SphilipDRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
283128561SphilipMODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
284128561Sphilip
285128561Sphilipstatic int
286128561Sphilipacpi_asus_probe(device_t dev)
287128561Sphilip{
288128561Sphilip	struct acpi_asus_model	*model;
289128561Sphilip	struct acpi_asus_softc	*sc;
290128561Sphilip	struct sbuf		*sb;
291128561Sphilip	ACPI_BUFFER		Buf;
292128561Sphilip	ACPI_OBJECT		Arg, *Obj;
293128561Sphilip	ACPI_OBJECT_LIST	Args;
294137631Sphilip	static char		*asus_ids[] = { "ATK0100", NULL };
295128561Sphilip
296128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
297128561Sphilip
298137632Sphilip	if (acpi_disabled("asus") ||
299137632Sphilip	    ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids) == NULL)
300137632Sphilip		return (ENXIO);
301137631Sphilip
302137632Sphilip	sc = device_get_softc(dev);
303137632Sphilip	sc->dev = dev;
304137632Sphilip	sc->handle = acpi_get_handle(dev);
305128561Sphilip
306137632Sphilip	Arg.Type = ACPI_TYPE_INTEGER;
307137632Sphilip	Arg.Integer.Value = 0;
308128561Sphilip
309137632Sphilip	Args.Count = 1;
310137632Sphilip	Args.Pointer = &Arg;
311137631Sphilip
312137632Sphilip	Buf.Pointer = NULL;
313137632Sphilip	Buf.Length = ACPI_ALLOCATE_BUFFER;
314128561Sphilip
315137632Sphilip	AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
316137632Sphilip	Obj = Buf.Pointer;
317137245Sphilip
318137632Sphilip	/*
319137632Sphilip	 * The Samsung P30 returns a null-pointer from INIT, we
320137632Sphilip	 * can identify it from the 'ODEM' string in the DSDT.
321137632Sphilip	 */
322137632Sphilip	if (Obj->String.Pointer == NULL) {
323137632Sphilip		ACPI_STATUS		status;
324137632Sphilip		ACPI_TABLE_HEADER	th;
325137245Sphilip
326137632Sphilip		status = AcpiGetTableHeader(ACPI_TABLE_DSDT, 1, &th);
327137632Sphilip		if (ACPI_FAILURE(status)) {
328137632Sphilip			device_printf(dev, "Unsupported (Samsung?) laptop\n");
329137632Sphilip			AcpiOsFree(Buf.Pointer);
330137632Sphilip			return (ENXIO);
331137245Sphilip		}
332137245Sphilip
333137632Sphilip		if (strncmp("ODEM", th.OemTableId, 4) == 0) {
334137632Sphilip			sc->model = &acpi_samsung_models[0];
335137632Sphilip			device_set_desc(dev, "Samsung P30 Laptop Extras");
336137632Sphilip			AcpiOsFree(Buf.Pointer);
337137632Sphilip			return (0);
338137632Sphilip		}
339137632Sphilip	}
340137245Sphilip
341137632Sphilip	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
342137632Sphilip	if (sb == NULL)
343137632Sphilip		return (ENOMEM);
344128561Sphilip
345137632Sphilip	/*
346137632Sphilip	 * Asus laptops are simply identified by name, easy!
347137632Sphilip	 */
348137632Sphilip	for (model = acpi_asus_models; model->name != NULL; model++)
349137632Sphilip		if (strncmp(Obj->String.Pointer, model->name, 3) == 0) {
350137632Sphilip			sbuf_printf(sb, "Asus %s Laptop Extras", model->name);
351137632Sphilip			sbuf_finish(sb);
352128561Sphilip
353137632Sphilip			sc->model = model;
354137632Sphilip			device_set_desc(dev, sbuf_data(sb));
355128561Sphilip
356137632Sphilip			sbuf_delete(sb);
357137632Sphilip			AcpiOsFree(Buf.Pointer);
358137632Sphilip			return (0);
359137632Sphilip		}
360128561Sphilip
361137632Sphilip	sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer);
362137632Sphilip	sbuf_finish(sb);
363128561Sphilip
364137632Sphilip	device_printf(dev, sbuf_data(sb));
365137632Sphilip
366137632Sphilip	sbuf_delete(sb);
367137632Sphilip	AcpiOsFree(Buf.Pointer);
368137632Sphilip
369128561Sphilip	return (ENXIO);
370128561Sphilip}
371128561Sphilip
372128561Sphilipstatic int
373128561Sphilipacpi_asus_attach(device_t dev)
374128561Sphilip{
375128561Sphilip	struct acpi_asus_softc	*sc;
376128561Sphilip	struct acpi_softc	*acpi_sc;
377128561Sphilip
378128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
379128561Sphilip
380128561Sphilip	sc = device_get_softc(dev);
381128561Sphilip	acpi_sc = acpi_device_get_parent_softc(dev);
382128561Sphilip
383128561Sphilip	/* Build sysctl tree */
384128561Sphilip	sysctl_ctx_init(&sc->sysctl_ctx);
385128561Sphilip	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
386128561Sphilip	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
387128561Sphilip	    OID_AUTO, "asus", CTLFLAG_RD, 0, "");
388128561Sphilip
389143894Sphilip	/* Hook up nodes */
390143894Sphilip	for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) {
391143894Sphilip		if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method))
392143894Sphilip			continue;
393143894Sphilip
394143894Sphilip		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
395143894Sphilip		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
396143894Sphilip		    acpi_asus_sysctls[i].name,
397143894Sphilip		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
398143894Sphilip		    sc, i, acpi_asus_sysctl, "I",
399143894Sphilip		    acpi_asus_sysctls[i].description);
400143894Sphilip	}
401143894Sphilip
402128561Sphilip	/* Attach leds */
403133095Sphilip	if (sc->model->mled_set) {
404133095Sphilip		sc->s_mled.dev = dev;
405133095Sphilip		sc->s_mled.type = ACPI_ASUS_LED_MLED;
406133095Sphilip		sc->s_mled.cdev =
407133095Sphilip		    led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
408133095Sphilip	}
409128561Sphilip
410133095Sphilip	if (sc->model->tled_set) {
411133095Sphilip		sc->s_tled.dev = dev;
412133095Sphilip		sc->s_tled.type = ACPI_ASUS_LED_TLED;
413133095Sphilip		sc->s_tled.cdev =
414133095Sphilip		    led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
415133095Sphilip	}
416128561Sphilip
417133095Sphilip	if (sc->model->wled_set) {
418133095Sphilip		sc->s_wled.dev = dev;
419133095Sphilip		sc->s_wled.type = ACPI_ASUS_LED_WLED;
420133095Sphilip		sc->s_wled.cdev =
421133095Sphilip		    led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
422133095Sphilip	}
423128561Sphilip
424128561Sphilip	/* Activate hotkeys */
425128561Sphilip	AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
426128561Sphilip
427128561Sphilip	/* Handle notifies */
428132610Snjl	AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
429132610Snjl	    acpi_asus_notify, dev);
430137631Sphilip
431128561Sphilip	return (0);
432128561Sphilip}
433128561Sphilip
434128561Sphilipstatic int
435128561Sphilipacpi_asus_detach(device_t dev)
436128561Sphilip{
437128561Sphilip	struct acpi_asus_softc	*sc;
438137631Sphilip
439128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
440128561Sphilip
441128561Sphilip	sc = device_get_softc(dev);
442128561Sphilip
443128561Sphilip	/* Turn the lights off */
444128561Sphilip	if (sc->model->mled_set)
445133095Sphilip		led_destroy(sc->s_mled.cdev);
446128561Sphilip
447128561Sphilip	if (sc->model->tled_set)
448133095Sphilip		led_destroy(sc->s_tled.cdev);
449128561Sphilip
450128561Sphilip	if (sc->model->wled_set)
451133095Sphilip		led_destroy(sc->s_wled.cdev);
452128561Sphilip
453128561Sphilip	/* Remove notify handler */
454132610Snjl	AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
455132610Snjl	    acpi_asus_notify);
456128561Sphilip
457128561Sphilip	/* Free sysctl tree */
458128561Sphilip	sysctl_ctx_free(&sc->sysctl_ctx);
459128561Sphilip
460128561Sphilip	return (0);
461128561Sphilip}
462128561Sphilip
463128561Sphilipstatic void
464133095Sphilipacpi_asus_led(struct acpi_asus_led *led, int state)
465128561Sphilip{
466128561Sphilip	struct acpi_asus_softc	*sc;
467133095Sphilip	char			*method;
468128561Sphilip
469128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
470128561Sphilip
471133095Sphilip	sc = device_get_softc(led->dev);
472128561Sphilip
473133095Sphilip	switch (led->type) {
474143894Sphilip	case ACPI_ASUS_LED_MLED:
475143894Sphilip		method = sc->model->mled_set;
476128561Sphilip
477143894Sphilip		/* Note: inverted */
478143894Sphilip		state = !state;
479143894Sphilip		break;
480143894Sphilip	case ACPI_ASUS_LED_TLED:
481143894Sphilip		method = sc->model->tled_set;
482143894Sphilip		break;
483143894Sphilip	case ACPI_ASUS_LED_WLED:
484143894Sphilip		method = sc->model->wled_set;
485143894Sphilip		break;
486143894Sphilip	default:
487143894Sphilip		printf("acpi_asus_led: invalid LED type %d\n",
488143894Sphilip		    (int)led->type);
489143894Sphilip		return;
490133095Sphilip	}
491128561Sphilip
492133095Sphilip	acpi_SetInteger(sc->handle, method, state);
493128561Sphilip}
494128561Sphilip
495128561Sphilipstatic int
496143894Sphilipacpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
497128561Sphilip{
498128561Sphilip	struct acpi_asus_softc	*sc;
499143894Sphilip	int			arg;
500143894Sphilip	int			error = 0;
501143894Sphilip	int			function;
502143894Sphilip	int			method;
503143894Sphilip
504128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
505128561Sphilip
506128561Sphilip	sc = (struct acpi_asus_softc *)oidp->oid_arg1;
507143894Sphilip	function = oidp->oid_arg2;
508143894Sphilip	method = acpi_asus_sysctls[function].method;
509143894Sphilip
510133628Snjl	ACPI_SERIAL_BEGIN(asus);
511143894Sphilip	arg = acpi_asus_sysctl_get(sc, method);
512143894Sphilip	error = sysctl_handle_int(oidp, &arg, 0, req);
513128561Sphilip
514128561Sphilip	/* Sanity check */
515143894Sphilip	if (error != 0 || req->newptr == NULL)
516133092Snjl		goto out;
517128561Sphilip
518143894Sphilip	/* Update */
519143894Sphilip	error = acpi_asus_sysctl_set(sc, method, arg);
520128561Sphilip
521143894Sphilipout:
522143894Sphilip	ACPI_SERIAL_END(asus);
523143894Sphilip	return (error);
524143894Sphilip}
525128561Sphilip
526143894Sphilipstatic int
527143894Sphilipacpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method)
528143894Sphilip{
529143894Sphilip	int val = 0;
530128561Sphilip
531143894Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
532143894Sphilip	ACPI_SERIAL_ASSERT(asus);
533143894Sphilip
534143894Sphilip	switch (method) {
535143894Sphilip	case ACPI_ASUS_METHOD_BRN:
536143894Sphilip		val = sc->s_brn;
537143894Sphilip		break;
538143894Sphilip	case ACPI_ASUS_METHOD_DISP:
539143894Sphilip		val = sc->s_disp;
540143894Sphilip		break;
541143894Sphilip	case ACPI_ASUS_METHOD_LCD:
542143894Sphilip		val = sc->s_lcd;
543143894Sphilip		break;
544128561Sphilip	}
545128561Sphilip
546143894Sphilip	return (val);
547128561Sphilip}
548128561Sphilip
549128561Sphilipstatic int
550143894Sphilipacpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg)
551128561Sphilip{
552143937Sphilip	ACPI_STATUS	status = AE_OK;
553128561Sphilip
554128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
555143894Sphilip	ACPI_SERIAL_ASSERT(asus);
556128561Sphilip
557143894Sphilip	switch (method) {
558143894Sphilip	case ACPI_ASUS_METHOD_BRN:
559143894Sphilip		if (arg < 0 || arg > 15)
560143894Sphilip			return (EINVAL);
561128561Sphilip
562143894Sphilip		if (sc->model->brn_set)
563143894Sphilip			status = acpi_SetInteger(sc->handle,
564143894Sphilip			    sc->model->brn_set, arg);
565143894Sphilip		else {
566143894Sphilip			while (arg != 0) {
567143894Sphilip				status = AcpiEvaluateObject(sc->handle,
568143894Sphilip				    (arg > 0) ?  sc->model->brn_up :
569143894Sphilip				    sc->model->brn_dn, NULL, NULL);
570143894Sphilip				(arg > 0) ? arg-- : arg++;
571143894Sphilip			}
572143894Sphilip		}
573128561Sphilip
574143894Sphilip		if (ACPI_SUCCESS(status))
575143894Sphilip			sc->s_brn = arg;
576128561Sphilip
577143894Sphilip		break;
578143894Sphilip	case ACPI_ASUS_METHOD_DISP:
579143894Sphilip		if (arg < 0 || arg > 7)
580143894Sphilip			return (EINVAL);
581128561Sphilip
582143894Sphilip		status = acpi_SetInteger(sc->handle,
583143894Sphilip		    sc->model->disp_set, arg);
584128561Sphilip
585143894Sphilip		if (ACPI_SUCCESS(status))
586143894Sphilip			sc->s_disp = arg;
587128561Sphilip
588143894Sphilip		break;
589143894Sphilip	case ACPI_ASUS_METHOD_LCD:
590143894Sphilip		if (arg < 0 || arg > 1)
591143894Sphilip			return (EINVAL);
592143894Sphilip
593143894Sphilip		if (strncmp(sc->model->name, "L3H", 3) != 0)
594143894Sphilip			status = AcpiEvaluateObject(sc->handle,
595143894Sphilip			    sc->model->lcd_set, NULL, NULL);
596143894Sphilip		else
597143894Sphilip			status = acpi_SetInteger(sc->handle,
598143894Sphilip			    sc->model->lcd_set, 0x7);
599143894Sphilip
600143894Sphilip		if (ACPI_SUCCESS(status))
601143894Sphilip			sc->s_lcd = arg;
602143894Sphilip
603143894Sphilip		break;
604143894Sphilip	}
605143894Sphilip
606143894Sphilip	return (0);
607128561Sphilip}
608128561Sphilip
609128561Sphilipstatic int
610143894Sphilipacpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method)
611128561Sphilip{
612143894Sphilip	ACPI_STATUS	status;
613128561Sphilip
614143894Sphilip	switch (method) {
615143894Sphilip	case ACPI_ASUS_METHOD_BRN:
616143894Sphilip		if (sc->model->brn_get) {
617143894Sphilip			/* GPLV/SPLV models */
618143894Sphilip			status = acpi_GetInteger(sc->handle,
619143894Sphilip			    sc->model->brn_get, &sc->s_brn);
620143894Sphilip			if (ACPI_SUCCESS(status))
621143894Sphilip				return (TRUE);
622143894Sphilip		} else if (sc->model->brn_up) {
623143894Sphilip			/* Relative models */
624143894Sphilip			status = AcpiEvaluateObject(sc->handle,
625143894Sphilip			    sc->model->brn_up, NULL, NULL);
626143894Sphilip			if (ACPI_FAILURE(status))
627143894Sphilip				return (FALSE);
628128561Sphilip
629143894Sphilip			status = AcpiEvaluateObject(sc->handle,
630143894Sphilip			    sc->model->brn_dn, NULL, NULL);
631143894Sphilip			if (ACPI_FAILURE(status))
632143894Sphilip				return (FALSE);
633128561Sphilip
634143894Sphilip			return (TRUE);
635143894Sphilip		}
636143894Sphilip		return (FALSE);
637143894Sphilip	case ACPI_ASUS_METHOD_DISP:
638143894Sphilip		if (sc->model->disp_get) {
639143894Sphilip			status = acpi_GetInteger(sc->handle,
640143894Sphilip			    sc->model->disp_get, &sc->s_disp);
641143894Sphilip			if (ACPI_SUCCESS(status))
642143894Sphilip				return (TRUE);
643143894Sphilip		}
644143894Sphilip		return (FALSE);
645143894Sphilip	case ACPI_ASUS_METHOD_LCD:
646143894Sphilip		if (sc->model->lcd_get &&
647143894Sphilip		    strncmp(sc->model->name, "L3H", 3) != 0) {
648143894Sphilip			status = acpi_GetInteger(sc->handle,
649143894Sphilip			    sc->model->lcd_get, &sc->s_lcd);
650143894Sphilip			if (ACPI_SUCCESS(status))
651143894Sphilip				return (TRUE);
652143894Sphilip		}
653143894Sphilip		else if (sc->model->lcd_get) {
654143894Sphilip			ACPI_BUFFER		Buf;
655143894Sphilip			ACPI_OBJECT		Arg[2], Obj;
656143894Sphilip			ACPI_OBJECT_LIST	Args;
657128561Sphilip
658143894Sphilip			/* L3H is a bit special */
659143894Sphilip			Arg[0].Type = ACPI_TYPE_INTEGER;
660143894Sphilip			Arg[0].Integer.Value = 0x02;
661143894Sphilip			Arg[1].Type = ACPI_TYPE_INTEGER;
662143894Sphilip			Arg[1].Integer.Value = 0x03;
663128561Sphilip
664143894Sphilip			Args.Count = 2;
665143894Sphilip			Args.Pointer = Arg;
666128561Sphilip
667143894Sphilip			Buf.Length = sizeof(Obj);
668143894Sphilip			Buf.Pointer = &Obj;
669128561Sphilip
670143894Sphilip			status = AcpiEvaluateObject(sc->handle,
671143894Sphilip			    sc->model->lcd_get, &Args, &Buf);
672143894Sphilip			if (ACPI_SUCCESS(status) &&
673143894Sphilip			    Obj.Type == ACPI_TYPE_INTEGER) {
674143894Sphilip				sc->s_lcd = Obj.Integer.Value >> 8;
675143894Sphilip				return (TRUE);
676143894Sphilip			}
677143894Sphilip		}
678143894Sphilip		return (FALSE);
679143894Sphilip	}
680143894Sphilip	return (FALSE);
681128561Sphilip}
682128561Sphilip
683128561Sphilipstatic void
684128561Sphilipacpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
685128561Sphilip{
686128561Sphilip	struct acpi_asus_softc	*sc;
687128561Sphilip	struct acpi_softc	*acpi_sc;
688128561Sphilip
689128561Sphilip	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
690128561Sphilip
691128561Sphilip	sc = device_get_softc((device_t)context);
692128561Sphilip	acpi_sc = acpi_device_get_parent_softc(sc->dev);
693128561Sphilip
694133628Snjl	ACPI_SERIAL_BEGIN(asus);
695128561Sphilip	if ((notify & ~0x10) <= 15) {
696132610Snjl		sc->s_brn = notify & ~0x10;
697128561Sphilip		ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
698128561Sphilip	} else if ((notify & ~0x20) <= 15) {
699132610Snjl		sc->s_brn = notify & ~0x20;
700128561Sphilip		ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
701128561Sphilip	} else if (notify == 0x33) {
702128561Sphilip		sc->s_lcd = 1;
703128561Sphilip		ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
704128561Sphilip	} else if (notify == 0x34) {
705128561Sphilip		sc->s_lcd = 0;
706128561Sphilip		ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
707128561Sphilip	} else {
708128561Sphilip		/* Notify devd(8) */
709128561Sphilip		acpi_UserNotify("ASUS", h, notify);
710128561Sphilip	}
711133628Snjl	ACPI_SERIAL_END(asus);
712128561Sphilip}
713