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. */
52struct 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
74STAILQ_HEAD(acpi_video_output_queue, acpi_video_output);
75
76struct 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 */
87static int	acpi_video_modevent(struct module*, int, void *);
88static void	acpi_video_identify(driver_t *driver, device_t parent);
89static int	acpi_video_probe(device_t);
90static int	acpi_video_attach(device_t);
91static int	acpi_video_detach(device_t);
92static int	acpi_video_resume(device_t);
93static int	acpi_video_shutdown(device_t);
94static void	acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *);
95static void	acpi_video_power_profile(void *);
96static void	acpi_video_bind_outputs(struct acpi_video_softc *);
97static struct acpi_video_output *acpi_video_vo_init(UINT32);
98static void	acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE);
99static void	acpi_video_vo_destroy(struct acpi_video_output *);
100static int	acpi_video_vo_check_level(struct acpi_video_output *, int);
101static void	acpi_video_vo_notify_handler(ACPI_HANDLE, UINT32, void *);
102static int	acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS);
103static int	acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS);
104static int	acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS);
105static int	acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS);
106
107/* operations */
108static void	vid_set_switch_policy(ACPI_HANDLE, UINT32);
109static int	vid_enum_outputs(ACPI_HANDLE,
110		    void(*)(ACPI_HANDLE, UINT32, void *), void *);
111static int	vo_get_brightness_levels(ACPI_HANDLE, int **);
112static int	vo_get_brightness(struct acpi_video_output *);
113static void	vo_set_brightness(struct acpi_video_output *, int);
114static UINT32	vo_get_device_status(ACPI_HANDLE);
115static UINT32	vo_get_graphics_state(ACPI_HANDLE);
116static 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
172static 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
182static driver_t acpi_video_driver = {
183	"acpi_video",
184	acpi_video_methods,
185	sizeof(struct acpi_video_softc),
186};
187
188DRIVER_MODULE(acpi_video, vgapci, acpi_video_driver, acpi_video_modevent, NULL);
189MODULE_DEPEND(acpi_video, acpi, 1, 1, 1);
190#ifdef EVDEV_SUPPORT
191MODULE_DEPEND(acpi_video, evdev, 1, 1, 1);
192#endif
193
194static struct sysctl_ctx_list	acpi_video_sysctl_ctx;
195static struct sysctl_oid	*acpi_video_sysctl_tree;
196static 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 */
204ACPI_SERIAL_DECL(video, "ACPI video");
205ACPI_SERIAL_DECL(video_output, "ACPI video output");
206static MALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension");
207
208#ifdef EVDEV_SUPPORT
209static 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
225static void
226acpi_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
247static int
248acpi_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
273static void
274acpi_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", -1);
279}
280
281static int
282acpi_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
299static int
300acpi_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
362static int
363acpi_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
388static int
389acpi_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
418static int
419acpi_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
429static void
430acpi_video_invoke_event_handler(void *context)
431{
432	EVENTHANDLER_INVOKE(acpi_video_event, (int)(intptr_t)context);
433}
434
435static void
436acpi_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
500static void
501acpi_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
525static void
526acpi_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
550static void
551acpi_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
558static struct acpi_video_output *
559acpi_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
697static void
698acpi_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
727static void
728acpi_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
766static int
767acpi_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
780static void
781acpi_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
856out:
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 */
864static int
865acpi_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));
880out:
881	ACPI_SERIAL_END(video_output);
882	return (err);
883}
884
885/* ARGSUSED */
886static int
887acpi_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
922out:
923	ACPI_SERIAL_END(video_output);
924	return (err);
925}
926
927static int
928acpi_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
964out:
965	ACPI_SERIAL_END(video_output);
966	return (err);
967}
968
969/* ARGSUSED */
970static int
971acpi_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
989out:
990	ACPI_SERIAL_END(video_output);
991	return (err);
992}
993
994static void
995vid_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
1005struct enum_callback_arg {
1006	void (*callback)(ACPI_HANDLE, UINT32, void *);
1007	void *context;
1008	ACPI_OBJECT *dod_pkg;
1009	int count;
1010};
1011
1012static ACPI_STATUS
1013vid_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
1039static int
1040vid_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));
1079out:
1080	if (dod_buf.Pointer != NULL)
1081		AcpiOsFree(dod_buf.Pointer);
1082	return (argset.count);
1083}
1084
1085static int
1086vo_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
1124out:
1125	if (bcl_buf.Pointer != NULL)
1126		AcpiOsFree(bcl_buf.Pointer);
1127	return (0);
1128}
1129
1130static int
1131vo_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
1154static int
1155vo_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
1173static void
1174vo_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
1191static UINT32
1192vo_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
1216static UINT32
1217vo_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
1240static void
1241vo_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