xref: /freebsd/sys/dev/acpica/acpi_video.c (revision 2357939bc239bd5334a169b62313806178dd8f30)
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  *	$FreeBSD$
28  */
29 
30 #include <sys/param.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
33 #include <sys/bus.h>
34 #include <sys/power.h>
35 #include <sys/queue.h>
36 #include <sys/sysctl.h>
37 
38 #include "acpi.h"
39 #include <dev/acpica/acpivar.h>
40 
41 /* ACPI video extension driver. */
42 struct acpi_video_output {
43 	ACPI_HANDLE	handle;
44 	UINT32		adr;
45 	STAILQ_ENTRY(acpi_video_output) vo_next;
46 	struct {
47 		int	num;
48 		STAILQ_ENTRY(acpi_video_output) next;
49 	} vo_unit;
50 	int		vo_brightness;
51 	int		vo_fullpower;
52 	int		vo_economy;
53 	int		vo_numlevels;
54 	int		*vo_levels;
55 	struct sysctl_ctx_list vo_sysctl_ctx;
56 	struct sysctl_oid *vo_sysctl_tree;
57 };
58 
59 STAILQ_HEAD(acpi_video_output_queue, acpi_video_output);
60 
61 struct acpi_video_softc {
62 	device_t		device;
63 	ACPI_HANDLE		handle;
64 	STAILQ_HEAD(, acpi_video_output) vid_outputs;
65 	eventhandler_tag	vid_pwr_evh;
66 };
67 
68 /* interfaces */
69 static int	acpi_video_modevent(struct module*, int, void *);
70 static int	acpi_video_probe(device_t);
71 static int	acpi_video_attach(device_t);
72 static int	acpi_video_detach(device_t);
73 static int	acpi_video_shutdown(device_t);
74 static void	acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *);
75 static void	acpi_video_power_profile(void *);
76 static void	acpi_video_bind_outputs(struct acpi_video_softc *);
77 static struct acpi_video_output
78 		*acpi_video_vo_init(UINT32);
79 static void	acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE);
80 static void	acpi_video_vo_destroy(struct acpi_video_output *);
81 static int	acpi_video_vo_check_level(struct acpi_video_output *, int);
82 static int	acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS);
83 static int	acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS);
84 static int	acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS);
85 static int	acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS);
86 
87 /* operations */
88 static int	vid_check_requirements(ACPI_HANDLE);
89 static void	vid_set_switch_policy(ACPI_HANDLE, UINT32);
90 static int	vid_enum_outputs(ACPI_HANDLE,
91 				 void(*)(ACPI_HANDLE, UINT32, void *), void *);
92 static int	vo_query_brightness_levels(ACPI_HANDLE, int **);
93 static void	vo_set_brightness(ACPI_HANDLE, int);
94 static UINT32	vo_get_device_status(ACPI_HANDLE);
95 static UINT32	vo_query_graphics_state(ACPI_HANDLE);
96 static void	vo_set_device_state(ACPI_HANDLE, UINT32);
97 
98 /* events */
99 #define VID_NOTIFY_SWITCHED	0x80
100 #define VID_NOTIFY_REPROBE	0x81
101 
102 /* _DOS (Enable/Disable Output Switching) argument bits */
103 #define DOS_SWITCH_MASK		((UINT32)3)
104 #define DOS_SWITCH_BY_OSPM	((UINT32)0)
105 #define DOS_SWITCH_BY_BIOS	((UINT32)1)
106 #define DOS_SWITCH_LOCKED	((UINT32)2)
107 #define DOS_BRIGHTNESS_BY_BIOS	((UINT32)1 << 2)
108 
109 /* _DOD and subdev's _ADR */
110 #define DOD_DEVID_MASK		((UINT32)0xffff)
111 #define DOD_DEVID_MONITOR	((UINT32)0x0100)
112 #define DOD_DEVID_PANEL		((UINT32)0x0110)
113 #define DOD_DEVID_TV		((UINT32)0x0200)
114 #define DOD_BIOS		((UINT32)1 << 16)
115 #define DOD_NONVGA		((UINT32)1 << 17)
116 #define DOD_HEAD_ID_SHIFT	18
117 #define DOD_HEAD_ID_BITS	3
118 #define DOD_HEAD_ID_MASK \
119 		((((UINT32)1 << DOD_HEAD_ID_BITS) - 1) << DOD_HEAD_ID_SHIFT)
120 
121 /* _BCL related constants */
122 #define BCL_FULLPOWER		0
123 #define BCL_ECONOMY		1
124 
125 /* _DCS (Device Currrent Status) value bits and masks. */
126 #define DCS_EXISTS		((UINT32)1 << 0)
127 #define DCS_ACTIVE		((UINT32)1 << 1)
128 #define DCS_READY		((UINT32)1 << 2)
129 #define DCS_FUNCTIONAL		((UINT32)1 << 3)
130 #define DCS_ATTACHED		((UINT32)1 << 4)
131 
132 /* _DSS (Device Set Status) argument bits and masks. */
133 #define DSS_INACTIVE		((UINT32)0)
134 #define DSS_ACTIVE		((UINT32)1 << 0)
135 #define DSS_ACTIVITY		((UINT32)1 << 0)
136 #define DSS_SETNEXT		((UINT32)1 << 30)
137 #define DSS_COMMIT		((UINT32)1 << 31)
138 
139 static device_method_t acpi_video_methods[] = {
140 	DEVMETHOD(device_probe, acpi_video_probe),
141 	DEVMETHOD(device_attach, acpi_video_attach),
142 	DEVMETHOD(device_detach, acpi_video_detach),
143 	DEVMETHOD(device_shutdown, acpi_video_shutdown),
144 	{ 0, 0 }
145 };
146 
147 static driver_t acpi_video_driver = {
148 	"acpi_video",
149 	acpi_video_methods,
150 	sizeof(struct acpi_video_softc),
151 };
152 
153 static devclass_t acpi_video_devclass;
154 
155 DRIVER_MODULE(acpi_video, acpi, acpi_video_driver, acpi_video_devclass,
156 	      acpi_video_modevent, NULL);
157 MODULE_DEPEND(acpi_video, acpi, 1, 1, 1);
158 
159 struct sysctl_ctx_list	acpi_video_sysctl_ctx;
160 struct sysctl_oid	*acpi_video_sysctl_tree;
161 
162 static struct acpi_video_output_queue
163 		lcd_units, crt_units, tv_units, other_units;
164 
165 MALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension");
166 
167 static int
168 acpi_video_modevent(struct module *mod __unused, int evt, void *cookie __unused)
169 {
170 	int err = 0;
171 
172 	switch (evt) {
173 	case MOD_LOAD:
174 		acpi_video_sysctl_tree = NULL;
175 		sysctl_ctx_init(&acpi_video_sysctl_ctx);
176 		STAILQ_INIT(&lcd_units);
177 		STAILQ_INIT(&crt_units);
178 		STAILQ_INIT(&tv_units);
179 		STAILQ_INIT(&other_units);
180 		break;
181 	case MOD_UNLOAD:
182 		acpi_video_sysctl_tree = NULL;
183 		sysctl_ctx_free(&acpi_video_sysctl_ctx);
184 		break;
185 	default:
186 		err = EINVAL;
187 	}
188 
189 	return (err);
190 }
191 
192 static int
193 acpi_video_probe(device_t dev)
194 {
195 	int err = ENXIO;
196 	ACPI_HANDLE handle;
197 	ACPI_LOCK_DECL;
198 
199 	ACPI_LOCK;
200 	handle = acpi_get_handle(dev);
201 	if (acpi_get_type(dev) == ACPI_TYPE_DEVICE &&
202 	    !acpi_disabled("video") &&
203 	    vid_check_requirements(handle)) {
204 		device_set_desc(dev, "ACPI video extension");
205 		err = 0;
206 	}
207 	ACPI_UNLOCK;
208 
209 	return (err);
210 }
211 
212 static int
213 acpi_video_attach(device_t dev)
214 {
215 	struct acpi_softc *acpi_sc;
216 	struct acpi_video_softc *sc;
217 	ACPI_LOCK_DECL;
218 
219 	sc = device_get_softc(dev);
220 	ACPI_LOCK;
221 
222 	acpi_sc = acpi_device_get_parent_softc(dev);
223 	if (acpi_video_sysctl_tree == NULL && acpi_sc != NULL) {
224 		acpi_video_sysctl_tree = SYSCTL_ADD_NODE(&acpi_video_sysctl_ctx,
225 				    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
226 				    OID_AUTO, "video", CTLFLAG_RD, 0,
227 				    "video extension control");
228 	}
229 
230 	sc->device = dev;
231 	sc->handle = acpi_get_handle(dev);
232 	STAILQ_INIT(&sc->vid_outputs);
233 
234 	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
235 				 acpi_video_notify_handler, sc);
236 	sc->vid_pwr_evh = EVENTHANDLER_REGISTER(power_profile_change,
237 				 acpi_video_power_profile, sc, 0);
238 
239 	acpi_video_bind_outputs(sc);
240 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_OSPM);
241 
242 	ACPI_UNLOCK;
243 	acpi_video_power_profile(sc);
244 
245 	return (0);
246 }
247 
248 static int
249 acpi_video_detach(device_t dev)
250 {
251 	struct acpi_video_softc *sc;
252 	struct acpi_video_output *vo, *vn;
253 	ACPI_LOCK_DECL;
254 
255 	sc = device_get_softc(dev);
256 	ACPI_LOCK;
257 
258 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
259 	EVENTHANDLER_DEREGISTER(power_profile_change, sc->vid_pwr_evh);
260 	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
261 				acpi_video_notify_handler);
262 
263 	for (vo = STAILQ_FIRST(&sc->vid_outputs); vo != NULL; vo = vn) {
264 		vn = STAILQ_NEXT(vo, vo_next);
265 		acpi_video_vo_destroy(vo);
266 	}
267 
268 	ACPI_UNLOCK;
269 	return (0);
270 }
271 
272 static int
273 acpi_video_shutdown(device_t dev)
274 {
275 	struct acpi_video_softc *sc;
276 	ACPI_LOCK_DECL;
277 
278 	sc = device_get_softc(dev);
279 	ACPI_LOCK;
280 	vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
281 	ACPI_UNLOCK;
282 
283 	return (0);
284 }
285 
286 static void
287 acpi_video_notify_handler(ACPI_HANDLE handle __unused, UINT32 notify, void *context)
288 {
289 	struct acpi_video_softc *sc;
290 	struct acpi_video_output *vo;
291 	ACPI_HANDLE lasthand = NULL;
292 	UINT32 dcs, dss, dss_p = 0;
293 
294 	ACPI_ASSERTLOCK;
295 	sc = context;
296 
297 	switch (notify) {
298 	case VID_NOTIFY_SWITCHED:
299 		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
300 			dss = vo_query_graphics_state(vo->handle);
301 			dcs = vo_get_device_status(vo->handle);
302 			if (!(dcs & DCS_READY))
303 				dss = DSS_INACTIVE;
304 			if (((dcs & DCS_ACTIVE) && dss == DSS_INACTIVE) ||
305 			    (!(dcs & DCS_ACTIVE) && dss == DSS_ACTIVE)) {
306 				if (lasthand != NULL)
307 					vo_set_device_state(lasthand, dss_p);
308 				dss_p = dss;
309 				lasthand = vo->handle;
310 			}
311 		}
312 		if (lasthand != NULL)
313 			vo_set_device_state(lasthand, dss_p|DSS_COMMIT);
314 		break;
315 	case VID_NOTIFY_REPROBE:
316 		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next)
317 			vo->handle = NULL;
318 		acpi_video_bind_outputs(sc);
319 		STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
320 			if (vo->handle == NULL) {
321 				STAILQ_REMOVE(&sc->vid_outputs, vo,
322 					      acpi_video_output, vo_next);
323 				acpi_video_vo_destroy(vo);
324 			}
325 		}
326 		break;
327 	default:
328 		device_printf(sc->device,
329 			      "unknown notify event 0x%x\n", notify);
330 	}
331 }
332 
333 static void
334 acpi_video_power_profile(void *context)
335 {
336 	int state;
337 	struct acpi_video_softc *sc;
338 	struct acpi_video_output *vo;
339 	ACPI_LOCK_DECL;
340 
341 	sc = context;
342 	state = power_profile_get_state();
343 	if (state != POWER_PROFILE_PERFORMANCE &&
344 	    state != POWER_PROFILE_ECONOMY)
345 		return;
346 
347 	ACPI_LOCK;
348 	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
349 		if (vo->vo_levels != NULL && vo->vo_brightness == -1)
350 			vo_set_brightness(vo->handle,
351 					  state == POWER_PROFILE_ECONOMY
352 					  ? vo->vo_economy : vo->vo_fullpower);
353 	}
354 	ACPI_UNLOCK;
355 }
356 
357 static void
358 acpi_video_bind_outputs_subr(ACPI_HANDLE handle, UINT32 adr, void *context)
359 {
360 	struct acpi_video_softc *sc;
361 	struct acpi_video_output *vo;
362 
363 	sc = context;
364 
365 	STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
366 		if (vo->adr == adr) {
367 			acpi_video_vo_bind(vo, handle);
368 			return;
369 		}
370 	}
371 	vo = acpi_video_vo_init(adr);
372 	if (vo != NULL) {
373 		acpi_video_vo_bind(vo, handle);
374 		STAILQ_INSERT_TAIL(&sc->vid_outputs, vo, vo_next);
375 	}
376 }
377 
378 static void
379 acpi_video_bind_outputs(struct acpi_video_softc *sc)
380 {
381 	ACPI_ASSERTLOCK;
382 
383 	vid_enum_outputs(sc->handle, acpi_video_bind_outputs_subr, sc);
384 }
385 
386 static struct acpi_video_output *
387 acpi_video_vo_init(UINT32 adr)
388 {
389 	struct acpi_video_output *vn, *vo, *vp;
390 	int n, x;
391 	char name[64], env[128];
392 	const char *type, *desc;
393 	struct acpi_video_output_queue *voqh;
394 
395 	switch (adr & DOD_DEVID_MASK) {
396 	case DOD_DEVID_MONITOR:
397 		desc = "CRT monitor";
398 		type = "crt";
399 		voqh = &crt_units;
400 		break;
401 	case DOD_DEVID_PANEL:
402 		desc = "LCD panel";
403 		type = "lcd";
404 		voqh = &lcd_units;
405 		break;
406 	case DOD_DEVID_TV:
407 		desc = "TV";
408 		type = "tv";
409 		voqh = &tv_units;
410 		break;
411 	default:
412 		desc = "unknown output";
413 		type = "out";
414 		voqh = &other_units;
415 	}
416 
417 	n = 0;
418 	vn = vp = NULL;
419 	/* XXX - needs locking for protecting STAILQ xxx_units. */
420 	STAILQ_FOREACH(vn, voqh, vo_unit.next) {
421 		if (vn->vo_unit.num != n)
422 			break;
423 		vp = vn;
424 		n++;
425 	}
426 
427 	snprintf(name, 64, "%s%d", type, n);
428 
429 	vo = malloc(sizeof(*vo), M_ACPIVIDEO, M_NOWAIT);
430 	if (vo != NULL) {
431 		vo->handle = NULL;
432 		vo->adr = adr;
433 		vo->vo_unit.num = n;
434 		vo->vo_brightness = -1;
435 		vo->vo_fullpower = -1;	/* TODO: override with tunables */
436 		vo->vo_economy = -1;
437 		vo->vo_numlevels = 0;
438 		vo->vo_levels = NULL;
439 		snprintf(env, 128, "hw.acpi.video.%s.fullpower", name);
440 		if (getenv_int(env, &x))
441 			vo->vo_fullpower = x;
442 		snprintf(env, 128, "hw.acpi.video.%s.economy", name);
443 		if (getenv_int(env, &x))
444 			vo->vo_economy = x;
445 
446 		sysctl_ctx_init(&vo->vo_sysctl_ctx);
447 		if (vp != NULL)
448 			STAILQ_INSERT_AFTER(voqh, vp, vo, vo_unit.next);
449 		else
450 			STAILQ_INSERT_TAIL(voqh, vo, vo_unit.next);
451 		if (acpi_video_sysctl_tree != NULL)
452 			vo->vo_sysctl_tree =
453 			    SYSCTL_ADD_NODE(&vo->vo_sysctl_ctx,
454 					    SYSCTL_CHILDREN(acpi_video_sysctl_tree),
455 					    OID_AUTO, name,
456 					    CTLFLAG_RD, 0, desc);
457 		if (vo->vo_sysctl_tree != NULL) {
458 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
459 					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
460 					OID_AUTO, "active",
461 					CTLTYPE_INT|CTLFLAG_RW,	vo, 0,
462 					acpi_video_vo_active_sysctl, "I",
463 					"current activity of this device");
464 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
465 					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
466 					OID_AUTO, "brightness",
467 					CTLTYPE_INT|CTLFLAG_RW, vo, 0,
468 					acpi_video_vo_bright_sysctl, "I",
469 					"current brightness level");
470 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
471 					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
472 					OID_AUTO, "fullpower",
473 					CTLTYPE_INT|CTLFLAG_RW, vo,
474 					POWER_PROFILE_PERFORMANCE,
475 					acpi_video_vo_presets_sysctl, "I",
476 					"preset level for full power mode");
477 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
478 					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
479 					OID_AUTO, "economy",
480 					CTLTYPE_INT|CTLFLAG_RW, vo,
481 					POWER_PROFILE_ECONOMY,
482 					acpi_video_vo_presets_sysctl, "I",
483 					"preset level for economy mode");
484 			SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
485 					SYSCTL_CHILDREN(vo->vo_sysctl_tree),
486 					OID_AUTO, "levels",
487 					CTLTYPE_OPAQUE|CTLFLAG_RD, vo, 0,
488 					acpi_video_vo_levels_sysctl, "I",
489 					"supported brightness levels");
490 		} else
491 			printf("%s: sysctl node creation failed\n", type);
492 	} else
493 		printf("%s: softc allocation failed\n", type);
494 
495 	/* XXX unlock here - needs locking for protecting STAILQ xxx_units. */
496 
497 	if (bootverbose) {
498 		printf("found %s(%x)", desc,
499 		       (unsigned int)(adr & DOD_DEVID_MASK));
500 		if (adr & DOD_BIOS)
501 			printf(", detectable by BIOS");
502 		if (adr & DOD_NONVGA)
503 			printf(" (not a VGA output)");
504 		printf(", head #%d\n",
505 		       (int)((adr & DOD_HEAD_ID_MASK) >> DOD_HEAD_ID_SHIFT));
506 	}
507 	return vo;
508 }
509 
510 static void
511 acpi_video_vo_bind(struct acpi_video_output *vo, ACPI_HANDLE handle)
512 {
513 	ACPI_ASSERTLOCK;
514 
515 	if (vo->vo_levels != NULL)
516 		AcpiOsFree(vo->vo_levels);
517 	vo->handle = handle;
518 	vo->vo_numlevels
519 		= vo_query_brightness_levels(handle, &vo->vo_levels);
520 	if (vo->vo_numlevels >= 2) {
521 		if (vo->vo_fullpower == -1
522 		    || acpi_video_vo_check_level(vo, vo->vo_fullpower) != 0)
523 			/* XXX - can't deal with rebinding... */
524 			vo->vo_fullpower = vo->vo_levels[BCL_FULLPOWER];
525 		if (vo->vo_economy == -1
526 		    || acpi_video_vo_check_level(vo, vo->vo_economy) != 0)
527 			/* XXX - see above. */
528 			vo->vo_economy = vo->vo_levels[BCL_ECONOMY];
529 	}
530 }
531 
532 static void
533 acpi_video_vo_destroy(struct acpi_video_output *vo)
534 {
535 	struct acpi_video_output_queue *voqh;
536 
537 	ACPI_ASSERTLOCK;
538 
539 	if (vo->vo_sysctl_tree != NULL) {
540 		vo->vo_sysctl_tree = NULL;
541 		sysctl_ctx_free(&vo->vo_sysctl_ctx);
542 	}
543 	if (vo->vo_levels != NULL)
544 		AcpiOsFree(vo->vo_levels);
545 
546 	switch (vo->adr & DOD_DEVID_MASK) {
547 	case DOD_DEVID_MONITOR:
548 		voqh = &crt_units;
549 		break;
550 	case DOD_DEVID_PANEL:
551 		voqh = &lcd_units;
552 		break;
553 	case DOD_DEVID_TV:
554 		voqh = &tv_units;
555 		break;
556 	default:
557 		voqh = &other_units;
558 	}
559 	/* XXX - needs locking for protecting STAILQ xxx_units. */
560 	STAILQ_REMOVE(voqh, vo, acpi_video_output, vo_unit.next);
561 	free(vo, M_ACPIVIDEO);
562 }
563 
564 static int
565 acpi_video_vo_check_level(struct acpi_video_output *vo, int level)
566 {
567 	int i;
568 
569 	if (vo->vo_levels == NULL)
570 		return (ENODEV);
571 	for (i = 0; i < vo->vo_numlevels; i++)
572 		if (vo->vo_levels[i] == level)
573 			return (0);
574 	return (EINVAL);
575 }
576 
577 /* ARGSUSED */
578 static int
579 acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS)
580 {
581 	struct acpi_video_output *vo;
582 	int state, err;
583 	ACPI_LOCK_DECL;
584 
585 	ACPI_LOCK;
586 	vo = (struct acpi_video_output *)arg1;
587 	if (vo->handle == NULL) {
588 		err = ENXIO;
589 		goto out;
590 	}
591 	state = vo_get_device_status(vo->handle) & DCS_ACTIVE? 1 : 0;
592 	err = sysctl_handle_int(oidp, &state, 0, req);
593 	if (err != 0 || req->newptr == NULL)
594 		goto out;
595 	vo_set_device_state(vo->handle,
596 			    DSS_COMMIT | (state? DSS_ACTIVE : DSS_INACTIVE));
597 out:
598 	ACPI_UNLOCK;
599 	return (err);
600 }
601 
602 /* ARGSUSED */
603 static int
604 acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS)
605 {
606 	struct acpi_video_output *vo;
607 	int level, preset, err;
608 	ACPI_LOCK_DECL;
609 
610 	ACPI_LOCK;
611 	vo = (struct acpi_video_output *)arg1;
612 	if (vo->handle == NULL) {
613 		err = ENXIO;
614 		goto out;
615 	}
616 	if (vo->vo_levels == NULL) {
617 		err = ENODEV;
618 		goto out;
619 	}
620 
621 	preset = (power_profile_get_state() == POWER_PROFILE_ECONOMY
622 		  ? vo->vo_economy
623 		  : vo->vo_fullpower);
624 	level = vo->vo_brightness;
625 	if (level == -1)
626 		level = preset;
627 
628 	err = sysctl_handle_int(oidp, &level, 0, req);
629 	if (err != 0 || req->newptr == NULL)
630 		goto out;
631 	if (level < -1 || level > 100) {
632 		err = EINVAL;
633 		goto out;
634 	}
635 
636 	if (level != -1 && (err = acpi_video_vo_check_level(vo, level)))
637 		goto out;
638 	vo->vo_brightness = level;
639 	vo_set_brightness(vo->handle, level == -1? preset : level);
640 out:
641 	ACPI_UNLOCK;
642 	return (err);
643 }
644 
645 static int
646 acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS)
647 {
648 	struct acpi_video_output *vo;
649 	int level, *preset, err = 0;
650 	ACPI_LOCK_DECL;
651 
652 	ACPI_LOCK;
653 	vo = (struct acpi_video_output *)arg1;
654 	if (vo->handle == NULL) {
655 		err = ENXIO;
656 		goto out;
657 	}
658 	if (vo->vo_levels == NULL) {
659 		err = ENODEV;
660 		goto out;
661 	}
662 	preset = (arg2 == POWER_PROFILE_ECONOMY
663 		  ? &vo->vo_economy
664 		  : &vo->vo_fullpower);
665 	level = *preset;
666 	err = sysctl_handle_int(oidp, &level, 0, req);
667 	if (err != 0 || req->newptr == NULL)
668 		goto out;
669 	if (level < -1 || level > 100) {
670 		err = EINVAL;
671 		goto out;
672 	}
673 	if (level == -1)
674 		level = vo->vo_levels
675 			[arg2 == POWER_PROFILE_ECONOMY
676 			 ? BCL_ECONOMY : BCL_FULLPOWER];
677 	else if ((err = acpi_video_vo_check_level(vo, level)) != 0)
678 		goto out;
679 
680 	if (vo->vo_brightness == -1 && (power_profile_get_state() == arg2))
681 		vo_set_brightness(vo->handle, level);
682 	*preset = level;
683 out:
684 	ACPI_UNLOCK;
685 	return (err);
686 }
687 
688 /* ARGSUSED */
689 static int
690 acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS)
691 {
692 	struct acpi_video_output *vo;
693 	int err;
694 	ACPI_LOCK_DECL;
695 
696 	ACPI_LOCK;
697 	vo = (struct acpi_video_output *)arg1;
698 	if (vo->vo_levels == NULL) {
699 		err = ENODEV;
700 		goto out;
701 	}
702 	if (req->newptr != NULL) {
703 		err = EPERM;
704 		goto out;
705 	}
706 	err = sysctl_handle_opaque(oidp, vo->vo_levels,
707 				vo->vo_numlevels * sizeof *vo->vo_levels, req);
708 out:
709 	ACPI_UNLOCK;
710 	return (err);
711 }
712 
713 static int
714 vid_check_requirements(ACPI_HANDLE handle)
715 {
716 	ACPI_HANDLE h_dod, h_dos;
717 	ACPI_OBJECT_TYPE t_dos;
718 
719 	ACPI_ASSERTLOCK;
720 
721 	/* check for _DOD, _DOS methods */
722 	return (ACPI_SUCCESS(AcpiGetHandle(handle, "_DOD", &h_dod))
723 	    && ACPI_SUCCESS(AcpiGetHandle(handle, "_DOS", &h_dos))
724 	    && ACPI_SUCCESS(AcpiGetType(h_dos, &t_dos))
725 	    && t_dos == ACPI_TYPE_METHOD);
726 }
727 
728 static void
729 vid_set_switch_policy(ACPI_HANDLE handle, UINT32 policy)
730 {
731 	ACPI_STATUS status;
732 
733 	ACPI_ASSERTLOCK;
734 
735 	status = acpi_SetInteger(handle, "_DOS", policy);
736 	if (ACPI_FAILURE(status))
737 		printf("can't evaluate %s._DOS - %s\n",
738 		       acpi_name(handle), AcpiFormatException(status));
739 }
740 
741 struct enum_callback_arg {
742 	void (*callback)(ACPI_HANDLE, UINT32, void *);
743 	void *context;
744 	ACPI_OBJECT *dod_pkg;
745 };
746 
747 static ACPI_STATUS
748 vid_enum_outputs_subr(ACPI_HANDLE handle, UINT32 level __unused,
749 		      void *context, void **retp)
750 {
751 	ACPI_STATUS status;
752 	ACPI_OBJECT *tmp;
753 	UINT32 adr;
754 	struct enum_callback_arg *argset;
755 	size_t i;
756 
757 	argset = context;
758 	status = acpi_GetInteger(handle, "_ADR", &adr);
759 	if (ACPI_SUCCESS(status)) {
760 		for (i = 0; i < argset->dod_pkg->Package.Count; i++) {
761 			tmp = &argset->dod_pkg->Package.Elements[i];
762 			if (tmp != NULL && tmp->Type == ACPI_TYPE_INTEGER &&
763 			    (tmp->Integer.Value & DOD_DEVID_MASK) == adr) {
764 				argset->callback(handle, tmp->Integer.Value,
765 						 argset->context);
766 				(**(int**)retp)++;
767 			}
768 		}
769 	}
770 
771 	return (AE_OK);
772 }
773 
774 static int
775 vid_enum_outputs(ACPI_HANDLE handle,
776 		 void (*callback)(ACPI_HANDLE, UINT32, void *), void *context)
777 {
778 	ACPI_STATUS status;
779 	ACPI_BUFFER dod_buf;
780 	ACPI_OBJECT *res;
781 	int num = 0;
782 	void *pnum;
783 	struct enum_callback_arg argset;
784 
785 	ACPI_ASSERTLOCK;
786 	dod_buf.Length = ACPI_ALLOCATE_BUFFER;
787 	dod_buf.Pointer = NULL;
788 	status = AcpiEvaluateObject(handle, "_DOD", NULL, &dod_buf);
789 	if (ACPI_FAILURE(status)) {
790 		if (status != AE_NOT_FOUND)
791 			printf("can't evaluate %s._DOD - %s\n",
792 			       acpi_name(handle), AcpiFormatException(status));
793 		num = -1;
794 		goto out;
795 	}
796 	res = (ACPI_OBJECT *)dod_buf.Pointer;
797 	if (res == NULL || res->Type != ACPI_TYPE_PACKAGE) {
798 		printf("evaluation of %s._DOD makes no sense\n",
799 		       acpi_name(handle));
800 		num = -1;
801 		goto out;
802 	}
803 	if (callback == NULL) {
804 		num = res->Package.Count;
805 		goto out;
806 	}
807 	argset.callback = callback;
808 	argset.context  = context;
809 	argset.dod_pkg  = res;
810 	pnum = &num;
811 	status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
812 				   vid_enum_outputs_subr, &argset,
813 				   &pnum);
814 	if (ACPI_FAILURE(status))
815 		printf("failed walking down %s - %s\n",
816 		       acpi_name(handle), AcpiFormatException(status));
817 out:
818 	if (dod_buf.Pointer != NULL)
819 		AcpiOsFree(dod_buf.Pointer);
820 	return (num);
821 }
822 
823 static int
824 vo_query_brightness_levels(ACPI_HANDLE handle, int **levelp)
825 {
826 	ACPI_STATUS status;
827 	ACPI_BUFFER bcl_buf;
828 	ACPI_OBJECT *res, *tmp;
829 	int num = 0, i, n, *levels;
830 
831 	ACPI_ASSERTLOCK;
832 	bcl_buf.Length = ACPI_ALLOCATE_BUFFER;
833 	bcl_buf.Pointer = NULL;
834 	status = AcpiEvaluateObject(handle, "_BCL", NULL, &bcl_buf);
835 	if (ACPI_FAILURE(status)) {
836 		if (status != AE_NOT_FOUND)
837 			printf("can't evaluate %s._BCL - %s\n",
838 			       acpi_name(handle), AcpiFormatException(status));
839 		num = -1;
840 		goto out;
841 	}
842 	res = (ACPI_OBJECT *)bcl_buf.Pointer;
843 	if (res == NULL || res->Type != ACPI_TYPE_PACKAGE ||
844 	    res->Package.Count < 2) {
845 		printf("evaluation of %s._BCL makes no sense\n",
846 		       acpi_name(handle));
847 		num = -1;
848 		goto out;
849 	}
850 	num = res->Package.Count;
851 	if (levelp == NULL)
852 		goto out;
853 	levels = AcpiOsAllocate(num * sizeof *levels);
854 	if (levels == NULL) {
855 		num = -1;
856 		goto out;
857 	}
858 	for (i = 0, n = 0; i < num; i++) {
859 		tmp = &res->Package.Elements[i];
860 		if (tmp != NULL && tmp->Type == ACPI_TYPE_INTEGER)
861 			levels[n++] = tmp->Integer.Value;
862 	}
863 	if (n < 2) {
864 		num = -1;
865 		AcpiOsFree(levels);
866 	} else {
867 		num = n;
868 		*levelp = levels;
869 	}
870 out:
871 	if (bcl_buf.Pointer != NULL)
872 		AcpiOsFree(bcl_buf.Pointer);
873 
874 	return (num);
875 }
876 
877 static void
878 vo_set_brightness(ACPI_HANDLE handle, int level)
879 {
880 	ACPI_STATUS status;
881 
882 	ACPI_ASSERTLOCK;
883 
884 	status = acpi_SetInteger(handle, "_BCM", level);
885 	if (ACPI_FAILURE(status))
886 		printf("can't evaluate %s._BCM - %s\n",
887 		       acpi_name(handle), AcpiFormatException(status));
888 }
889 
890 static UINT32
891 vo_get_device_status(ACPI_HANDLE handle)
892 {
893 	UINT32 dcs = 0;
894 	ACPI_STATUS status;
895 
896 	ACPI_ASSERTLOCK;
897 	status = acpi_GetInteger(handle, "_DCS", &dcs);
898 	if (ACPI_FAILURE(status))
899 		printf("can't evaluate %s._DCS - %s\n",
900 		       acpi_name(handle), AcpiFormatException(status));
901 
902 	return (dcs);
903 }
904 
905 static UINT32
906 vo_query_graphics_state(ACPI_HANDLE handle)
907 {
908 	UINT32 dgs = 0;
909 	ACPI_STATUS status;
910 
911 	ACPI_ASSERTLOCK;
912 	status = acpi_GetInteger(handle, "_DGS", &dgs);
913 	if (ACPI_FAILURE(status))
914 		printf("can't evaluate %s._DGS - %s\n",
915 		       acpi_name(handle), AcpiFormatException(status));
916 
917 	return (dgs);
918 }
919 
920 static void
921 vo_set_device_state(ACPI_HANDLE handle, UINT32 state)
922 {
923 	ACPI_STATUS status;
924 
925 	ACPI_ASSERTLOCK;
926 
927 	status = acpi_SetInteger(handle, "_DSS", state);
928 	if (ACPI_FAILURE(status))
929 		printf("can't evaluate %s._DSS - %s\n",
930 		       acpi_name(handle), AcpiFormatException(status));
931 }
932