Add acpi_video(4) - a driver for ACPI video extensions.
authorHasso Tepper <hasso@dragonflybsd.org>
Thu, 28 Aug 2008 09:25:42 +0000 (09:25 +0000)
committerHasso Tepper <hasso@dragonflybsd.org>
Thu, 28 Aug 2008 09:25:42 +0000 (09:25 +0000)
Obtained-from: FreeBSD with modifications

sys/conf/files
sys/dev/acpica5/Makefile
sys/dev/acpica5/acpi_video/Makefile [new file with mode: 0644]
sys/dev/acpica5/acpi_video/acpi_video.c [new file with mode: 0644]

index 06c228f..6d96cf4 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/sys/conf/files,v 1.340.2.137 2003/06/04 17:10:30 sam Exp $
-# $DragonFly: src/sys/conf/files,v 1.232 2008/08/28 07:10:04 hasso Exp $
+# $DragonFly: src/sys/conf/files,v 1.233 2008/08/28 09:25:42 hasso Exp $
 #
 # The long compile-with and dependency lines are required because of
 # limitations in config: backslash-newline doesn't work in strings, and
@@ -1564,6 +1564,7 @@ ${OSACPI_MI_DIR}/Osd/OsdTable.c           optional acpi
 
 ${OSACPI_MI_DIR}/acpi_toshiba/acpi_toshiba.c   optional acpi_toshiba acpi
 ${OSACPI_MI_DIR}/acpi_thinkpad/acpi_thinkpad.c optional acpi_thinkpad acpi
+${OSACPI_MI_DIR}/acpi_video/acpi_video.c       optional acpi_video acpi
 
 # ACPICA code
 ${ACPICA_DIR}/debugger/dbcmds.c                        optional acpi acpi_debug
index 3934070..958323d 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/sys/modules/acpi/acpi/Makefile,v 1.3 2004/01/08 16:38:32 njl Exp $
-# $DragonFly: src/sys/dev/acpica5/Makefile,v 1.22 2008/08/28 07:10:05 hasso Exp $
+# $DragonFly: src/sys/dev/acpica5/Makefile,v 1.23 2008/08/28 09:25:42 hasso Exp $
 
 CONTRIBDIR=    ${SYSDIR}/${ACPICA_DIR}
 # patches to fix problems in ACPI-CA code
@@ -117,7 +117,7 @@ acpi_wakecode.h: acpi_wakecode.S
        ${MAKE} -f ${SYSDIR}/${OSACPI_MD_DIR}/Makefile \
                MAKESRCPATH=${SYSDIR}/${OSACPI_MD_DIR}
 
-SUBDIR=        acpi_thinkpad acpi_toshiba
+SUBDIR=        acpi_thinkpad acpi_toshiba acpi_video
 all: ${PROG} ${SUBDIR}
 
 # *.o file for each patched *.c file is created under the same directory,
diff --git a/sys/dev/acpica5/acpi_video/Makefile b/sys/dev/acpica5/acpi_video/Makefile
new file mode 100644 (file)
index 0000000..c774706
--- /dev/null
@@ -0,0 +1,7 @@
+# $DragonFly: src/sys/dev/acpica5/acpi_video/Makefile,v 1.1 2008/08/28 09:25:42 hasso Exp $
+
+KMOD=          acpi_video
+CFLAGS+=       -I${.OBJDIR}/.. -I${.CURDIR}/..
+SRCS=          acpi_video.c opt_acpi.h device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/dev/acpica5/acpi_video/acpi_video.c b/sys/dev/acpica5/acpi_video/acpi_video.c
new file mode 100644 (file)
index 0000000..f9f8b0b
--- /dev/null
@@ -0,0 +1,921 @@
+/*-
+ * Copyright (c) 2002-2003 Taku YAMAMOTO <taku@cent.saitama-u.ac.jp>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/sys/dev/acpica/acpi_video.c,v 1.13 2006/08/10 13:18:02 bruno Exp $
+ * $DragonFly: src/sys/dev/acpica5/acpi_video/acpi_video.c,v 1.1 2008/08/28 09:25:42 hasso Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/power.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+
+#include "acpi.h"
+#include "acpivar.h"
+
+/* ACPI video extension driver. */
+struct acpi_video_output {
+       ACPI_HANDLE     handle;
+       UINT32          adr;
+       STAILQ_ENTRY(acpi_video_output) vo_next;
+       struct {
+               int     num;
+               STAILQ_ENTRY(acpi_video_output) next;
+       } vo_unit;
+       int             vo_brightness;
+       int             vo_fullpower;
+       int             vo_economy;
+       int             vo_numlevels;
+       int             *vo_levels;
+       struct sysctl_ctx_list vo_sysctl_ctx;
+       struct sysctl_oid *vo_sysctl_tree;
+};
+
+STAILQ_HEAD(acpi_video_output_queue, acpi_video_output);
+
+struct acpi_video_softc {
+       device_t                device;
+       ACPI_HANDLE             handle;
+       struct acpi_video_output_queue vid_outputs;
+       eventhandler_tag        vid_pwr_evh;
+};
+
+/* interfaces */
+static int     acpi_video_modevent(struct module*, int, void *);
+static int     acpi_video_probe(device_t);
+static int     acpi_video_attach(device_t);
+static int     acpi_video_detach(device_t);
+static int     acpi_video_shutdown(device_t);
+static void    acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *);
+static void    acpi_video_power_profile(void *);
+static void    acpi_video_bind_outputs(struct acpi_video_softc *);
+static struct acpi_video_output *acpi_video_vo_init(UINT32);
+static void    acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE);
+static void    acpi_video_vo_destroy(struct acpi_video_output *);
+static int     acpi_video_vo_check_level(struct acpi_video_output *, int);
+static int     acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS);
+static int     acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS);
+static int     acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS);
+static int     acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS);
+
+/* operations */
+static void    vid_set_switch_policy(ACPI_HANDLE, UINT32);
+static int     vid_enum_outputs(ACPI_HANDLE,
+                   void(*)(ACPI_HANDLE, UINT32, void *), void *);
+static int     vo_get_brightness_levels(ACPI_HANDLE, int **);
+static void    vo_set_brightness(ACPI_HANDLE, int);
+static UINT32  vo_get_device_status(ACPI_HANDLE);
+static UINT32  vo_get_graphics_state(ACPI_HANDLE);
+static void    vo_set_device_state(ACPI_HANDLE, UINT32);
+
+/* events */
+#define VID_NOTIFY_SWITCHED    0x80
+#define VID_NOTIFY_REPROBE     0x81
+
+/* _DOS (Enable/Disable Output Switching) argument bits */
+#define DOS_SWITCH_MASK                3
+#define DOS_SWITCH_BY_OSPM     0
+#define DOS_SWITCH_BY_BIOS     1
+#define DOS_SWITCH_LOCKED      2
+#define DOS_BRIGHTNESS_BY_BIOS (1 << 2)
+
+/* _DOD and subdev's _ADR */
+#define DOD_DEVID_MASK         0x0f00
+#define DOD_DEVID_MASK_FULL    0xffff
+#define DOD_DEVID_MASK_DISPIDX 0x000f
+#define DOD_DEVID_MASK_DISPPORT        0x00f0
+#define DOD_DEVID_MONITOR      0x0100
+#define DOD_DEVID_LCD          0x0110
+#define DOD_DEVID_TV           0x0200
+#define DOD_DEVID_EXT          0x0300
+#define DOD_DEVID_INTDFP       0x0400
+#define DOD_BIOS               (1 << 16)
+#define DOD_NONVGA             (1 << 17)
+#define DOD_HEAD_ID_SHIFT      18
+#define DOD_HEAD_ID_BITS       3
+#define DOD_HEAD_ID_MASK \
+               (((1 << DOD_HEAD_ID_BITS) - 1) << DOD_HEAD_ID_SHIFT)
+#define DOD_DEVID_SCHEME_STD   (1 << 31)
+
+/* _BCL related constants */
+#define BCL_FULLPOWER          0
+#define BCL_ECONOMY            1
+
+/* _DCS (Device Currrent Status) value bits and masks. */
+#define DCS_EXISTS             (1 << 0)
+#define DCS_ACTIVE             (1 << 1)
+#define DCS_READY              (1 << 2)
+#define DCS_FUNCTIONAL         (1 << 3)
+#define DCS_ATTACHED           (1 << 4)
+
+/* _DSS (Device Set Status) argument bits and masks. */
+#define DSS_INACTIVE           0
+#define DSS_ACTIVE             (1 << 0)
+#define DSS_SETNEXT            (1 << 30)
+#define DSS_COMMIT             (1 << 31)
+
+static device_method_t acpi_video_methods[] = {
+       DEVMETHOD(device_probe, acpi_video_probe),
+       DEVMETHOD(device_attach, acpi_video_attach),
+       DEVMETHOD(device_detach, acpi_video_detach),
+       DEVMETHOD(device_shutdown, acpi_video_shutdown),
+       { 0, 0 }
+};
+
+static driver_t acpi_video_driver = {
+       "acpi_video",
+       acpi_video_methods,
+       sizeof(struct acpi_video_softc),
+};
+
+static devclass_t acpi_video_devclass;
+
+DRIVER_MODULE(acpi_video, acpi, acpi_video_driver, acpi_video_devclass,
+             acpi_video_modevent, NULL);
+MODULE_DEPEND(acpi_video, acpi, 1, 1, 1);
+
+static struct sysctl_ctx_list  acpi_video_sysctl_ctx;
+static struct sysctl_oid       *acpi_video_sysctl_tree;
+static struct acpi_video_output_queue crt_units, tv_units,
+    ext_units, lcd_units, other_units;
+static struct lock avlock;
+
+MALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension");
+
+static int
+acpi_video_modevent(struct module *mod __unused, int evt, void *cookie __unused)
+{
+       int err;
+
+       err = 0;
+       switch (evt) {
+       case MOD_LOAD:
+               sysctl_ctx_init(&acpi_video_sysctl_ctx);
+               STAILQ_INIT(&crt_units);
+               STAILQ_INIT(&tv_units);
+               STAILQ_INIT(&ext_units);
+               STAILQ_INIT(&lcd_units);
+               STAILQ_INIT(&other_units);
+               break;
+       case MOD_UNLOAD:
+               sysctl_ctx_free(&acpi_video_sysctl_ctx);
+               acpi_video_sysctl_tree = NULL;
+               break;
+       default:
+               err = EINVAL;
+       }
+
+       return (err);
+}
+
+static int
+acpi_video_probe(device_t dev)
+{
+       ACPI_HANDLE devh, h;
+       ACPI_OBJECT_TYPE t_dos;
+
+       devh = acpi_get_handle(dev);
+       if (acpi_disabled("video") ||
+           ACPI_FAILURE(AcpiGetHandle(devh, "_DOD", &h)) ||
+           ACPI_FAILURE(AcpiGetHandle(devh, "_DOS", &h)) ||
+           ACPI_FAILURE(AcpiGetType(h, &t_dos)) ||
+           t_dos != ACPI_TYPE_METHOD)
+               return (ENXIO);
+
+       device_set_desc(dev, "ACPI video extension");
+       return (0);
+}
+
+static int
+acpi_video_attach(device_t dev)
+{
+       struct acpi_softc *acpi_sc;
+       struct acpi_video_softc *sc;
+
+       sc = device_get_softc(dev);
+
+       acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
+       if (acpi_sc == NULL)
+               return (ENXIO);
+       if (acpi_video_sysctl_tree == NULL) {
+               acpi_video_sysctl_tree = SYSCTL_ADD_NODE(&acpi_video_sysctl_ctx,
+                                   SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
+                                   OID_AUTO, "video", CTLFLAG_RD, 0,
+                                   "video extension control");
+       }
+
+       sc->device = dev;
+       sc->handle = acpi_get_handle(dev);
+       STAILQ_INIT(&sc->vid_outputs);
+
+       AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
+                                acpi_video_notify_handler, sc);
+       sc->vid_pwr_evh = EVENTHANDLER_REGISTER(power_profile_change,
+                                acpi_video_power_profile, sc, 0);
+
+       lockinit(&avlock, "acpi_video", 0, 0);
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       acpi_video_bind_outputs(sc);
+       lockmgr(&avlock, LK_RELEASE);
+
+       /*
+        * Notify the BIOS that we want to switch both active outputs and
+        * brightness levels.
+        */
+       vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_OSPM |
+           DOS_BRIGHTNESS_BY_BIOS);
+
+       acpi_video_power_profile(sc);
+
+       return (0);
+}
+
+static int
+acpi_video_detach(device_t dev)
+{
+       struct acpi_video_softc *sc;
+       struct acpi_video_output *vo, *vn;
+
+       sc = device_get_softc(dev);
+
+       vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
+       EVENTHANDLER_DEREGISTER(power_profile_change, sc->vid_pwr_evh);
+       AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
+                               acpi_video_notify_handler);
+
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       for (vo = STAILQ_FIRST(&sc->vid_outputs); vo != NULL; vo = vn) {
+               vn = STAILQ_NEXT(vo, vo_next);
+               acpi_video_vo_destroy(vo);
+       }
+       lockmgr(&avlock, LK_RELEASE);
+
+       return (0);
+}
+
+static int
+acpi_video_shutdown(device_t dev)
+{
+       struct acpi_video_softc *sc;
+
+       sc = device_get_softc(dev);
+       vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS);
+
+       return (0);
+}
+
+static void
+acpi_video_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context)
+{
+       struct acpi_video_softc *sc;
+       struct acpi_video_output *vo, *vo_tmp;
+       ACPI_HANDLE lasthand;
+       UINT32 dcs, dss, dss_p;
+
+       sc = (struct acpi_video_softc *)context;
+
+       switch (notify) {
+       case VID_NOTIFY_SWITCHED:
+               dss_p = 0;
+               lasthand = NULL;
+               lockmgr(&avlock, LK_EXCLUSIVE);
+               STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
+                       dss = vo_get_graphics_state(vo->handle);
+                       dcs = vo_get_device_status(vo->handle);
+                       if (!(dcs & DCS_READY))
+                               dss = DSS_INACTIVE;
+                       if (((dcs & DCS_ACTIVE) && dss == DSS_INACTIVE) ||
+                           (!(dcs & DCS_ACTIVE) && dss == DSS_ACTIVE)) {
+                               if (lasthand != NULL)
+                                       vo_set_device_state(lasthand, dss_p);
+                               dss_p = dss;
+                               lasthand = vo->handle;
+                       }
+               }
+               if (lasthand != NULL)
+                       vo_set_device_state(lasthand, dss_p|DSS_COMMIT);
+               lockmgr(&avlock, LK_RELEASE);
+               break;
+       case VID_NOTIFY_REPROBE:
+               lockmgr(&avlock, LK_EXCLUSIVE);
+               STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next)
+                       vo->handle = NULL;
+               acpi_video_bind_outputs(sc);
+               STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vo_tmp) {
+                       if (vo->handle == NULL) {
+                               STAILQ_REMOVE(&sc->vid_outputs, vo,
+                                   acpi_video_output, vo_next);
+                               acpi_video_vo_destroy(vo);
+                       }
+               }
+               lockmgr(&avlock, LK_RELEASE);
+               break;
+       default:
+               device_printf(sc->device, "unknown notify event 0x%x\n",
+                   notify);
+       }
+}
+
+static void
+acpi_video_power_profile(void *context)
+{
+       int state;
+       struct acpi_video_softc *sc;
+       struct acpi_video_output *vo;
+
+       sc = context;
+       state = power_profile_get_state();
+       if (state != POWER_PROFILE_PERFORMANCE &&
+           state != POWER_PROFILE_ECONOMY)
+               return;
+
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
+               if (vo->vo_levels != NULL && vo->vo_brightness == -1)
+                       vo_set_brightness(vo->handle,
+                           state == POWER_PROFILE_ECONOMY ?
+                           vo->vo_economy : vo->vo_fullpower);
+       }
+       lockmgr(&avlock, LK_RELEASE);
+}
+
+static void
+acpi_video_bind_outputs_subr(ACPI_HANDLE handle, UINT32 adr, void *context)
+{
+       struct acpi_video_softc *sc;
+       struct acpi_video_output *vo;
+
+       sc = context;
+
+       STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) {
+               if (vo->adr == adr) {
+                       acpi_video_vo_bind(vo, handle);
+                       return;
+               }
+       }
+       vo = acpi_video_vo_init(adr);
+       if (vo != NULL) {
+               acpi_video_vo_bind(vo, handle);
+               STAILQ_INSERT_TAIL(&sc->vid_outputs, vo, vo_next);
+       }
+}
+
+static void
+acpi_video_bind_outputs(struct acpi_video_softc *sc)
+{
+
+       vid_enum_outputs(sc->handle, acpi_video_bind_outputs_subr, sc);
+}
+
+static struct acpi_video_output *
+acpi_video_vo_init(UINT32 adr)
+{
+       struct acpi_video_output *vn, *vo, *vp;
+       int n, x;
+       int display_index;
+       int display_port;
+       char name[8], env[32];
+       const char *type, *desc;
+       struct acpi_video_output_queue *voqh;
+
+       display_index = adr & DOD_DEVID_MASK_DISPIDX;
+       display_port = (adr & DOD_DEVID_MASK_DISPPORT) >> 4;
+
+       switch (adr & DOD_DEVID_MASK) {
+       case DOD_DEVID_MONITOR:
+               if ((adr & DOD_DEVID_MASK_FULL) == DOD_DEVID_LCD) {
+                       /* DOD_DEVID_LCD is a common, backward compatible ID */
+                       desc = "Internal/Integrated Digital Flat Panel";
+                       type = "lcd";
+                       voqh = &lcd_units;
+               } else {
+                       desc = "VGA CRT or VESA Compatible Analog Monitor";
+                       type = "crt";
+                       voqh = &crt_units;
+               }
+               break;
+       case DOD_DEVID_TV:
+               desc = "TV/HDTV or Analog-Video Monitor";
+               type = "tv";
+               voqh = &tv_units;
+               break;
+       case DOD_DEVID_EXT:
+               desc = "External Digital Monitor";
+               type = "ext";
+               voqh = &ext_units;
+               break;
+       case DOD_DEVID_INTDFP:
+               desc = "Internal/Integrated Digital Flat Panel";
+               type = "lcd";
+               voqh = &lcd_units;
+               break;
+       default:
+               desc = "unknown output";
+               type = "out";
+               voqh = &other_units;
+       }
+
+       n = 0;
+       vn = vp = NULL;
+       STAILQ_FOREACH(vn, voqh, vo_unit.next) {
+               if (vn->vo_unit.num != n)
+                       break;
+               vp = vn;
+               n++;
+       }
+
+       ksnprintf(name, sizeof(name), "%s%d", type, n);
+
+       vo = kmalloc(sizeof(*vo), M_ACPIVIDEO, M_NOWAIT);
+       if (vo != NULL) {
+               vo->handle = NULL;
+               vo->adr = adr;
+               vo->vo_unit.num = n;
+               vo->vo_brightness = -1;
+               vo->vo_fullpower = -1;  /* TODO: override with tunables */
+               vo->vo_economy = -1;
+               vo->vo_numlevels = 0;
+               vo->vo_levels = NULL;
+               ksnprintf(env, sizeof(env), "hw.acpi.video.%s.fullpower", name);
+               if (kgetenv_int(env, &x))
+                       vo->vo_fullpower = x;
+               ksnprintf(env, sizeof(env), "hw.acpi.video.%s.economy", name);
+               if (kgetenv_int(env, &x))
+                       vo->vo_economy = x;
+
+               sysctl_ctx_init(&vo->vo_sysctl_ctx);
+               if (vp != NULL)
+                       STAILQ_INSERT_AFTER(voqh, vp, vo, vo_unit.next);
+               else
+                       STAILQ_INSERT_TAIL(voqh, vo, vo_unit.next);
+               if (acpi_video_sysctl_tree != NULL)
+                       vo->vo_sysctl_tree =
+                           SYSCTL_ADD_NODE(&vo->vo_sysctl_ctx,
+                               SYSCTL_CHILDREN(acpi_video_sysctl_tree),
+                               OID_AUTO, name, CTLFLAG_RD, 0, desc);
+               if (vo->vo_sysctl_tree != NULL) {
+                       SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
+                           SYSCTL_CHILDREN(vo->vo_sysctl_tree),
+                           OID_AUTO, "active",
+                           CTLTYPE_INT|CTLFLAG_RW, vo, 0,
+                           acpi_video_vo_active_sysctl, "I",
+                           "current activity of this device");
+                       SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
+                           SYSCTL_CHILDREN(vo->vo_sysctl_tree),
+                           OID_AUTO, "brightness",
+                           CTLTYPE_INT|CTLFLAG_RW, vo, 0,
+                           acpi_video_vo_bright_sysctl, "I",
+                           "current brightness level");
+                       SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
+                           SYSCTL_CHILDREN(vo->vo_sysctl_tree),
+                           OID_AUTO, "fullpower",
+                           CTLTYPE_INT|CTLFLAG_RW, vo,
+                           POWER_PROFILE_PERFORMANCE,
+                           acpi_video_vo_presets_sysctl, "I",
+                           "preset level for full power mode");
+                       SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
+                           SYSCTL_CHILDREN(vo->vo_sysctl_tree),
+                           OID_AUTO, "economy",
+                           CTLTYPE_INT|CTLFLAG_RW, vo,
+                           POWER_PROFILE_ECONOMY,
+                           acpi_video_vo_presets_sysctl, "I",
+                           "preset level for economy mode");
+                       SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx,
+                           SYSCTL_CHILDREN(vo->vo_sysctl_tree),
+                           OID_AUTO, "levels",
+                           CTLTYPE_OPAQUE|CTLFLAG_RD, vo, 0,
+                           acpi_video_vo_levels_sysctl, "I",
+                           "supported brightness levels");
+               } else
+                       kprintf("%s: sysctl node creation failed\n", type);
+       } else
+               kprintf("%s: softc allocation failed\n", type);
+
+       if (bootverbose) {
+               kprintf("found %s(%x)", desc, adr & DOD_DEVID_MASK_FULL);
+               kprintf(", idx#%x", adr & DOD_DEVID_MASK_DISPIDX);
+               kprintf(", port#%x", (adr & DOD_DEVID_MASK_DISPPORT) >> 4);
+               if (adr & DOD_BIOS)
+                       kprintf(", detectable by BIOS");
+               if (adr & DOD_NONVGA)
+                       kprintf(" (Non-VGA output device whose power "
+                           "is related to the VGA device)");
+               kprintf(", head #%d\n",
+                       (adr & DOD_HEAD_ID_MASK) >> DOD_HEAD_ID_SHIFT);
+       }
+       return (vo);
+}
+
+static void
+acpi_video_vo_bind(struct acpi_video_output *vo, ACPI_HANDLE handle)
+{
+       if (vo->vo_levels != NULL)
+               AcpiOsFree(vo->vo_levels);
+       vo->handle = handle;
+       vo->vo_numlevels = vo_get_brightness_levels(handle, &vo->vo_levels);
+       if (vo->vo_numlevels >= 2) {
+               if (vo->vo_fullpower == -1
+                   || acpi_video_vo_check_level(vo, vo->vo_fullpower) != 0)
+                       /* XXX - can't deal with rebinding... */
+                       vo->vo_fullpower = vo->vo_levels[BCL_FULLPOWER];
+               if (vo->vo_economy == -1
+                   || acpi_video_vo_check_level(vo, vo->vo_economy) != 0)
+                       /* XXX - see above. */
+                       vo->vo_economy = vo->vo_levels[BCL_ECONOMY];
+       }
+}
+
+static void
+acpi_video_vo_destroy(struct acpi_video_output *vo)
+{
+       struct acpi_video_output_queue *voqh;
+
+       if (vo->vo_sysctl_tree != NULL) {
+               vo->vo_sysctl_tree = NULL;
+               sysctl_ctx_free(&vo->vo_sysctl_ctx);
+       }
+       if (vo->vo_levels != NULL)
+               AcpiOsFree(vo->vo_levels);
+
+       switch (vo->adr & DOD_DEVID_MASK) {
+       case DOD_DEVID_MONITOR:
+               voqh = &crt_units;
+               break;
+       case DOD_DEVID_TV:
+               voqh = &tv_units;
+               break;
+       case DOD_DEVID_EXT:
+               voqh = &ext_units;
+               break;
+       case DOD_DEVID_INTDFP:
+               voqh = &lcd_units;
+               break;
+       default:
+               voqh = &other_units;
+       }
+       STAILQ_REMOVE(voqh, vo, acpi_video_output, vo_unit.next);
+       kfree(vo, M_ACPIVIDEO);
+}
+
+static int
+acpi_video_vo_check_level(struct acpi_video_output *vo, int level)
+{
+       int i;
+
+       if (vo->vo_levels == NULL)
+               return (ENODEV);
+       for (i = 0; i < vo->vo_numlevels; i++)
+               if (vo->vo_levels[i] == level)
+                       return (0);
+       return (EINVAL);
+}
+
+/* ARGSUSED */
+static int
+acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS)
+{
+       struct acpi_video_output *vo;
+       int state, err;
+
+       vo = (struct acpi_video_output *)arg1;
+       if (vo->handle == NULL)
+               return (ENXIO);
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       state = (vo_get_device_status(vo->handle) & DCS_ACTIVE) ? 1 : 0;
+       err = sysctl_handle_int(oidp, &state, 0, req);
+       if (err != 0 || req->newptr == NULL)
+               goto out;
+       vo_set_device_state(vo->handle,
+           DSS_COMMIT | (state ? DSS_ACTIVE : DSS_INACTIVE));
+out:
+       lockmgr(&avlock, LK_RELEASE);
+       return (err);
+}
+
+/* ARGSUSED */
+static int
+acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS)
+{
+       struct acpi_video_output *vo;
+       int level, preset, err;
+
+       vo = (struct acpi_video_output *)arg1;
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       if (vo->handle == NULL) {
+               err = ENXIO;
+               goto out;
+       }
+       if (vo->vo_levels == NULL) {
+               err = ENODEV;
+               goto out;
+       }
+
+       preset = (power_profile_get_state() == POWER_PROFILE_ECONOMY) ?
+                 vo->vo_economy : vo->vo_fullpower;
+       level = vo->vo_brightness;
+       if (level == -1)
+               level = preset;
+
+       err = sysctl_handle_int(oidp, &level, 0, req);
+       if (err != 0 || req->newptr == NULL)
+               goto out;
+       if (level < -1 || level > 100) {
+               err = EINVAL;
+               goto out;
+       }
+
+       if (level != -1 && (err = acpi_video_vo_check_level(vo, level)))
+               goto out;
+       vo->vo_brightness = level;
+       vo_set_brightness(vo->handle, (level == -1) ? preset : level);
+
+out:
+       lockmgr(&avlock, LK_RELEASE);
+       return (err);
+}
+
+static int
+acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS)
+{
+       struct acpi_video_output *vo;
+       int i, level, *preset, err;
+
+       err = 0;
+       vo = (struct acpi_video_output *)arg1;
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       if (vo->handle == NULL) {
+               err = ENXIO;
+               goto out;
+       }
+       if (vo->vo_levels == NULL) {
+               err = ENODEV;
+               goto out;
+       }
+       preset = (arg2 == POWER_PROFILE_ECONOMY) ?
+                 &vo->vo_economy : &vo->vo_fullpower;
+       level = *preset;
+       err = sysctl_handle_int(oidp, &level, 0, req);
+       if (err != 0 || req->newptr == NULL)
+               goto out;
+       if (level < -1 || level > 100) {
+               err = EINVAL;
+               goto out;
+       }
+       if (level == -1) {
+               i = (arg2 == POWER_PROFILE_ECONOMY) ?
+                   BCL_ECONOMY : BCL_FULLPOWER;
+               level = vo->vo_levels[i];
+       } else if ((err = acpi_video_vo_check_level(vo, level)) != 0)
+               goto out;
+
+       if (vo->vo_brightness == -1 && (power_profile_get_state() == arg2))
+               vo_set_brightness(vo->handle, level);
+       *preset = level;
+
+out:
+       lockmgr(&avlock, LK_RELEASE);
+       return (err);
+}
+
+/* ARGSUSED */
+static int
+acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS)
+{
+       struct acpi_video_output *vo;
+       int err;
+
+       vo = (struct acpi_video_output *)arg1;
+       lockmgr(&avlock, LK_EXCLUSIVE);
+       if (vo->vo_levels == NULL) {
+               err = ENODEV;
+               goto out;
+       }
+       if (req->newptr != NULL) {
+               err = EPERM;
+               goto out;
+       }
+       err = sysctl_handle_opaque(oidp, vo->vo_levels,
+           vo->vo_numlevels * sizeof(*vo->vo_levels), req);
+
+out:
+       lockmgr(&avlock, LK_RELEASE);
+       return (err);
+}
+
+static void
+vid_set_switch_policy(ACPI_HANDLE handle, UINT32 policy)
+{
+       ACPI_STATUS status;
+
+       status = acpi_SetInteger(handle, "_DOS", policy);
+       if (ACPI_FAILURE(status))
+               kprintf("can't evaluate %s._DOS - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+}
+
+struct enum_callback_arg {
+       void (*callback)(ACPI_HANDLE, UINT32, void *);
+       void *context;
+       ACPI_OBJECT *dod_pkg;
+       int count;
+};
+
+static ACPI_STATUS
+vid_enum_outputs_subr(ACPI_HANDLE handle, UINT32 level __unused,
+                     void *context, void **retp __unused)
+{
+       ACPI_STATUS status;
+       UINT32 adr, val;
+       struct enum_callback_arg *argset;
+       size_t i;
+
+       argset = context;
+       status = acpi_GetInteger(handle, "_ADR", &adr);
+       if (ACPI_FAILURE(status))
+               return (AE_OK);
+
+       for (i = 0; i < argset->dod_pkg->Package.Count; i++) {
+               if (acpi_PkgInt32(argset->dod_pkg, i, &val) == 0 &&
+                   (val & DOD_DEVID_MASK_FULL) == adr) {
+                       argset->callback(handle, val, argset->context);
+                       argset->count++;
+               }
+       }
+
+       return (AE_OK);
+}
+
+static int
+vid_enum_outputs(ACPI_HANDLE handle,
+                void (*callback)(ACPI_HANDLE, UINT32, void *), void *context)
+{
+       ACPI_STATUS status;
+       ACPI_BUFFER dod_buf;
+       ACPI_OBJECT *res;
+       struct enum_callback_arg argset;
+
+       dod_buf.Length = ACPI_ALLOCATE_BUFFER;
+       dod_buf.Pointer = NULL;
+       status = AcpiEvaluateObject(handle, "_DOD", NULL, &dod_buf);
+       if (ACPI_FAILURE(status)) {
+               if (status != AE_NOT_FOUND)
+                       kprintf("can't evaluate %s._DOD - %s\n",
+                              acpi_name(handle), AcpiFormatException(status));
+               argset.count = -1;
+               goto out;
+       }
+       res = (ACPI_OBJECT *)dod_buf.Pointer;
+       if (!ACPI_PKG_VALID(res, 1)) {
+               kprintf("evaluation of %s._DOD makes no sense\n",
+                      acpi_name(handle));
+               argset.count = -1;
+               goto out;
+       }
+       if (callback == NULL) {
+               argset.count = res->Package.Count;
+               goto out;
+       }
+       argset.callback = callback;
+       argset.context  = context;
+       argset.dod_pkg  = res;
+       argset.count    = 0;
+       status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
+           vid_enum_outputs_subr, &argset, NULL);
+       if (ACPI_FAILURE(status))
+               kprintf("failed walking down %s - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+out:
+       if (dod_buf.Pointer != NULL)
+               AcpiOsFree(dod_buf.Pointer);
+       return (argset.count);
+}
+
+static int
+vo_get_brightness_levels(ACPI_HANDLE handle, int **levelp)
+{
+       ACPI_STATUS status;
+       ACPI_BUFFER bcl_buf;
+       ACPI_OBJECT *res;
+       int num, i, n, *levels;
+
+       num = 0;
+       bcl_buf.Length = ACPI_ALLOCATE_BUFFER;
+       bcl_buf.Pointer = NULL;
+       status = AcpiEvaluateObject(handle, "_BCL", NULL, &bcl_buf);
+       if (ACPI_FAILURE(status)) {
+               if (status != AE_NOT_FOUND)
+                       kprintf("can't evaluate %s._BCL - %s\n",
+                              acpi_name(handle), AcpiFormatException(status));
+               num = -1;
+               goto out;
+       }
+       res = (ACPI_OBJECT *)bcl_buf.Pointer;
+       if (!ACPI_PKG_VALID(res, 2)) {
+               kprintf("evaluation of %s._BCL makes no sense\n",
+                      acpi_name(handle));
+               num = -1;
+               goto out;
+       }
+       num = res->Package.Count;
+       if (levelp == NULL)
+               goto out;
+       levels = AcpiOsAllocate(num * sizeof(*levels));
+       if (levels == NULL) {
+               num = -1;
+               goto out;
+       }
+       for (i = 0, n = 0; i < num; i++)
+               if (acpi_PkgInt32(res, i, &levels[n]) == 0)
+                       n++;
+       if (n < 2) {
+               num = -1;
+               AcpiOsFree(levels);
+       } else {
+               num = n;
+               *levelp = levels;
+       }
+out:
+       if (bcl_buf.Pointer != NULL)
+               AcpiOsFree(bcl_buf.Pointer);
+
+       return (num);
+}
+
+static void
+vo_set_brightness(ACPI_HANDLE handle, int level)
+{
+       ACPI_STATUS status;
+
+       status = acpi_SetInteger(handle, "_BCM", level);
+       if (ACPI_FAILURE(status))
+               kprintf("can't evaluate %s._BCM - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+}
+
+static UINT32
+vo_get_device_status(ACPI_HANDLE handle)
+{
+       UINT32 dcs;
+       ACPI_STATUS status;
+
+       dcs = 0;
+       status = acpi_GetInteger(handle, "_DCS", &dcs);
+       if (ACPI_FAILURE(status))
+               kprintf("can't evaluate %s._DCS - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+
+       return (dcs);
+}
+
+static UINT32
+vo_get_graphics_state(ACPI_HANDLE handle)
+{
+       UINT32 dgs;
+       ACPI_STATUS status;
+
+       dgs = 0;
+       status = acpi_GetInteger(handle, "_DGS", &dgs);
+       if (ACPI_FAILURE(status))
+               kprintf("can't evaluate %s._DGS - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+
+       return (dgs);
+}
+
+static void
+vo_set_device_state(ACPI_HANDLE handle, UINT32 state)
+{
+       ACPI_STATUS status;
+
+       status = acpi_SetInteger(handle, "_DSS", state);
+       if (ACPI_FAILURE(status))
+               kprintf("can't evaluate %s._DSS - %s\n",
+                      acpi_name(handle), AcpiFormatException(status));
+}