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