acpi_video.c revision 129879
1126430Snjl/*-
2126430Snjl * Copyright (c) 2002-2003 Taku YAMAMOTO <taku@cent.saitama-u.ac.jp>
3126430Snjl * All rights reserved.
4126430Snjl *
5126430Snjl * Redistribution and use in source and binary forms, with or without
6126430Snjl * modification, are permitted provided that the following conditions
7126430Snjl * are met:
8126430Snjl * 1. Redistributions of source code must retain the above copyright
9126430Snjl *    notice, this list of conditions and the following disclaimer.
10126430Snjl * 2. Redistributions in binary form must reproduce the above copyright
11126430Snjl *    notice, this list of conditions and the following disclaimer in the
12126430Snjl *    documentation and/or other materials provided with the distribution.
13126430Snjl *
14126430Snjl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15126430Snjl * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16126430Snjl * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17126430Snjl * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18126430Snjl * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19126430Snjl * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20126430Snjl * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21126430Snjl * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22126430Snjl * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23126430Snjl * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24126430Snjl * SUCH DAMAGE.
25126430Snjl *
26126430Snjl *	$Id: acpi_vid.c,v 1.4 2003/10/13 10:07:36 taku Exp $
27126430Snjl *	$FreeBSD: head/sys/dev/acpica/acpi_video.c 129879 2004-05-30 20:08:47Z phk $
28126430Snjl */
29126430Snjl
30126430Snjl#include <sys/param.h>
31126430Snjl#include <sys/kernel.h>
32126430Snjl#include <sys/malloc.h>
33129879Sphk#include <sys/module.h>
34126430Snjl#include <sys/bus.h>
35126430Snjl#include <sys/power.h>
36126430Snjl#include <sys/queue.h>
37126430Snjl#include <sys/sysctl.h>
38126430Snjl
39126430Snjl#include "acpi.h"
40126430Snjl#include <dev/acpica/acpivar.h>
41126430Snjl
42126430Snjl/* ACPI video extension driver. */
43126430Snjlstruct acpi_video_output {
44126430Snjl	ACPI_HANDLE	handle;
45126430Snjl	UINT32		adr;
46126430Snjl	STAILQ_ENTRY(acpi_video_output) vo_next;
47126430Snjl	struct {
48126430Snjl		int	num;
49126430Snjl		STAILQ_ENTRY(acpi_video_output) next;
50126430Snjl	} vo_unit;
51126430Snjl	int		vo_brightness;
52126430Snjl	int		vo_fullpower;
53126430Snjl	int		vo_economy;
54126430Snjl	int		vo_numlevels;
55126430Snjl	int		*vo_levels;
56126430Snjl	struct sysctl_ctx_list vo_sysctl_ctx;
57126430Snjl	struct sysctl_oid *vo_sysctl_tree;
58126430Snjl};
59126430Snjl
60126430SnjlSTAILQ_HEAD(acpi_video_output_queue, acpi_video_output);
61126430Snjl
62126430Snjlstruct acpi_video_softc {
63126430Snjl	device_t		device;
64126430Snjl	ACPI_HANDLE		handle;
65126430Snjl	STAILQ_HEAD(, acpi_video_output) vid_outputs;
66126430Snjl	eventhandler_tag	vid_pwr_evh;
67126430Snjl};
68126430Snjl
69126430Snjl/* interfaces */
70126430Snjlstatic int	acpi_video_modevent(struct module*, int, void *);
71126430Snjlstatic int	acpi_video_probe(device_t);
72126430Snjlstatic int	acpi_video_attach(device_t);
73126430Snjlstatic int	acpi_video_detach(device_t);
74126430Snjlstatic int	acpi_video_shutdown(device_t);
75126430Snjlstatic void	acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *);
76126430Snjlstatic void	acpi_video_power_profile(void *);
77126430Snjlstatic void	acpi_video_bind_outputs(struct acpi_video_softc *);
78126430Snjlstatic struct acpi_video_output
79126430Snjl		*acpi_video_vo_init(UINT32);
80126430Snjlstatic void	acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE);
81126430Snjlstatic void	acpi_video_vo_destroy(struct acpi_video_output *);
82126430Snjlstatic int	acpi_video_vo_check_level(struct acpi_video_output *, int);
83126430Snjlstatic int	acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS);
84126430Snjlstatic int	acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS);
85126430Snjlstatic int	acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS);
86126430Snjlstatic int	acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS);
87126430Snjl
88126430Snjl/* operations */
89126430Snjlstatic int	vid_check_requirements(ACPI_HANDLE);
90126430Snjlstatic void	vid_set_switch_policy(ACPI_HANDLE, UINT32);
91126430Snjlstatic int	vid_enum_outputs(ACPI_HANDLE,
92126430Snjl				 void(*)(ACPI_HANDLE, UINT32, void *), void *);
93126430Snjlstatic int	vo_query_brightness_levels(ACPI_HANDLE, int **);
94126430Snjlstatic void	vo_set_brightness(ACPI_HANDLE, int);
95126430Snjlstatic UINT32	vo_get_device_status(ACPI_HANDLE);
96126430Snjlstatic UINT32	vo_query_graphics_state(ACPI_HANDLE);
97126430Snjlstatic void	vo_set_device_state(ACPI_HANDLE, UINT32);
98126430Snjl
99126430Snjl/* events */
100126430Snjl#define VID_NOTIFY_SWITCHED	0x80
101126430Snjl#define VID_NOTIFY_REPROBE	0x81
102126430Snjl
103126430Snjl/* _DOS (Enable/Disable Output Switching) argument bits */
104126430Snjl#define DOS_SWITCH_MASK		((UINT32)3)
105126430Snjl#define DOS_SWITCH_BY_OSPM	((UINT32)0)
106126430Snjl#define DOS_SWITCH_BY_BIOS	((UINT32)1)
107126430Snjl#define DOS_SWITCH_LOCKED	((UINT32)2)
108126430Snjl#define DOS_BRIGHTNESS_BY_BIOS	((UINT32)1 << 2)
109126430Snjl
110126430Snjl/* _DOD and subdev's _ADR */
111126430Snjl#define DOD_DEVID_MASK		((UINT32)0xffff)
112126430Snjl#define DOD_DEVID_MONITOR	((UINT32)0x0100)
113126430Snjl#define DOD_DEVID_PANEL		((UINT32)0x0110)
114126430Snjl#define DOD_DEVID_TV		((UINT32)0x0200)
115126430Snjl#define DOD_BIOS		((UINT32)1 << 16)
116126430Snjl#define DOD_NONVGA		((UINT32)1 << 17)
117126430Snjl#define DOD_HEAD_ID_SHIFT	18
118126430Snjl#define DOD_HEAD_ID_BITS	3
119126430Snjl#define DOD_HEAD_ID_MASK \
120126430Snjl		((((UINT32)1 << DOD_HEAD_ID_BITS) - 1) << DOD_HEAD_ID_SHIFT)
121126430Snjl
122126430Snjl/* _BCL related constants */
123126430Snjl#define BCL_FULLPOWER		0
124126430Snjl#define BCL_ECONOMY		1
125126430Snjl
126126430Snjl/* _DCS (Device Currrent Status) value bits and masks. */
127126430Snjl#define DCS_EXISTS		((UINT32)1 << 0)
128126430Snjl#define DCS_ACTIVE		((UINT32)1 << 1)
129126430Snjl#define DCS_READY		((UINT32)1 << 2)
130126430Snjl#define DCS_FUNCTIONAL		((UINT32)1 << 3)
131126430Snjl#define DCS_ATTACHED		((UINT32)1 << 4)
132126430Snjl
133126430Snjl/* _DSS (Device Set Status) argument bits and masks. */
134126430Snjl#define DSS_INACTIVE		((UINT32)0)
135126430Snjl#define DSS_ACTIVE		((UINT32)1 << 0)
136126430Snjl#define DSS_ACTIVITY		((UINT32)1 << 0)
137126430Snjl#define DSS_SETNEXT		((UINT32)1 << 30)
138126430Snjl#define DSS_COMMIT		((UINT32)1 << 31)
139126430Snjl
140126430Snjlstatic device_method_t acpi_video_methods[] = {
141126430Snjl	DEVMETHOD(device_probe, acpi_video_probe),
142126430Snjl	DEVMETHOD(device_attach, acpi_video_attach),
143126430Snjl	DEVMETHOD(device_detach, acpi_video_detach),
144126430Snjl	DEVMETHOD(device_shutdown, acpi_video_shutdown),
145126430Snjl	{ 0, 0 }
146126430Snjl};
147126430Snjl
148126430Snjlstatic driver_t acpi_video_driver = {
149126430Snjl	"acpi_video",
150126430Snjl	acpi_video_methods,
151126430Snjl	sizeof(struct acpi_video_softc),
152126430Snjl};
153126430Snjl
154126430Snjlstatic devclass_t acpi_video_devclass;
155126430Snjl
156126430SnjlDRIVER_MODULE(acpi_video, acpi, acpi_video_driver, acpi_video_devclass,
157126430Snjl	      acpi_video_modevent, NULL);
158128036SnjlMODULE_DEPEND(acpi_video, acpi, 1, 1, 1);
159126430Snjl
160126430Snjlstruct sysctl_ctx_list	acpi_video_sysctl_ctx;
161126430Snjlstruct sysctl_oid	*acpi_video_sysctl_tree;
162126430Snjl
163126430Snjlstatic struct acpi_video_output_queue
164126430Snjl		lcd_units, crt_units, tv_units, other_units;
165126430Snjl
166126430SnjlMALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension");
167126430Snjl
168126430Snjlstatic int
169126430Snjlacpi_video_modevent(struct module *mod __unused, int evt, void *cookie __unused)
170126430Snjl{
171126430Snjl	int err = 0;
172126430Snjl
173126430Snjl	switch (evt) {
174126430Snjl	case MOD_LOAD:
175126430Snjl		acpi_video_sysctl_tree = NULL;
176126430Snjl		sysctl_ctx_init(&acpi_video_sysctl_ctx);
177126430Snjl		STAILQ_INIT(&lcd_units);
178126430Snjl		STAILQ_INIT(&crt_units);
179126430Snjl		STAILQ_INIT(&tv_units);
180126430Snjl		STAILQ_INIT(&other_units);
181126430Snjl		break;
182126430Snjl	case MOD_UNLOAD:
183126430Snjl		acpi_video_sysctl_tree = NULL;
184126430Snjl		sysctl_ctx_free(&acpi_video_sysctl_ctx);
185126430Snjl		break;
186126430Snjl	default:
187126430Snjl		err = EINVAL;
188126430Snjl	}
189126430Snjl
190126430Snjl	return (err);
191126430Snjl}
192126430Snjl
193126430Snjlstatic int
194126430Snjlacpi_video_probe(device_t dev)
195126430Snjl{
196126430Snjl	int err = ENXIO;
197126430Snjl	ACPI_HANDLE handle;
198126430Snjl	ACPI_LOCK_DECL;
199126430Snjl
200126430Snjl	ACPI_LOCK;
201126430Snjl	handle = acpi_get_handle(dev);
202126430Snjl	if (acpi_get_type(dev) == ACPI_TYPE_DEVICE &&
203126430Snjl	    !acpi_disabled("video") &&
204126430Snjl	    vid_check_requirements(handle)) {
205126430Snjl		device_set_desc(dev, "ACPI video extension");
206126430Snjl		err = 0;
207126430Snjl	}
208126430Snjl	ACPI_UNLOCK;
209126430Snjl
210126430Snjl	return (err);
211126430Snjl}
212126430Snjl
213126430Snjlstatic int
214126430Snjlacpi_video_attach(device_t dev)
215126430Snjl{
216126430Snjl	struct acpi_softc *acpi_sc;
217126430Snjl	struct acpi_video_softc *sc;
218126430Snjl	ACPI_LOCK_DECL;
219126430Snjl
220126430Snjl	sc = device_get_softc(dev);
221126430Snjl	ACPI_LOCK;
222126430Snjl
223126430Snjl	acpi_sc = acpi_device_get_parent_softc(dev);
224126430Snjl	if (acpi_video_sysctl_tree == NULL && acpi_sc != NULL) {
225126430Snjl		acpi_video_sysctl_tree = SYSCTL_ADD_NODE(&acpi_video_sysctl_ctx,
226126430Snjl				    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
227126430Snjl				    OID_AUTO, "video", CTLFLAG_RD, 0,
228126430Snjl				    "video extension control");
229126430Snjl	}
230126430Snjl
231126430Snjl	sc->device = dev;
232126430Snjl	sc->handle = acpi_get_handle(dev);
233126430Snjl	STAILQ_INIT(&sc->vid_outputs);
234126430Snjl
235126430Snjl	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
236126430Snjl				 acpi_video_notify_handler, sc);
237126430Snjl	sc->vid_pwr_evh = EVENTHANDLER_REGISTER(power_profile_change,
238126430Snjl				 acpi_video_power_profile, sc, 0);
239126430Snjl
240126430Snjl	acpi_video_bind_outputs(sc);
241126430Snjl	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_OSPM);
242126430Snjl
243126430Snjl	ACPI_UNLOCK;
244126430Snjl	acpi_video_power_profile(sc);
245126430Snjl
246126430Snjl	return (0);
247126430Snjl}
248126430Snjl
249126430Snjlstatic int
250126430Snjlacpi_video_detach(device_t dev)
251126430Snjl{
252126430Snjl	struct acpi_video_softc *sc;
253126430Snjl	struct acpi_video_output *vo, *vn;
254126430Snjl	ACPI_LOCK_DECL;
255126430Snjl
256126430Snjl	sc = device_get_softc(dev);
257126430Snjl	ACPI_LOCK;
258126430Snjl
259126430Snjl	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
260126430Snjl	EVENTHANDLER_DEREGISTER(power_profile_change, sc->vid_pwr_evh);
261126430Snjl	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
262126430Snjl				acpi_video_notify_handler);
263126430Snjl
264126430Snjl	for (vo = STAILQ_FIRST(&sc->vid_outputs); vo != NULL; vo = vn) {
265126430Snjl		vn = STAILQ_NEXT(vo, vo_next);
266126430Snjl		acpi_video_vo_destroy(vo);
267126430Snjl	}
268126430Snjl
269126430Snjl	ACPI_UNLOCK;
270126430Snjl	return (0);
271126430Snjl}
272126430Snjl
273126430Snjlstatic int
274126430Snjlacpi_video_shutdown(device_t dev)
275126430Snjl{
276126430Snjl	struct acpi_video_softc *sc;
277126430Snjl	ACPI_LOCK_DECL;
278126430Snjl
279126430Snjl	sc = device_get_softc(dev);
280126430Snjl	ACPI_LOCK;
281126430Snjl	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
282126430Snjl	ACPI_UNLOCK;
283126430Snjl
284126430Snjl	return (0);
285126430Snjl}
286126430Snjl
287126430Snjlstatic void
288126430Snjlacpi_video_notify_handler(ACPI_HANDLE handle __unused, UINT32 notify, void *context)
289126430Snjl{
290126430Snjl	struct acpi_video_softc *sc;
291126430Snjl	struct acpi_video_output *vo;
292126430Snjl	ACPI_HANDLE lasthand = NULL;
293126430Snjl	UINT32 dcs, dss, dss_p = 0;
294126430Snjl
295126430Snjl	ACPI_ASSERTLOCK;
296126430Snjl	sc = context;
297126430Snjl
298126430Snjl	switch (notify) {
299126430Snjl	case VID_NOTIFY_SWITCHED:
300126430Snjl		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
301126430Snjl			dss = vo_query_graphics_state(vo->handle);
302126430Snjl			dcs = vo_get_device_status(vo->handle);
303126430Snjl			if (!(dcs & DCS_READY))
304126430Snjl				dss = DSS_INACTIVE;
305126430Snjl			if (((dcs & DCS_ACTIVE) && dss == DSS_INACTIVE) ||
306126430Snjl			    (!(dcs & DCS_ACTIVE) && dss == DSS_ACTIVE)) {
307126430Snjl				if (lasthand != NULL)
308126430Snjl					vo_set_device_state(lasthand, dss_p);
309126430Snjl				dss_p = dss;
310126430Snjl				lasthand = vo->handle;
311126430Snjl			}
312126430Snjl		}
313126430Snjl		if (lasthand != NULL)
314126430Snjl			vo_set_device_state(lasthand, dss_p|DSS_COMMIT);
315126430Snjl		break;
316126430Snjl	case VID_NOTIFY_REPROBE:
317126430Snjl		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next)
318126430Snjl			vo->handle = NULL;
319126430Snjl		acpi_video_bind_outputs(sc);
320126430Snjl		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
321126430Snjl			if (vo->handle == NULL) {
322126430Snjl				STAILQ_REMOVE(&sc->vid_outputs, vo,
323126430Snjl					      acpi_video_output, vo_next);
324126430Snjl				acpi_video_vo_destroy(vo);
325126430Snjl			}
326126430Snjl		}
327126430Snjl		break;
328126430Snjl	default:
329126430Snjl		device_printf(sc->device,
330126430Snjl			      "unknown notify event 0x%x\n", notify);
331126430Snjl	}
332126430Snjl}
333126430Snjl
334126430Snjlstatic void
335126430Snjlacpi_video_power_profile(void *context)
336126430Snjl{
337126430Snjl	int state;
338126430Snjl	struct acpi_video_softc *sc;
339126430Snjl	struct acpi_video_output *vo;
340126430Snjl	ACPI_LOCK_DECL;
341126430Snjl
342126430Snjl	sc = context;
343126430Snjl	state = power_profile_get_state();
344126430Snjl	if (state != POWER_PROFILE_PERFORMANCE &&
345126430Snjl	    state != POWER_PROFILE_ECONOMY)
346126430Snjl		return;
347126430Snjl
348126430Snjl	ACPI_LOCK;
349126430Snjl	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
350126430Snjl		if (vo->vo_levels != NULL && vo->vo_brightness == -1)
351126430Snjl			vo_set_brightness(vo->handle,
352126430Snjl					  state == POWER_PROFILE_ECONOMY
353126430Snjl					  ? vo->vo_economy : vo->vo_fullpower);
354126430Snjl	}
355126430Snjl	ACPI_UNLOCK;
356126430Snjl}
357126430Snjl
358126430Snjlstatic void
359126430Snjlacpi_video_bind_outputs_subr(ACPI_HANDLE handle, UINT32 adr, void *context)
360126430Snjl{
361126430Snjl	struct acpi_video_softc *sc;
362126430Snjl	struct acpi_video_output *vo;
363126430Snjl
364126430Snjl	sc = context;
365126430Snjl
366126430Snjl	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
367126430Snjl		if (vo->adr == adr) {
368126430Snjl			acpi_video_vo_bind(vo, handle);
369126430Snjl			return;
370126430Snjl		}
371126430Snjl	}
372126430Snjl	vo = acpi_video_vo_init(adr);
373126430Snjl	if (vo != NULL) {
374126430Snjl		acpi_video_vo_bind(vo, handle);
375126430Snjl		STAILQ_INSERT_TAIL(&sc->vid_outputs, vo, vo_next);
376126430Snjl	}
377126430Snjl}
378126430Snjl
379126430Snjlstatic void
380126430Snjlacpi_video_bind_outputs(struct acpi_video_softc *sc)
381126430Snjl{
382126430Snjl	ACPI_ASSERTLOCK;
383126430Snjl
384126430Snjl	vid_enum_outputs(sc->handle, acpi_video_bind_outputs_subr, sc);
385126430Snjl}
386126430Snjl
387126430Snjlstatic struct acpi_video_output *
388126430Snjlacpi_video_vo_init(UINT32 adr)
389126430Snjl{
390126430Snjl	struct acpi_video_output *vn, *vo, *vp;
391126430Snjl	int n, x;
392126430Snjl	char name[64], env[128];
393126430Snjl	const char *type, *desc;
394126430Snjl	struct acpi_video_output_queue *voqh;
395126430Snjl
396126430Snjl	switch (adr & DOD_DEVID_MASK) {
397126430Snjl	case DOD_DEVID_MONITOR:
398126430Snjl		desc = "CRT monitor";
399126430Snjl		type = "crt";
400126430Snjl		voqh = &crt_units;
401126430Snjl		break;
402126430Snjl	case DOD_DEVID_PANEL:
403126430Snjl		desc = "LCD panel";
404126430Snjl		type = "lcd";
405126430Snjl		voqh = &lcd_units;
406126430Snjl		break;
407126430Snjl	case DOD_DEVID_TV:
408126430Snjl		desc = "TV";
409126430Snjl		type = "tv";
410126430Snjl		voqh = &tv_units;
411126430Snjl		break;
412126430Snjl	default:
413126430Snjl		desc = "unknown output";
414126430Snjl		type = "out";
415126430Snjl		voqh = &other_units;
416126430Snjl	}
417126430Snjl
418126430Snjl	n = 0;
419126430Snjl	vn = vp = NULL;
420126430Snjl	/* XXX - needs locking for protecting STAILQ xxx_units. */
421126430Snjl	STAILQ_FOREACH(vn, voqh, vo_unit.next) {
422126430Snjl		if (vn->vo_unit.num != n)
423126430Snjl			break;
424126430Snjl		vp = vn;
425126430Snjl		n++;
426126430Snjl	}
427126430Snjl
428126430Snjl	snprintf(name, 64, "%s%d", type, n);
429126430Snjl
430126430Snjl	vo = malloc(sizeof(*vo), M_ACPIVIDEO, M_NOWAIT);
431126430Snjl	if (vo != NULL) {
432126430Snjl		vo->handle = NULL;
433126430Snjl		vo->adr = adr;
434126430Snjl		vo->vo_unit.num = n;
435126430Snjl		vo->vo_brightness = -1;
436126430Snjl		vo->vo_fullpower = -1;	/* TODO: override with tunables */
437126430Snjl		vo->vo_economy = -1;
438126430Snjl		vo->vo_numlevels = 0;
439126430Snjl		vo->vo_levels = NULL;
440126430Snjl		snprintf(env, 128, "hw.acpi.video.%s.fullpower", name);
441126430Snjl		if (getenv_int(env, &x))
442126430Snjl			vo->vo_fullpower = x;
443126430Snjl		snprintf(env, 128, "hw.acpi.video.%s.economy", name);
444126430Snjl		if (getenv_int(env, &x))
445126430Snjl			vo->vo_economy = x;
446126430Snjl
447126430Snjl		sysctl_ctx_init(&vo->vo_sysctl_ctx);
448126430Snjl		if (vp != NULL)
449126430Snjl			STAILQ_INSERT_AFTER(voqh, vp, vo, vo_unit.next);
450126430Snjl		else
451126430Snjl			STAILQ_INSERT_TAIL(voqh, vo, vo_unit.next);
452126430Snjl		if (acpi_video_sysctl_tree != NULL)
453126430Snjl			vo->vo_sysctl_tree =
454126430Snjl			    SYSCTL_ADD_NODE(&vo->vo_sysctl_ctx,
455126430Snjl					    SYSCTL_CHILDREN(acpi_video_sysctl_tree),
456126430Snjl					    OID_AUTO, name,
457126430Snjl					    CTLFLAG_RD, 0, desc);
458126430Snjl		if (vo->vo_sysctl_tree != NULL) {
459126430Snjl			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
460126430Snjl					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
461126430Snjl					OID_AUTO, "active",
462126430Snjl					CTLTYPE_INT|CTLFLAG_RW,	vo, 0,
463126430Snjl					acpi_video_vo_active_sysctl, "I",
464126430Snjl					"current activity of this device");
465126430Snjl			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
466126430Snjl					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
467126430Snjl					OID_AUTO, "brightness",
468126430Snjl					CTLTYPE_INT|CTLFLAG_RW, vo, 0,
469126430Snjl					acpi_video_vo_bright_sysctl, "I",
470126430Snjl					"current brightness level");
471126430Snjl			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
472126430Snjl					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
473126430Snjl					OID_AUTO, "fullpower",
474126430Snjl					CTLTYPE_INT|CTLFLAG_RW, vo,
475126430Snjl					POWER_PROFILE_PERFORMANCE,
476126430Snjl					acpi_video_vo_presets_sysctl, "I",
477126430Snjl					"preset level for full power mode");
478126430Snjl			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
479126430Snjl					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
480126430Snjl					OID_AUTO, "economy",
481126430Snjl					CTLTYPE_INT|CTLFLAG_RW, vo,
482126430Snjl					POWER_PROFILE_ECONOMY,
483126430Snjl					acpi_video_vo_presets_sysctl, "I",
484126430Snjl					"preset level for economy mode");
485126430Snjl			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
486126430Snjl					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
487126430Snjl					OID_AUTO, "levels",
488126430Snjl					CTLTYPE_OPAQUE|CTLFLAG_RD, vo, 0,
489126430Snjl					acpi_video_vo_levels_sysctl, "I",
490126430Snjl					"supported brightness levels");
491126430Snjl		} else
492126430Snjl			printf("%s: sysctl node creation failed\n", type);
493126430Snjl	} else
494126430Snjl		printf("%s: softc allocation failed\n", type);
495126430Snjl
496126430Snjl	/* XXX unlock here - needs locking for protecting STAILQ xxx_units. */
497126430Snjl
498126430Snjl	if (bootverbose) {
499126430Snjl		printf("found %s(%x)", desc,
500126430Snjl		       (unsigned int)(adr & DOD_DEVID_MASK));
501126430Snjl		if (adr & DOD_BIOS)
502126430Snjl			printf(", detectable by BIOS");
503126430Snjl		if (adr & DOD_NONVGA)
504126430Snjl			printf(" (not a VGA output)");
505126430Snjl		printf(", head #%d\n",
506126430Snjl		       (int)((adr & DOD_HEAD_ID_MASK) >> DOD_HEAD_ID_SHIFT));
507126430Snjl	}
508126430Snjl	return vo;
509126430Snjl}
510126430Snjl
511126430Snjlstatic void
512126430Snjlacpi_video_vo_bind(struct acpi_video_output *vo, ACPI_HANDLE handle)
513126430Snjl{
514126430Snjl	ACPI_ASSERTLOCK;
515126430Snjl
516126430Snjl	if (vo->vo_levels != NULL)
517126430Snjl		AcpiOsFree(vo->vo_levels);
518126430Snjl	vo->handle = handle;
519126430Snjl	vo->vo_numlevels
520126430Snjl		= vo_query_brightness_levels(handle, &vo->vo_levels);
521126430Snjl	if (vo->vo_numlevels >= 2) {
522126430Snjl		if (vo->vo_fullpower == -1
523126430Snjl		    || acpi_video_vo_check_level(vo, vo->vo_fullpower) != 0)
524126430Snjl			/* XXX - can't deal with rebinding... */
525126430Snjl			vo->vo_fullpower = vo->vo_levels[BCL_FULLPOWER];
526126430Snjl		if (vo->vo_economy == -1
527126430Snjl		    || acpi_video_vo_check_level(vo, vo->vo_economy) != 0)
528126430Snjl			/* XXX - see above. */
529126430Snjl			vo->vo_economy = vo->vo_levels[BCL_ECONOMY];
530126430Snjl	}
531126430Snjl}
532126430Snjl
533126430Snjlstatic void
534126430Snjlacpi_video_vo_destroy(struct acpi_video_output *vo)
535126430Snjl{
536126430Snjl	struct acpi_video_output_queue *voqh;
537126430Snjl
538126430Snjl	ACPI_ASSERTLOCK;
539126430Snjl
540126430Snjl	if (vo->vo_sysctl_tree != NULL) {
541126430Snjl		vo->vo_sysctl_tree = NULL;
542126430Snjl		sysctl_ctx_free(&vo->vo_sysctl_ctx);
543126430Snjl	}
544126430Snjl	if (vo->vo_levels != NULL)
545126430Snjl		AcpiOsFree(vo->vo_levels);
546126430Snjl
547126430Snjl	switch (vo->adr & DOD_DEVID_MASK) {
548126430Snjl	case DOD_DEVID_MONITOR:
549126430Snjl		voqh = &crt_units;
550126430Snjl		break;
551126430Snjl	case DOD_DEVID_PANEL:
552126430Snjl		voqh = &lcd_units;
553126430Snjl		break;
554126430Snjl	case DOD_DEVID_TV:
555126430Snjl		voqh = &tv_units;
556126430Snjl		break;
557126430Snjl	default:
558126430Snjl		voqh = &other_units;
559126430Snjl	}
560126430Snjl	/* XXX - needs locking for protecting STAILQ xxx_units. */
561126430Snjl	STAILQ_REMOVE(voqh, vo, acpi_video_output, vo_unit.next);
562126430Snjl	free(vo, M_ACPIVIDEO);
563126430Snjl}
564126430Snjl
565126430Snjlstatic int
566126430Snjlacpi_video_vo_check_level(struct acpi_video_output *vo, int level)
567126430Snjl{
568126430Snjl	int i;
569126430Snjl
570126430Snjl	if (vo->vo_levels == NULL)
571126430Snjl		return (ENODEV);
572126430Snjl	for (i = 0; i < vo->vo_numlevels; i++)
573126430Snjl		if (vo->vo_levels[i] == level)
574126430Snjl			return (0);
575126430Snjl	return (EINVAL);
576126430Snjl}
577126430Snjl
578126430Snjl/* ARGSUSED */
579126430Snjlstatic int
580126430Snjlacpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS)
581126430Snjl{
582126430Snjl	struct acpi_video_output *vo;
583126430Snjl	int state, err;
584126430Snjl	ACPI_LOCK_DECL;
585126430Snjl
586126430Snjl	ACPI_LOCK;
587126430Snjl	vo = (struct acpi_video_output *)arg1;
588126430Snjl	if (vo->handle == NULL) {
589126430Snjl		err = ENXIO;
590126430Snjl		goto out;
591126430Snjl	}
592126430Snjl	state = vo_get_device_status(vo->handle) & DCS_ACTIVE? 1 : 0;
593126430Snjl	err = sysctl_handle_int(oidp, &state, 0, req);
594126430Snjl	if (err != 0 || req->newptr == NULL)
595126430Snjl		goto out;
596126430Snjl	vo_set_device_state(vo->handle,
597126430Snjl			    DSS_COMMIT | (state? DSS_ACTIVE : DSS_INACTIVE));
598126430Snjlout:
599126430Snjl	ACPI_UNLOCK;
600126430Snjl	return (err);
601126430Snjl}
602126430Snjl
603126430Snjl/* ARGSUSED */
604126430Snjlstatic int
605126430Snjlacpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS)
606126430Snjl{
607126430Snjl	struct acpi_video_output *vo;
608126430Snjl	int level, preset, err;
609126430Snjl	ACPI_LOCK_DECL;
610126430Snjl
611126430Snjl	ACPI_LOCK;
612126430Snjl	vo = (struct acpi_video_output *)arg1;
613126430Snjl	if (vo->handle == NULL) {
614126430Snjl		err = ENXIO;
615126430Snjl		goto out;
616126430Snjl	}
617126430Snjl	if (vo->vo_levels == NULL) {
618126430Snjl		err = ENODEV;
619126430Snjl		goto out;
620126430Snjl	}
621126430Snjl
622126430Snjl	preset = (power_profile_get_state() == POWER_PROFILE_ECONOMY
623126430Snjl		  ? vo->vo_economy
624126430Snjl		  : vo->vo_fullpower);
625126430Snjl	level = vo->vo_brightness;
626126430Snjl	if (level == -1)
627126430Snjl		level = preset;
628126430Snjl
629126430Snjl	err = sysctl_handle_int(oidp, &level, 0, req);
630126430Snjl	if (err != 0 || req->newptr == NULL)
631126430Snjl		goto out;
632126430Snjl	if (level < -1 || level > 100) {
633126430Snjl		err = EINVAL;
634126430Snjl		goto out;
635126430Snjl	}
636126430Snjl
637126430Snjl	if (level != -1 && (err = acpi_video_vo_check_level(vo, level)))
638126430Snjl		goto out;
639126430Snjl	vo->vo_brightness = level;
640126430Snjl	vo_set_brightness(vo->handle, level == -1? preset : level);
641126430Snjlout:
642126430Snjl	ACPI_UNLOCK;
643126430Snjl	return (err);
644126430Snjl}
645126430Snjl
646126430Snjlstatic int
647126430Snjlacpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS)
648126430Snjl{
649126430Snjl	struct acpi_video_output *vo;
650126430Snjl	int level, *preset, err = 0;
651126430Snjl	ACPI_LOCK_DECL;
652126430Snjl
653126430Snjl	ACPI_LOCK;
654126430Snjl	vo = (struct acpi_video_output *)arg1;
655126430Snjl	if (vo->handle == NULL) {
656126430Snjl		err = ENXIO;
657126430Snjl		goto out;
658126430Snjl	}
659126430Snjl	if (vo->vo_levels == NULL) {
660126430Snjl		err = ENODEV;
661126430Snjl		goto out;
662126430Snjl	}
663126430Snjl	preset = (arg2 == POWER_PROFILE_ECONOMY
664126430Snjl		  ? &vo->vo_economy
665126430Snjl		  : &vo->vo_fullpower);
666126430Snjl	level = *preset;
667126430Snjl	err = sysctl_handle_int(oidp, &level, 0, req);
668126430Snjl	if (err != 0 || req->newptr == NULL)
669126430Snjl		goto out;
670126430Snjl	if (level < -1 || level > 100) {
671126430Snjl		err = EINVAL;
672126430Snjl		goto out;
673126430Snjl	}
674126430Snjl	if (level == -1)
675126430Snjl		level = vo->vo_levels
676126430Snjl			[arg2 == POWER_PROFILE_ECONOMY
677126430Snjl			 ? BCL_ECONOMY : BCL_FULLPOWER];
678126430Snjl	else if ((err = acpi_video_vo_check_level(vo, level)) != 0)
679126430Snjl		goto out;
680126430Snjl
681126430Snjl	if (vo->vo_brightness == -1 && (power_profile_get_state() == arg2))
682126430Snjl		vo_set_brightness(vo->handle, level);
683126430Snjl	*preset = level;
684126430Snjlout:
685126430Snjl	ACPI_UNLOCK;
686126430Snjl	return (err);
687126430Snjl}
688126430Snjl
689126430Snjl/* ARGSUSED */
690126430Snjlstatic int
691126430Snjlacpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS)
692126430Snjl{
693126430Snjl	struct acpi_video_output *vo;
694126430Snjl	int err;
695126430Snjl	ACPI_LOCK_DECL;
696126430Snjl
697126430Snjl	ACPI_LOCK;
698126430Snjl	vo = (struct acpi_video_output *)arg1;
699126430Snjl	if (vo->vo_levels == NULL) {
700126430Snjl		err = ENODEV;
701126430Snjl		goto out;
702126430Snjl	}
703126430Snjl	if (req->newptr != NULL) {
704126430Snjl		err = EPERM;
705126430Snjl		goto out;
706126430Snjl	}
707126430Snjl	err = sysctl_handle_opaque(oidp, vo->vo_levels,
708126430Snjl				vo->vo_numlevels * sizeof *vo->vo_levels, req);
709126430Snjlout:
710126430Snjl	ACPI_UNLOCK;
711126430Snjl	return (err);
712126430Snjl}
713126430Snjl
714126430Snjlstatic int
715126430Snjlvid_check_requirements(ACPI_HANDLE handle)
716126430Snjl{
717126430Snjl	ACPI_HANDLE h_dod, h_dos;
718126430Snjl	ACPI_OBJECT_TYPE t_dos;
719126430Snjl
720126430Snjl	ACPI_ASSERTLOCK;
721126430Snjl
722126430Snjl	/* check for _DOD, _DOS methods */
723126430Snjl	return (ACPI_SUCCESS(AcpiGetHandle(handle, "_DOD", &h_dod))
724126430Snjl	    && ACPI_SUCCESS(AcpiGetHandle(handle, "_DOS", &h_dos))
725126430Snjl	    && ACPI_SUCCESS(AcpiGetType(h_dos, &t_dos))
726126430Snjl	    && t_dos == ACPI_TYPE_METHOD);
727126430Snjl}
728126430Snjl
729126430Snjlstatic void
730126430Snjlvid_set_switch_policy(ACPI_HANDLE handle, UINT32 policy)
731126430Snjl{
732126430Snjl	ACPI_STATUS status;
733126430Snjl
734126430Snjl	ACPI_ASSERTLOCK;
735126560Snjl
736126560Snjl	status = acpi_SetInteger(handle, "_DOS", policy);
737126430Snjl	if (ACPI_FAILURE(status))
738126430Snjl		printf("can't evaluate %s._DOS - %s\n",
739126430Snjl		       acpi_name(handle), AcpiFormatException(status));
740126430Snjl}
741126430Snjl
742126430Snjlstruct enum_callback_arg {
743126430Snjl	void (*callback)(ACPI_HANDLE, UINT32, void *);
744126430Snjl	void *context;
745126430Snjl	ACPI_OBJECT *dod_pkg;
746126430Snjl};
747126430Snjl
748126430Snjlstatic ACPI_STATUS
749126430Snjlvid_enum_outputs_subr(ACPI_HANDLE handle, UINT32 level __unused,
750126430Snjl		      void *context, void **retp)
751126430Snjl{
752126430Snjl	ACPI_STATUS status;
753126430Snjl	ACPI_OBJECT *tmp;
754126430Snjl	UINT32 adr;
755126430Snjl	struct enum_callback_arg *argset;
756126430Snjl	size_t i;
757126430Snjl
758126430Snjl	argset = context;
759126560Snjl	status = acpi_GetInteger(handle, "_ADR", &adr);
760126430Snjl	if (ACPI_SUCCESS(status)) {
761126430Snjl		for (i = 0; i < argset->dod_pkg->Package.Count; i++) {
762126430Snjl			tmp = &argset->dod_pkg->Package.Elements[i];
763126430Snjl			if (tmp != NULL && tmp->Type == ACPI_TYPE_INTEGER &&
764126430Snjl			    (tmp->Integer.Value & DOD_DEVID_MASK) == adr) {
765126430Snjl				argset->callback(handle, tmp->Integer.Value,
766126430Snjl						 argset->context);
767126430Snjl				(**(int**)retp)++;
768126430Snjl			}
769126430Snjl		}
770126430Snjl	}
771126430Snjl
772126430Snjl	return (AE_OK);
773126430Snjl}
774126430Snjl
775126430Snjlstatic int
776126430Snjlvid_enum_outputs(ACPI_HANDLE handle,
777126430Snjl		 void (*callback)(ACPI_HANDLE, UINT32, void *), void *context)
778126430Snjl{
779126430Snjl	ACPI_STATUS status;
780126430Snjl	ACPI_BUFFER dod_buf;
781126430Snjl	ACPI_OBJECT *res;
782126430Snjl	int num = 0;
783126430Snjl	void *pnum;
784126430Snjl	struct enum_callback_arg argset;
785126430Snjl
786126430Snjl	ACPI_ASSERTLOCK;
787126430Snjl	dod_buf.Length = ACPI_ALLOCATE_BUFFER;
788126430Snjl	dod_buf.Pointer = NULL;
789126430Snjl	status = AcpiEvaluateObject(handle, "_DOD", NULL, &dod_buf);
790126430Snjl	if (ACPI_FAILURE(status)) {
791126430Snjl		if (status != AE_NOT_FOUND)
792126430Snjl			printf("can't evaluate %s._DOD - %s\n",
793126430Snjl			       acpi_name(handle), AcpiFormatException(status));
794126430Snjl		num = -1;
795126430Snjl		goto out;
796126430Snjl	}
797126430Snjl	res = (ACPI_OBJECT *)dod_buf.Pointer;
798126430Snjl	if (res == NULL || res->Type != ACPI_TYPE_PACKAGE) {
799126430Snjl		printf("evaluation of %s._DOD makes no sense\n",
800126430Snjl		       acpi_name(handle));
801126430Snjl		num = -1;
802126430Snjl		goto out;
803126430Snjl	}
804126430Snjl	if (callback == NULL) {
805126430Snjl		num = res->Package.Count;
806126430Snjl		goto out;
807126430Snjl	}
808126430Snjl	argset.callback = callback;
809126430Snjl	argset.context  = context;
810126430Snjl	argset.dod_pkg  = res;
811126430Snjl	pnum = &num;
812126430Snjl	status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
813126430Snjl				   vid_enum_outputs_subr, &argset,
814126430Snjl				   &pnum);
815126430Snjl	if (ACPI_FAILURE(status))
816126430Snjl		printf("failed walking down %s - %s\n",
817126430Snjl		       acpi_name(handle), AcpiFormatException(status));
818126430Snjlout:
819126430Snjl	if (dod_buf.Pointer != NULL)
820126430Snjl		AcpiOsFree(dod_buf.Pointer);
821126430Snjl	return (num);
822126430Snjl}
823126430Snjl
824126430Snjlstatic int
825126430Snjlvo_query_brightness_levels(ACPI_HANDLE handle, int **levelp)
826126430Snjl{
827126430Snjl	ACPI_STATUS status;
828126430Snjl	ACPI_BUFFER bcl_buf;
829126430Snjl	ACPI_OBJECT *res, *tmp;
830126430Snjl	int num = 0, i, n, *levels;
831126430Snjl
832126430Snjl	ACPI_ASSERTLOCK;
833126430Snjl	bcl_buf.Length = ACPI_ALLOCATE_BUFFER;
834126430Snjl	bcl_buf.Pointer = NULL;
835126430Snjl	status = AcpiEvaluateObject(handle, "_BCL", NULL, &bcl_buf);
836126430Snjl	if (ACPI_FAILURE(status)) {
837126430Snjl		if (status != AE_NOT_FOUND)
838126430Snjl			printf("can't evaluate %s._BCL - %s\n",
839126430Snjl			       acpi_name(handle), AcpiFormatException(status));
840126430Snjl		num = -1;
841126430Snjl		goto out;
842126430Snjl	}
843126430Snjl	res = (ACPI_OBJECT *)bcl_buf.Pointer;
844126430Snjl	if (res == NULL || res->Type != ACPI_TYPE_PACKAGE ||
845126430Snjl	    res->Package.Count < 2) {
846126430Snjl		printf("evaluation of %s._BCL makes no sense\n",
847126430Snjl		       acpi_name(handle));
848126430Snjl		num = -1;
849126430Snjl		goto out;
850126430Snjl	}
851126430Snjl	num = res->Package.Count;
852126430Snjl	if (levelp == NULL)
853126430Snjl		goto out;
854126430Snjl	levels = AcpiOsAllocate(num * sizeof *levels);
855126430Snjl	if (levels == NULL) {
856126430Snjl		num = -1;
857126430Snjl		goto out;
858126430Snjl	}
859126430Snjl	for (i = 0, n = 0; i < num; i++) {
860126430Snjl		tmp = &res->Package.Elements[i];
861126430Snjl		if (tmp != NULL && tmp->Type == ACPI_TYPE_INTEGER)
862126430Snjl			levels[n++] = tmp->Integer.Value;
863126430Snjl	}
864126430Snjl	if (n < 2) {
865126430Snjl		num = -1;
866126430Snjl		AcpiOsFree(levels);
867126430Snjl	} else {
868126430Snjl		num = n;
869126430Snjl		*levelp = levels;
870126430Snjl	}
871126430Snjlout:
872126430Snjl	if (bcl_buf.Pointer != NULL)
873126430Snjl		AcpiOsFree(bcl_buf.Pointer);
874126430Snjl
875126430Snjl	return (num);
876126430Snjl}
877126430Snjl
878126430Snjlstatic void
879126430Snjlvo_set_brightness(ACPI_HANDLE handle, int level)
880126430Snjl{
881126430Snjl	ACPI_STATUS status;
882126430Snjl
883126430Snjl	ACPI_ASSERTLOCK;
884126560Snjl
885126560Snjl	status = acpi_SetInteger(handle, "_BCM", level);
886126430Snjl	if (ACPI_FAILURE(status))
887126430Snjl		printf("can't evaluate %s._BCM - %s\n",
888126430Snjl		       acpi_name(handle), AcpiFormatException(status));
889126430Snjl}
890126430Snjl
891126430Snjlstatic UINT32
892126430Snjlvo_get_device_status(ACPI_HANDLE handle)
893126430Snjl{
894126430Snjl	UINT32 dcs = 0;
895126430Snjl	ACPI_STATUS status;
896126430Snjl
897126430Snjl	ACPI_ASSERTLOCK;
898126560Snjl	status = acpi_GetInteger(handle, "_DCS", &dcs);
899126430Snjl	if (ACPI_FAILURE(status))
900126430Snjl		printf("can't evaluate %s._DCS - %s\n",
901126430Snjl		       acpi_name(handle), AcpiFormatException(status));
902126430Snjl
903126430Snjl	return (dcs);
904126430Snjl}
905126430Snjl
906126430Snjlstatic UINT32
907126430Snjlvo_query_graphics_state(ACPI_HANDLE handle)
908126430Snjl{
909126430Snjl	UINT32 dgs = 0;
910126430Snjl	ACPI_STATUS status;
911126430Snjl
912126430Snjl	ACPI_ASSERTLOCK;
913126560Snjl	status = acpi_GetInteger(handle, "_DGS", &dgs);
914126430Snjl	if (ACPI_FAILURE(status))
915126430Snjl		printf("can't evaluate %s._DGS - %s\n",
916126430Snjl		       acpi_name(handle), AcpiFormatException(status));
917126430Snjl
918126430Snjl	return (dgs);
919126430Snjl}
920126430Snjl
921126430Snjlstatic void
922126430Snjlvo_set_device_state(ACPI_HANDLE handle, UINT32 state)
923126430Snjl{
924126430Snjl	ACPI_STATUS status;
925126430Snjl
926126430Snjl	ACPI_ASSERTLOCK;
927126560Snjl
928126560Snjl	status = acpi_SetInteger(handle, "_DSS", state);
929126430Snjl	if (ACPI_FAILURE(status))
930126430Snjl		printf("can't evaluate %s._DSS - %s\n",
931126430Snjl		       acpi_name(handle), AcpiFormatException(status));
932126430Snjl}
933