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