xref: /freebsd/sys/dev/acpica/acpi_video.c (revision 53b70c86)
1 /*-
2  * Copyright (c) 2002-2003 Taku YAMAMOTO <taku@cent.saitama-u.ac.jp>
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  *	$Id: acpi_vid.c,v 1.4 2003/10/13 10:07:36 taku Exp $
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include "opt_evdev.h"
33 
34 #include <sys/param.h>
35 #include <sys/bus.h>
36 #include <sys/eventhandler.h>
37 #include <sys/kernel.h>
38 #include <sys/malloc.h>
39 #include <sys/module.h>
40 #include <sys/power.h>
41 #include <sys/queue.h>
42 #include <sys/sysctl.h>
43 
44 #include <contrib/dev/acpica/include/acpi.h>
45 
46 #include <dev/acpica/acpivar.h>
47 
48 #ifdef EVDEV_SUPPORT
49 #include <dev/evdev/input.h>
50 #include <dev/evdev/evdev.h>
51 #endif
52 
53 /* ACPI video extension driver. */
54 struct acpi_video_output {
55 	ACPI_HANDLE	handle;
56 	UINT32		adr;
57 	STAILQ_ENTRY(acpi_video_output) vo_next;
58 	struct {
59 		int	num;
60 		STAILQ_ENTRY(acpi_video_output) next;
61 	} vo_unit;
62 	int		vo_hasbqc;	/* Query method is present. */
63 	int		vo_level;	/* Cached level when !vo_hasbqc. */
64 	int		vo_brightness;
65 	int		vo_fullpower;
66 	int		vo_economy;
67 	int		vo_numlevels;
68 	int		*vo_levels;
69 	struct sysctl_ctx_list vo_sysctl_ctx;
70 	struct sysctl_oid *vo_sysctl_tree;
71 #ifdef EVDEV_SUPPORT
72 	struct evdev_dev *evdev;
73 #endif
74 };
75 
76 STAILQ_HEAD(acpi_video_output_queue, acpi_video_output);
77 
78 struct acpi_video_softc {
79 	device_t		device;
80 	ACPI_HANDLE		handle;
81 	struct acpi_video_output_queue vid_outputs;
82 	eventhandler_tag	vid_pwr_evh;
83 #ifdef EVDEV_SUPPORT
84 	struct evdev_dev	*evdev;
85 #endif
86 };
87 
88 /* interfaces */
89 static int	acpi_video_modevent(struct module*, int, void *);
90 static void	acpi_video_identify(driver_t *driver, device_t parent);
91 static int	acpi_video_probe(device_t);
92 static int	acpi_video_attach(device_t);
93 static int	acpi_video_detach(device_t);
94 static int	acpi_video_resume(device_t);
95 static int	acpi_video_shutdown(device_t);
96 static void	acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *);
97 static void	acpi_video_power_profile(void *);
98 static void	acpi_video_bind_outputs(struct acpi_video_softc *);
99 static struct acpi_video_output *acpi_video_vo_init(UINT32);
100 static void	acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE);
101 static void	acpi_video_vo_destroy(struct acpi_video_output *);
102 static int	acpi_video_vo_check_level(struct acpi_video_output *, int);
103 static void	acpi_video_vo_notify_handler(ACPI_HANDLE, UINT32, void *);
104 static int	acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS);
105 static int	acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS);
106 static int	acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS);
107 static int	acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS);
108 
109 /* operations */
110 static void	vid_set_switch_policy(ACPI_HANDLE, UINT32);
111 static int	vid_enum_outputs(ACPI_HANDLE,
112 		    void(*)(ACPI_HANDLE, UINT32, void *), void *);
113 static int	vo_get_brightness_levels(ACPI_HANDLE, int **);
114 static int	vo_get_brightness(struct acpi_video_output *);
115 static void	vo_set_brightness(struct acpi_video_output *, int);
116 static UINT32	vo_get_device_status(ACPI_HANDLE);
117 static UINT32	vo_get_graphics_state(ACPI_HANDLE);
118 static void	vo_set_device_state(ACPI_HANDLE, UINT32);
119 
120 /* events */
121 #define	VID_NOTIFY_SWITCHED	0x80
122 #define	VID_NOTIFY_REPROBE	0x81
123 #define	VID_NOTIFY_CYCLE_OUT	0x82
124 #define	VID_NOTIFY_NEXT_OUT	0x83
125 #define	VID_NOTIFY_PREV_OUT	0x84
126 #define	VID_NOTIFY_CYCLE_BRN	0x85
127 #define	VID_NOTIFY_INC_BRN	0x86
128 #define	VID_NOTIFY_DEC_BRN	0x87
129 #define	VID_NOTIFY_ZERO_BRN	0x88
130 #define	VID_NOTIFY_DISP_OFF	0x89
131 
132 /* _DOS (Enable/Disable Output Switching) argument bits */
133 #define	DOS_SWITCH_MASK		3
134 #define	DOS_SWITCH_BY_OSPM	0
135 #define	DOS_SWITCH_BY_BIOS	1
136 #define	DOS_SWITCH_LOCKED	2
137 #define	DOS_BRIGHTNESS_BY_OSPM	(1 << 2)
138 
139 /* _DOD and subdev's _ADR */
140 #define	DOD_DEVID_MASK		0x0f00
141 #define	DOD_DEVID_MASK_FULL	0xffff
142 #define	DOD_DEVID_MASK_DISPIDX	0x000f
143 #define	DOD_DEVID_MASK_DISPPORT	0x00f0
144 #define	DOD_DEVID_MONITOR	0x0100
145 #define	DOD_DEVID_LCD		0x0110
146 #define	DOD_DEVID_TV		0x0200
147 #define	DOD_DEVID_EXT		0x0300
148 #define	DOD_DEVID_INTDFP	0x0400
149 #define	DOD_BIOS		(1 << 16)
150 #define	DOD_NONVGA		(1 << 17)
151 #define	DOD_HEAD_ID_SHIFT	18
152 #define	DOD_HEAD_ID_BITS	3
153 #define	DOD_HEAD_ID_MASK \
154 		(((1 << DOD_HEAD_ID_BITS) - 1) << DOD_HEAD_ID_SHIFT)
155 #define	DOD_DEVID_SCHEME_STD	(1U << 31)
156 
157 /* _BCL related constants */
158 #define	BCL_FULLPOWER		0
159 #define	BCL_ECONOMY		1
160 
161 /* _DCS (Device Currrent Status) value bits and masks. */
162 #define	DCS_EXISTS		(1 << 0)
163 #define	DCS_ACTIVE		(1 << 1)
164 #define	DCS_READY		(1 << 2)
165 #define	DCS_FUNCTIONAL		(1 << 3)
166 #define	DCS_ATTACHED		(1 << 4)
167 
168 /* _DSS (Device Set Status) argument bits and masks. */
169 #define	DSS_INACTIVE		0
170 #define	DSS_ACTIVE		(1 << 0)
171 #define	DSS_SETNEXT		(1 << 30)
172 #define	DSS_COMMIT		(1U << 31)
173 
174 static device_method_t acpi_video_methods[] = {
175 	DEVMETHOD(device_identify, acpi_video_identify),
176 	DEVMETHOD(device_probe, acpi_video_probe),
177 	DEVMETHOD(device_attach, acpi_video_attach),
178 	DEVMETHOD(device_detach, acpi_video_detach),
179 	DEVMETHOD(device_resume, acpi_video_resume),
180 	DEVMETHOD(device_shutdown, acpi_video_shutdown),
181 	{ 0, 0 }
182 };
183 
184 static driver_t acpi_video_driver = {
185 	"acpi_video",
186 	acpi_video_methods,
187 	sizeof(struct acpi_video_softc),
188 };
189 
190 static devclass_t acpi_video_devclass;
191 
192 DRIVER_MODULE(acpi_video, vgapci, acpi_video_driver, acpi_video_devclass,
193 	      acpi_video_modevent, NULL);
194 MODULE_DEPEND(acpi_video, acpi, 1, 1, 1);
195 #ifdef EVDEV_SUPPORT
196 MODULE_DEPEND(acpi_video, evdev, 1, 1, 1);
197 #endif
198 
199 static struct sysctl_ctx_list	acpi_video_sysctl_ctx;
200 static struct sysctl_oid	*acpi_video_sysctl_tree;
201 static struct acpi_video_output_queue crt_units, tv_units,
202     ext_units, lcd_units, other_units;
203 
204 /*
205  * The 'video' lock protects the hierarchy of video output devices
206  * (the video "bus").  The 'video_output' lock protects per-output
207  * data is equivalent to a softc lock for each video output.
208  */
209 ACPI_SERIAL_DECL(video, "ACPI video");
210 ACPI_SERIAL_DECL(video_output, "ACPI video output");
211 static MALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension");
212 
213 #ifdef EVDEV_SUPPORT
214 static const struct {
215 	UINT32		notify;
216 	uint16_t	key;
217 } acpi_video_evdev_map[] = {
218 	{ VID_NOTIFY_SWITCHED,	KEY_SWITCHVIDEOMODE },
219 	{ VID_NOTIFY_REPROBE,	KEY_SWITCHVIDEOMODE },
220 	{ VID_NOTIFY_CYCLE_OUT,	KEY_SWITCHVIDEOMODE },
221 	{ VID_NOTIFY_NEXT_OUT,	KEY_VIDEO_NEXT },
222 	{ VID_NOTIFY_PREV_OUT,	KEY_VIDEO_PREV },
223 	{ VID_NOTIFY_CYCLE_BRN,	KEY_BRIGHTNESS_CYCLE },
224 	{ VID_NOTIFY_INC_BRN,	KEY_BRIGHTNESSUP },
225 	{ VID_NOTIFY_DEC_BRN,	KEY_BRIGHTNESSDOWN },
226 	{ VID_NOTIFY_ZERO_BRN,	KEY_BRIGHTNESS_ZERO },
227 	{ VID_NOTIFY_DISP_OFF,	KEY_DISPLAY_OFF },
228 };
229 
230 static void
231 acpi_video_push_evdev_event(struct evdev_dev *evdev, UINT32 notify)
232 {
233 	int i;
234 	uint16_t key;
235 
236 	/* Do not allow to execute 2 instances this routine concurrently */
237 	ACPI_SERIAL_ASSERT(video_output);
238 
239 	for (i = 0; i < nitems(acpi_video_evdev_map); i++) {
240 		if (acpi_video_evdev_map[i].notify == notify) {
241 			key = acpi_video_evdev_map[i].key;
242 			evdev_push_key(evdev, key, 1);
243 			evdev_sync(evdev);
244 			evdev_push_key(evdev, key, 0);
245 			evdev_sync(evdev);
246 			break;
247 		}
248 	}
249 }
250 #endif
251 
252 static int
253 acpi_video_modevent(struct module *mod __unused, int evt, void *cookie __unused)
254 {
255 	int err;
256 
257 	err = 0;
258 	switch (evt) {
259 	case MOD_LOAD:
260 		sysctl_ctx_init(&acpi_video_sysctl_ctx);
261 		STAILQ_INIT(&crt_units);
262 		STAILQ_INIT(&tv_units);
263 		STAILQ_INIT(&ext_units);
264 		STAILQ_INIT(&lcd_units);
265 		STAILQ_INIT(&other_units);
266 		break;
267 	case MOD_UNLOAD:
268 		sysctl_ctx_free(&acpi_video_sysctl_ctx);
269 		acpi_video_sysctl_tree = NULL;
270 		break;
271 	default:
272 		err = EINVAL;
273 	}
274 
275 	return (err);
276 }
277 
278 static void
279 acpi_video_identify(driver_t *driver, device_t parent)
280 {
281 
282 	if (device_find_child(parent, "acpi_video", -1) == NULL)
283 		device_add_child(parent, "acpi_video", -1);
284 }
285 
286 static int
287 acpi_video_probe(device_t dev)
288 {
289 	ACPI_HANDLE devh, h;
290 	ACPI_OBJECT_TYPE t_dos;
291 
292 	devh = acpi_get_handle(dev);
293 	if (acpi_disabled("video") ||
294 	    ACPI_FAILURE(AcpiGetHandle(devh, "_DOD", &h)) ||
295 	    ACPI_FAILURE(AcpiGetHandle(devh, "_DOS", &h)) ||
296 	    ACPI_FAILURE(AcpiGetType(h, &t_dos)) ||
297 	    t_dos != ACPI_TYPE_METHOD)
298 		return (ENXIO);
299 
300 	device_set_desc(dev, "ACPI video extension");
301 	return (0);
302 }
303 
304 static int
305 acpi_video_attach(device_t dev)
306 {
307 	struct acpi_softc *acpi_sc;
308 	struct acpi_video_softc *sc;
309 #ifdef EVDEV_SUPPORT
310 	int i;
311 #endif
312 
313 	sc = device_get_softc(dev);
314 
315 	acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
316 	if (acpi_sc == NULL)
317 		return (ENXIO);
318 
319 #ifdef EVDEV_SUPPORT
320 	sc->evdev = evdev_alloc();
321 	evdev_set_name(sc->evdev, device_get_desc(dev));
322 	evdev_set_phys(sc->evdev, device_get_nameunit(dev));
323 	evdev_set_id(sc->evdev, BUS_HOST, 0, 0, 1);
324 	evdev_support_event(sc->evdev, EV_SYN);
325 	evdev_support_event(sc->evdev, EV_KEY);
326 	for (i = 0; i < nitems(acpi_video_evdev_map); i++)
327 		evdev_support_key(sc->evdev, acpi_video_evdev_map[i].key);
328 
329 	if (evdev_register(sc->evdev) != 0)
330 		return (ENXIO);
331 #endif
332 
333 	ACPI_SERIAL_BEGIN(video);
334 	if (acpi_video_sysctl_tree == NULL) {
335 		acpi_video_sysctl_tree = SYSCTL_ADD_NODE(&acpi_video_sysctl_ctx,
336 		    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
337 		    "video", CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
338 		    "video extension control");
339 	}
340 	ACPI_SERIAL_END(video);
341 
342 	sc->device = dev;
343 	sc->handle = acpi_get_handle(dev);
344 	STAILQ_INIT(&sc->vid_outputs);
345 
346 	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
347 				 acpi_video_notify_handler, sc);
348 	sc->vid_pwr_evh = EVENTHANDLER_REGISTER(power_profile_change,
349 				 acpi_video_power_profile, sc, 0);
350 
351 	ACPI_SERIAL_BEGIN(video);
352 	acpi_video_bind_outputs(sc);
353 	ACPI_SERIAL_END(video);
354 
355 	/*
356 	 * Notify the BIOS that we want to switch both active outputs and
357 	 * brightness levels.
358 	 */
359 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_OSPM |
360 	    DOS_BRIGHTNESS_BY_OSPM);
361 
362 	acpi_video_power_profile(sc);
363 
364 	return (0);
365 }
366 
367 static int
368 acpi_video_detach(device_t dev)
369 {
370 	struct acpi_video_softc *sc;
371 	struct acpi_video_output *vo, *vn;
372 
373 	sc = device_get_softc(dev);
374 
375 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
376 	EVENTHANDLER_DEREGISTER(power_profile_change, sc->vid_pwr_evh);
377 	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
378 				acpi_video_notify_handler);
379 
380 	ACPI_SERIAL_BEGIN(video);
381 	STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vn) {
382 		acpi_video_vo_destroy(vo);
383 	}
384 	ACPI_SERIAL_END(video);
385 
386 #ifdef EVDEV_SUPPORT
387 	evdev_free(sc->evdev);
388 #endif
389 
390 	return (0);
391 }
392 
393 static int
394 acpi_video_resume(device_t dev)
395 {
396 	struct acpi_video_softc *sc;
397 	struct acpi_video_output *vo, *vn;
398 	int level;
399 
400 	sc = device_get_softc(dev);
401 
402 	/* Restore brightness level */
403 	ACPI_SERIAL_BEGIN(video);
404 	ACPI_SERIAL_BEGIN(video_output);
405 	STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vn) {
406 		if ((vo->adr & DOD_DEVID_MASK_FULL) != DOD_DEVID_LCD &&
407 		    (vo->adr & DOD_DEVID_MASK) != DOD_DEVID_INTDFP)
408 			continue;
409 
410 		if ((vo_get_device_status(vo->handle) & DCS_ACTIVE) == 0)
411 			continue;
412 
413 		level = vo_get_brightness(vo);
414 		if (level != -1)
415 			vo_set_brightness(vo, level);
416 	}
417 	ACPI_SERIAL_END(video_output);
418 	ACPI_SERIAL_END(video);
419 
420 	return (0);
421 }
422 
423 static int
424 acpi_video_shutdown(device_t dev)
425 {
426 	struct acpi_video_softc *sc;
427 
428 	sc = device_get_softc(dev);
429 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
430 
431 	return (0);
432 }
433 
434 static void
435 acpi_video_invoke_event_handler(void *context)
436 {
437 	EVENTHANDLER_INVOKE(acpi_video_event, (int)(intptr_t)context);
438 }
439 
440 static void
441 acpi_video_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context)
442 {
443 	struct acpi_video_softc *sc;
444 	struct acpi_video_output *vo, *vo_tmp;
445 	ACPI_HANDLE lasthand;
446 	UINT32 dcs, dss, dss_p;
447 
448 	sc = (struct acpi_video_softc *)context;
449 
450 	switch (notify) {
451 	case VID_NOTIFY_SWITCHED:
452 		dss_p = 0;
453 		lasthand = NULL;
454 		ACPI_SERIAL_BEGIN(video);
455 		ACPI_SERIAL_BEGIN(video_output);
456 		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
457 			dss = vo_get_graphics_state(vo->handle);
458 			dcs = vo_get_device_status(vo->handle);
459 			if (!(dcs & DCS_READY))
460 				dss = DSS_INACTIVE;
461 			if (((dcs & DCS_ACTIVE) && dss == DSS_INACTIVE) ||
462 			    (!(dcs & DCS_ACTIVE) && dss == DSS_ACTIVE)) {
463 				if (lasthand != NULL)
464 					vo_set_device_state(lasthand, dss_p);
465 				dss_p = dss;
466 				lasthand = vo->handle;
467 			}
468 		}
469 		if (lasthand != NULL)
470 			vo_set_device_state(lasthand, dss_p|DSS_COMMIT);
471 		ACPI_SERIAL_END(video_output);
472 		ACPI_SERIAL_END(video);
473 		break;
474 	case VID_NOTIFY_REPROBE:
475 		ACPI_SERIAL_BEGIN(video);
476 		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next)
477 			vo->handle = NULL;
478 		acpi_video_bind_outputs(sc);
479 		STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vo_tmp) {
480 			if (vo->handle == NULL) {
481 				STAILQ_REMOVE(&sc->vid_outputs, vo,
482 				    acpi_video_output, vo_next);
483 				acpi_video_vo_destroy(vo);
484 			}
485 		}
486 		ACPI_SERIAL_END(video);
487 		break;
488 	/* Next events should not appear if DOS_SWITCH_BY_OSPM policy is set */
489 	case VID_NOTIFY_CYCLE_OUT:
490 	case VID_NOTIFY_NEXT_OUT:
491 	case VID_NOTIFY_PREV_OUT:
492 	default:
493 		device_printf(sc->device, "unknown notify event 0x%x\n",
494 		    notify);
495 	}
496 	AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_video_invoke_event_handler,
497 	    (void *)(uintptr_t)notify);
498 #ifdef EVDEV_SUPPORT
499 	ACPI_SERIAL_BEGIN(video_output);
500 	acpi_video_push_evdev_event(sc->evdev, notify);
501 	ACPI_SERIAL_END(video_output);
502 #endif
503 }
504 
505 static void
506 acpi_video_power_profile(void *context)
507 {
508 	int state;
509 	struct acpi_video_softc *sc;
510 	struct acpi_video_output *vo;
511 
512 	sc = context;
513 	state = power_profile_get_state();
514 	if (state != POWER_PROFILE_PERFORMANCE &&
515 	    state != POWER_PROFILE_ECONOMY)
516 		return;
517 
518 	ACPI_SERIAL_BEGIN(video);
519 	ACPI_SERIAL_BEGIN(video_output);
520 	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
521 		if (vo->vo_levels != NULL && vo->vo_brightness == -1)
522 			vo_set_brightness(vo,
523 			    state == POWER_PROFILE_ECONOMY ?
524 			    vo->vo_economy : vo->vo_fullpower);
525 	}
526 	ACPI_SERIAL_END(video_output);
527 	ACPI_SERIAL_END(video);
528 }
529 
530 static void
531 acpi_video_bind_outputs_subr(ACPI_HANDLE handle, UINT32 adr, void *context)
532 {
533 	struct acpi_video_softc *sc;
534 	struct acpi_video_output *vo;
535 
536 	ACPI_SERIAL_ASSERT(video);
537 	sc = context;
538 
539 	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
540 		if (vo->adr == adr) {
541 			acpi_video_vo_bind(vo, handle);
542 			return;
543 		}
544 	}
545 	vo = acpi_video_vo_init(adr);
546 	if (vo != NULL) {
547 #ifdef EVDEV_SUPPORT
548 		vo->evdev = sc->evdev;
549 #endif
550 		acpi_video_vo_bind(vo, handle);
551 		STAILQ_INSERT_TAIL(&sc->vid_outputs, vo, vo_next);
552 	}
553 }
554 
555 static void
556 acpi_video_bind_outputs(struct acpi_video_softc *sc)
557 {
558 
559 	ACPI_SERIAL_ASSERT(video);
560 	vid_enum_outputs(sc->handle, acpi_video_bind_outputs_subr, sc);
561 }
562 
563 static struct acpi_video_output *
564 acpi_video_vo_init(UINT32 adr)
565 {
566 	struct acpi_video_output *vn, *vo, *vp;
567 	int n, x;
568 	char name[8], env[32];
569 	const char *type, *desc;
570 	struct acpi_video_output_queue *voqh;
571 
572 	ACPI_SERIAL_ASSERT(video);
573 
574 	switch (adr & DOD_DEVID_MASK) {
575 	case DOD_DEVID_MONITOR:
576 		if ((adr & DOD_DEVID_MASK_FULL) == DOD_DEVID_LCD) {
577 			/* DOD_DEVID_LCD is a common, backward compatible ID */
578 			desc = "Internal/Integrated Digital Flat Panel";
579 			type = "lcd";
580 			voqh = &lcd_units;
581 		} else {
582 			desc = "VGA CRT or VESA Compatible Analog Monitor";
583 			type = "crt";
584 			voqh = &crt_units;
585 		}
586 		break;
587 	case DOD_DEVID_TV:
588 		desc = "TV/HDTV or Analog-Video Monitor";
589 		type = "tv";
590 		voqh = &tv_units;
591 		break;
592 	case DOD_DEVID_EXT:
593 		desc = "External Digital Monitor";
594 		type = "ext";
595 		voqh = &ext_units;
596 		break;
597 	case DOD_DEVID_INTDFP:
598 		desc = "Internal/Integrated Digital Flat Panel";
599 		type = "lcd";
600 		voqh = &lcd_units;
601 		break;
602 	default:
603 		desc = "unknown output";
604 		type = "out";
605 		voqh = &other_units;
606 	}
607 
608 	n = 0;
609 	vp = NULL;
610 	STAILQ_FOREACH(vn, voqh, vo_unit.next) {
611 		if (vn->vo_unit.num != n)
612 			break;
613 		vp = vn;
614 		n++;
615 	}
616 
617 	snprintf(name, sizeof(name), "%s%d", type, n);
618 
619 	vo = malloc(sizeof(*vo), M_ACPIVIDEO, M_NOWAIT);
620 	if (vo != NULL) {
621 		vo->handle = NULL;
622 		vo->adr = adr;
623 		vo->vo_unit.num = n;
624 		vo->vo_hasbqc = -1;
625 		vo->vo_level = -1;
626 		vo->vo_brightness = -1;
627 		vo->vo_fullpower = -1;	/* TODO: override with tunables */
628 		vo->vo_economy = -1;
629 		vo->vo_numlevels = 0;
630 		vo->vo_levels = NULL;
631 		snprintf(env, sizeof(env), "hw.acpi.video.%s.fullpower", name);
632 		if (getenv_int(env, &x))
633 			vo->vo_fullpower = x;
634 		snprintf(env, sizeof(env), "hw.acpi.video.%s.economy", name);
635 		if (getenv_int(env, &x))
636 			vo->vo_economy = x;
637 
638 		sysctl_ctx_init(&vo->vo_sysctl_ctx);
639 		if (vp != NULL)
640 			STAILQ_INSERT_AFTER(voqh, vp, vo, vo_unit.next);
641 		else
642 			STAILQ_INSERT_TAIL(voqh, vo, vo_unit.next);
643 		if (acpi_video_sysctl_tree != NULL)
644 			vo->vo_sysctl_tree =
645 			    SYSCTL_ADD_NODE(&vo->vo_sysctl_ctx,
646 				SYSCTL_CHILDREN(acpi_video_sysctl_tree),
647 				OID_AUTO, name, CTLFLAG_RD | CTLFLAG_MPSAFE,
648 				0, desc);
649 		if (vo->vo_sysctl_tree != NULL) {
650 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
651 			    SYSCTL_CHILDREN(vo->vo_sysctl_tree),
652 			    OID_AUTO, "active",
653 			    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, vo,
654 			    0, acpi_video_vo_active_sysctl, "I",
655 			    "current activity of this device");
656 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
657 			    SYSCTL_CHILDREN(vo->vo_sysctl_tree),
658 			    OID_AUTO, "brightness",
659 			    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, vo,
660 			    0, acpi_video_vo_bright_sysctl, "I",
661 			    "current brightness level");
662 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
663 			    SYSCTL_CHILDREN(vo->vo_sysctl_tree),
664 			    OID_AUTO, "fullpower",
665 			    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, vo,
666 			    POWER_PROFILE_PERFORMANCE,
667 			    acpi_video_vo_presets_sysctl, "I",
668 			    "preset level for full power mode");
669 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
670 			    SYSCTL_CHILDREN(vo->vo_sysctl_tree),
671 			    OID_AUTO, "economy",
672 			    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, vo,
673 			    POWER_PROFILE_ECONOMY,
674 			    acpi_video_vo_presets_sysctl, "I",
675 			    "preset level for economy mode");
676 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
677 			    SYSCTL_CHILDREN(vo->vo_sysctl_tree),
678 			    OID_AUTO, "levels",
679 			    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, vo,
680 			    0, acpi_video_vo_levels_sysctl, "I",
681 			    "supported brightness levels");
682 		} else
683 			printf("%s: sysctl node creation failed\n", type);
684 	} else
685 		printf("%s: softc allocation failed\n", type);
686 
687 	if (bootverbose) {
688 		printf("found %s(%x)", desc, adr & DOD_DEVID_MASK_FULL);
689 		printf(", idx#%x", adr & DOD_DEVID_MASK_DISPIDX);
690 		printf(", port#%x", (adr & DOD_DEVID_MASK_DISPPORT) >> 4);
691 		if (adr & DOD_BIOS)
692 			printf(", detectable by BIOS");
693 		if (adr & DOD_NONVGA)
694 			printf(" (Non-VGA output device whose power "
695 			    "is related to the VGA device)");
696 		printf(", head #%d\n",
697 			(adr & DOD_HEAD_ID_MASK) >> DOD_HEAD_ID_SHIFT);
698 	}
699 	return (vo);
700 }
701 
702 static void
703 acpi_video_vo_bind(struct acpi_video_output *vo, ACPI_HANDLE handle)
704 {
705 
706 	ACPI_SERIAL_BEGIN(video_output);
707 	if (vo->vo_levels != NULL) {
708 		AcpiRemoveNotifyHandler(vo->handle, ACPI_DEVICE_NOTIFY,
709 		    acpi_video_vo_notify_handler);
710 		AcpiOsFree(vo->vo_levels);
711 		vo->vo_levels = NULL;
712 	}
713 	vo->handle = handle;
714 	vo->vo_numlevels = vo_get_brightness_levels(handle, &vo->vo_levels);
715 	if (vo->vo_numlevels >= 2) {
716 		if (vo->vo_fullpower == -1 ||
717 		    acpi_video_vo_check_level(vo, vo->vo_fullpower) != 0) {
718 			/* XXX - can't deal with rebinding... */
719 			vo->vo_fullpower = vo->vo_levels[BCL_FULLPOWER];
720 		}
721 		if (vo->vo_economy == -1 ||
722 		    acpi_video_vo_check_level(vo, vo->vo_economy) != 0) {
723 			/* XXX - see above. */
724 			vo->vo_economy = vo->vo_levels[BCL_ECONOMY];
725 		}
726 		AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY,
727 		    acpi_video_vo_notify_handler, vo);
728 	}
729 	ACPI_SERIAL_END(video_output);
730 }
731 
732 static void
733 acpi_video_vo_destroy(struct acpi_video_output *vo)
734 {
735 	struct acpi_video_output_queue *voqh;
736 
737 	ACPI_SERIAL_ASSERT(video);
738 	if (vo->vo_sysctl_tree != NULL) {
739 		vo->vo_sysctl_tree = NULL;
740 		sysctl_ctx_free(&vo->vo_sysctl_ctx);
741 	}
742 	if (vo->vo_levels != NULL) {
743 		AcpiRemoveNotifyHandler(vo->handle, ACPI_DEVICE_NOTIFY,
744 		    acpi_video_vo_notify_handler);
745 		AcpiOsFree(vo->vo_levels);
746 	}
747 
748 	switch (vo->adr & DOD_DEVID_MASK) {
749 	case DOD_DEVID_MONITOR:
750 		if ((vo->adr & DOD_DEVID_MASK_FULL) == DOD_DEVID_LCD)
751 			voqh = &lcd_units;
752 		else
753 			voqh = &crt_units;
754 		break;
755 	case DOD_DEVID_TV:
756 		voqh = &tv_units;
757 		break;
758 	case DOD_DEVID_EXT:
759 		voqh = &ext_units;
760 		break;
761 	case DOD_DEVID_INTDFP:
762 		voqh = &lcd_units;
763 		break;
764 	default:
765 		voqh = &other_units;
766 	}
767 	STAILQ_REMOVE(voqh, vo, acpi_video_output, vo_unit.next);
768 	free(vo, M_ACPIVIDEO);
769 }
770 
771 static int
772 acpi_video_vo_check_level(struct acpi_video_output *vo, int level)
773 {
774 	int i;
775 
776 	ACPI_SERIAL_ASSERT(video_output);
777 	if (vo->vo_levels == NULL)
778 		return (ENODEV);
779 	for (i = 0; i < vo->vo_numlevels; i++)
780 		if (vo->vo_levels[i] == level)
781 			return (0);
782 	return (EINVAL);
783 }
784 
785 static void
786 acpi_video_vo_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context)
787 {
788 	struct acpi_video_output *vo;
789 	int i, j, level, new_level;
790 
791 	vo = context;
792 	ACPI_SERIAL_BEGIN(video_output);
793 	if (vo->handle != handle)
794 		goto out;
795 
796 	switch (notify) {
797 	case VID_NOTIFY_CYCLE_BRN:
798 		if (vo->vo_numlevels <= 3)
799 			goto out;
800 		/* FALLTHROUGH */
801 	case VID_NOTIFY_INC_BRN:
802 	case VID_NOTIFY_DEC_BRN:
803 	case VID_NOTIFY_ZERO_BRN:
804 	case VID_NOTIFY_DISP_OFF:
805 		if (vo->vo_levels == NULL)
806 			goto out;
807 		level = vo_get_brightness(vo);
808 		if (level < 0)
809 			goto out;
810 		break;
811 	default:
812 		printf("unknown notify event 0x%x from %s\n",
813 		    notify, acpi_name(handle));
814 		goto out;
815 	}
816 
817 	new_level = level;
818 	switch (notify) {
819 	case VID_NOTIFY_CYCLE_BRN:
820 		for (i = 2; i < vo->vo_numlevels; i++)
821 			if (vo->vo_levels[i] == level) {
822 				new_level = vo->vo_numlevels > i + 1 ?
823 				     vo->vo_levels[i + 1] : vo->vo_levels[2];
824 				break;
825 			}
826 		break;
827 	case VID_NOTIFY_INC_BRN:
828 	case VID_NOTIFY_DEC_BRN:
829 		for (i = 0; i < vo->vo_numlevels; i++) {
830 			j = vo->vo_levels[i];
831 			if (notify == VID_NOTIFY_INC_BRN) {
832 				if (j > level &&
833 				    (j < new_level || level == new_level))
834 					new_level = j;
835 			} else {
836 				if (j < level &&
837 				    (j > new_level || level == new_level))
838 					new_level = j;
839 			}
840 		}
841 		break;
842 	case VID_NOTIFY_ZERO_BRN:
843 		for (i = 0; i < vo->vo_numlevels; i++)
844 			if (vo->vo_levels[i] == 0) {
845 				new_level = 0;
846 				break;
847 			}
848 		break;
849 	case VID_NOTIFY_DISP_OFF:
850 		acpi_pwr_switch_consumer(handle, ACPI_STATE_D3);
851 		break;
852 	}
853 	if (new_level != level) {
854 		vo_set_brightness(vo, new_level);
855 		vo->vo_brightness = new_level;
856 	}
857 #ifdef EVDEV_SUPPORT
858 	acpi_video_push_evdev_event(vo->evdev, notify);
859 #endif
860 
861 out:
862 	ACPI_SERIAL_END(video_output);
863 
864 	AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_video_invoke_event_handler,
865 	    (void *)(uintptr_t)notify);
866 }
867 
868 /* ARGSUSED */
869 static int
870 acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS)
871 {
872 	struct acpi_video_output *vo;
873 	int state, err;
874 
875 	vo = (struct acpi_video_output *)arg1;
876 	if (vo->handle == NULL)
877 		return (ENXIO);
878 	ACPI_SERIAL_BEGIN(video_output);
879 	state = (vo_get_device_status(vo->handle) & DCS_ACTIVE) ? 1 : 0;
880 	err = sysctl_handle_int(oidp, &state, 0, req);
881 	if (err != 0 || req->newptr == NULL)
882 		goto out;
883 	vo_set_device_state(vo->handle,
884 	    DSS_COMMIT | (state ? DSS_ACTIVE : DSS_INACTIVE));
885 out:
886 	ACPI_SERIAL_END(video_output);
887 	return (err);
888 }
889 
890 /* ARGSUSED */
891 static int
892 acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS)
893 {
894 	struct acpi_video_output *vo;
895 	int level, preset, err;
896 
897 	vo = (struct acpi_video_output *)arg1;
898 	ACPI_SERIAL_BEGIN(video_output);
899 	if (vo->handle == NULL) {
900 		err = ENXIO;
901 		goto out;
902 	}
903 	if (vo->vo_levels == NULL) {
904 		err = ENODEV;
905 		goto out;
906 	}
907 
908 	preset = (power_profile_get_state() == POWER_PROFILE_ECONOMY) ?
909 		  vo->vo_economy : vo->vo_fullpower;
910 	level = vo->vo_brightness;
911 	if (level == -1)
912 		level = preset;
913 
914 	err = sysctl_handle_int(oidp, &level, 0, req);
915 	if (err != 0 || req->newptr == NULL)
916 		goto out;
917 	if (level < -1 || level > 100) {
918 		err = EINVAL;
919 		goto out;
920 	}
921 
922 	if (level != -1 && (err = acpi_video_vo_check_level(vo, level)))
923 		goto out;
924 	vo->vo_brightness = level;
925 	vo_set_brightness(vo, (level == -1) ? preset : level);
926 
927 out:
928 	ACPI_SERIAL_END(video_output);
929 	return (err);
930 }
931 
932 static int
933 acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS)
934 {
935 	struct acpi_video_output *vo;
936 	int i, level, *preset, err;
937 
938 	vo = (struct acpi_video_output *)arg1;
939 	ACPI_SERIAL_BEGIN(video_output);
940 	if (vo->handle == NULL) {
941 		err = ENXIO;
942 		goto out;
943 	}
944 	if (vo->vo_levels == NULL) {
945 		err = ENODEV;
946 		goto out;
947 	}
948 	preset = (arg2 == POWER_PROFILE_ECONOMY) ?
949 		  &vo->vo_economy : &vo->vo_fullpower;
950 	level = *preset;
951 	err = sysctl_handle_int(oidp, &level, 0, req);
952 	if (err != 0 || req->newptr == NULL)
953 		goto out;
954 	if (level < -1 || level > 100) {
955 		err = EINVAL;
956 		goto out;
957 	}
958 	if (level == -1) {
959 		i = (arg2 == POWER_PROFILE_ECONOMY) ?
960 		    BCL_ECONOMY : BCL_FULLPOWER;
961 		level = vo->vo_levels[i];
962 	} else if ((err = acpi_video_vo_check_level(vo, level)) != 0)
963 		goto out;
964 
965 	if (vo->vo_brightness == -1 && (power_profile_get_state() == arg2))
966 		vo_set_brightness(vo, level);
967 	*preset = level;
968 
969 out:
970 	ACPI_SERIAL_END(video_output);
971 	return (err);
972 }
973 
974 /* ARGSUSED */
975 static int
976 acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS)
977 {
978 	struct acpi_video_output *vo;
979 	int err;
980 
981 	vo = (struct acpi_video_output *)arg1;
982 	ACPI_SERIAL_BEGIN(video_output);
983 	if (vo->vo_levels == NULL) {
984 		err = ENODEV;
985 		goto out;
986 	}
987 	if (req->newptr != NULL) {
988 		err = EPERM;
989 		goto out;
990 	}
991 	err = sysctl_handle_opaque(oidp, vo->vo_levels,
992 	    vo->vo_numlevels * sizeof(*vo->vo_levels), req);
993 
994 out:
995 	ACPI_SERIAL_END(video_output);
996 	return (err);
997 }
998 
999 static void
1000 vid_set_switch_policy(ACPI_HANDLE handle, UINT32 policy)
1001 {
1002 	ACPI_STATUS status;
1003 
1004 	status = acpi_SetInteger(handle, "_DOS", policy);
1005 	if (ACPI_FAILURE(status))
1006 		printf("can't evaluate %s._DOS - %s\n",
1007 		       acpi_name(handle), AcpiFormatException(status));
1008 }
1009 
1010 struct enum_callback_arg {
1011 	void (*callback)(ACPI_HANDLE, UINT32, void *);
1012 	void *context;
1013 	ACPI_OBJECT *dod_pkg;
1014 	int count;
1015 };
1016 
1017 static ACPI_STATUS
1018 vid_enum_outputs_subr(ACPI_HANDLE handle, UINT32 level __unused,
1019 		      void *context, void **retp __unused)
1020 {
1021 	ACPI_STATUS status;
1022 	UINT32 adr, val;
1023 	struct enum_callback_arg *argset;
1024 	size_t i;
1025 
1026 	ACPI_SERIAL_ASSERT(video);
1027 	argset = context;
1028 	status = acpi_GetInteger(handle, "_ADR", &adr);
1029 	if (ACPI_FAILURE(status))
1030 		return (AE_OK);
1031 
1032 	for (i = 0; i < argset->dod_pkg->Package.Count; i++) {
1033 		if (acpi_PkgInt32(argset->dod_pkg, i, &val) == 0 &&
1034 		    (val & DOD_DEVID_MASK_FULL) ==
1035 		    (adr & DOD_DEVID_MASK_FULL)) {
1036 			argset->callback(handle, val, argset->context);
1037 			argset->count++;
1038 		}
1039 	}
1040 
1041 	return (AE_OK);
1042 }
1043 
1044 static int
1045 vid_enum_outputs(ACPI_HANDLE handle,
1046 		 void (*callback)(ACPI_HANDLE, UINT32, void *), void *context)
1047 {
1048 	ACPI_STATUS status;
1049 	ACPI_BUFFER dod_buf;
1050 	ACPI_OBJECT *res;
1051 	struct enum_callback_arg argset;
1052 
1053 	ACPI_SERIAL_ASSERT(video);
1054 	dod_buf.Length = ACPI_ALLOCATE_BUFFER;
1055 	dod_buf.Pointer = NULL;
1056 	status = AcpiEvaluateObject(handle, "_DOD", NULL, &dod_buf);
1057 	if (ACPI_FAILURE(status)) {
1058 		if (status != AE_NOT_FOUND)
1059 			printf("can't evaluate %s._DOD - %s\n",
1060 			       acpi_name(handle), AcpiFormatException(status));
1061 		argset.count = -1;
1062 		goto out;
1063 	}
1064 	res = (ACPI_OBJECT *)dod_buf.Pointer;
1065 	if (!ACPI_PKG_VALID(res, 1)) {
1066 		printf("evaluation of %s._DOD makes no sense\n",
1067 		       acpi_name(handle));
1068 		argset.count = -1;
1069 		goto out;
1070 	}
1071 	if (callback == NULL) {
1072 		argset.count = res->Package.Count;
1073 		goto out;
1074 	}
1075 	argset.callback = callback;
1076 	argset.context  = context;
1077 	argset.dod_pkg  = res;
1078 	argset.count    = 0;
1079 	status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
1080 	    vid_enum_outputs_subr, NULL, &argset, NULL);
1081 	if (ACPI_FAILURE(status))
1082 		printf("failed walking down %s - %s\n",
1083 		       acpi_name(handle), AcpiFormatException(status));
1084 out:
1085 	if (dod_buf.Pointer != NULL)
1086 		AcpiOsFree(dod_buf.Pointer);
1087 	return (argset.count);
1088 }
1089 
1090 static int
1091 vo_get_brightness_levels(ACPI_HANDLE handle, int **levelp)
1092 {
1093 	ACPI_STATUS status;
1094 	ACPI_BUFFER bcl_buf;
1095 	ACPI_OBJECT *res;
1096 	int num, i, n, *levels;
1097 
1098 	bcl_buf.Length = ACPI_ALLOCATE_BUFFER;
1099 	bcl_buf.Pointer = NULL;
1100 	status = AcpiEvaluateObject(handle, "_BCL", NULL, &bcl_buf);
1101 	if (ACPI_FAILURE(status)) {
1102 		if (status != AE_NOT_FOUND)
1103 			printf("can't evaluate %s._BCL - %s\n",
1104 			       acpi_name(handle), AcpiFormatException(status));
1105 		goto out;
1106 	}
1107 	res = (ACPI_OBJECT *)bcl_buf.Pointer;
1108 	if (!ACPI_PKG_VALID(res, 2)) {
1109 		printf("evaluation of %s._BCL makes no sense\n",
1110 		       acpi_name(handle));
1111 		goto out;
1112 	}
1113 	num = res->Package.Count;
1114 	if (num < 2 || levelp == NULL)
1115 		goto out;
1116 	levels = AcpiOsAllocate(num * sizeof(*levels));
1117 	if (levels == NULL)
1118 		goto out;
1119 	for (i = 0, n = 0; i < num; i++)
1120 		if (acpi_PkgInt32(res, i, &levels[n]) == 0)
1121 			n++;
1122 	if (n < 2) {
1123 		AcpiOsFree(levels);
1124 		goto out;
1125 	}
1126 	*levelp = levels;
1127 	return (n);
1128 
1129 out:
1130 	if (bcl_buf.Pointer != NULL)
1131 		AcpiOsFree(bcl_buf.Pointer);
1132 	return (0);
1133 }
1134 
1135 static int
1136 vo_get_bqc(struct acpi_video_output *vo, UINT32 *level)
1137 {
1138 	ACPI_STATUS status;
1139 
1140 	switch (vo->vo_hasbqc) {
1141 	case 1:
1142 	case -1:
1143 		status = acpi_GetInteger(vo->handle, "_BQC", level);
1144 		if (vo->vo_hasbqc == 1)
1145 			break;
1146 		vo->vo_hasbqc = status != AE_NOT_FOUND;
1147 		if (vo->vo_hasbqc == 1)
1148 			break;
1149 		/* FALLTHROUGH */
1150 	default:
1151 		KASSERT(vo->vo_hasbqc == 0,
1152 		    ("bad vo_hasbqc state %d", vo->vo_hasbqc));
1153 		*level = vo->vo_level;
1154 		status = AE_OK;
1155 	}
1156 	return (status);
1157 }
1158 
1159 static int
1160 vo_get_brightness(struct acpi_video_output *vo)
1161 {
1162 	UINT32 level;
1163 	ACPI_STATUS status;
1164 
1165 	ACPI_SERIAL_ASSERT(video_output);
1166 	status = vo_get_bqc(vo, &level);
1167 	if (ACPI_FAILURE(status)) {
1168 		printf("can't evaluate %s._BQC - %s\n", acpi_name(vo->handle),
1169 		    AcpiFormatException(status));
1170 		return (-1);
1171 	}
1172 	if (level > 100)
1173 		return (-1);
1174 
1175 	return (level);
1176 }
1177 
1178 static void
1179 vo_set_brightness(struct acpi_video_output *vo, int level)
1180 {
1181 	char notify_buf[16];
1182 	ACPI_STATUS status;
1183 
1184 	ACPI_SERIAL_ASSERT(video_output);
1185 	status = acpi_SetInteger(vo->handle, "_BCM", level);
1186 	if (ACPI_FAILURE(status)) {
1187 		printf("can't evaluate %s._BCM - %s\n",
1188 		    acpi_name(vo->handle), AcpiFormatException(status));
1189 	} else {
1190 		vo->vo_level = level;
1191 	}
1192 	snprintf(notify_buf, sizeof(notify_buf), "notify=%d", level);
1193 	devctl_notify("ACPI", "Video", "brightness", notify_buf);
1194 }
1195 
1196 static UINT32
1197 vo_get_device_status(ACPI_HANDLE handle)
1198 {
1199 	UINT32 dcs;
1200 	ACPI_STATUS status;
1201 
1202 	ACPI_SERIAL_ASSERT(video_output);
1203 	dcs = 0;
1204 	status = acpi_GetInteger(handle, "_DCS", &dcs);
1205 	if (ACPI_FAILURE(status)) {
1206 		/*
1207 		 * If the method is missing, assume that the device is always
1208 		 * operational.
1209 		 */
1210 		if (status != AE_NOT_FOUND) {
1211 			printf("can't evaluate %s._DCS - %s\n",
1212 			    acpi_name(handle), AcpiFormatException(status));
1213 		} else {
1214 			dcs = 0xff;
1215 		}
1216 	}
1217 
1218 	return (dcs);
1219 }
1220 
1221 static UINT32
1222 vo_get_graphics_state(ACPI_HANDLE handle)
1223 {
1224 	UINT32 dgs;
1225 	ACPI_STATUS status;
1226 
1227 	dgs = 0;
1228 	status = acpi_GetInteger(handle, "_DGS", &dgs);
1229 	if (ACPI_FAILURE(status)) {
1230 		/*
1231 		 * If the method is missing, assume that the device is always
1232 		 * operational.
1233 		 */
1234 		if (status != AE_NOT_FOUND) {
1235 			printf("can't evaluate %s._DGS - %s\n",
1236 			    acpi_name(handle), AcpiFormatException(status));
1237 		} else {
1238 			dgs = 0xff;
1239 		}
1240 	}
1241 
1242 	return (dgs);
1243 }
1244 
1245 static void
1246 vo_set_device_state(ACPI_HANDLE handle, UINT32 state)
1247 {
1248 	ACPI_STATUS status;
1249 
1250 	ACPI_SERIAL_ASSERT(video_output);
1251 	status = acpi_SetInteger(handle, "_DSS", state);
1252 	if (ACPI_FAILURE(status) && status != AE_NOT_FOUND)
1253 		printf("can't evaluate %s._DSS - %s\n",
1254 		    acpi_name(handle), AcpiFormatException(status));
1255 }
1256