From 745566f21a5db56312e05bf1d10331fdad5662e3 Mon Sep 17 00:00:00 2001 From: Hasso Tepper Date: Thu, 28 Aug 2008 09:25:42 +0000 Subject: [PATCH] Add acpi_video(4) - a driver for ACPI video extensions. Obtained-from: FreeBSD with modifications --- sys/conf/files | 3 +- sys/dev/acpica5/Makefile | 4 +- sys/dev/acpica5/acpi_video/Makefile | 7 + sys/dev/acpica5/acpi_video/acpi_video.c | 921 ++++++++++++++++++++++++ 4 files changed, 932 insertions(+), 3 deletions(-) create mode 100644 sys/dev/acpica5/acpi_video/Makefile create mode 100644 sys/dev/acpica5/acpi_video/acpi_video.c diff --git a/sys/conf/files b/sys/conf/files index 06c228ff06..6d96cf4950 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -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 diff --git a/sys/dev/acpica5/Makefile b/sys/dev/acpica5/Makefile index 3934070923..958323d362 100644 --- a/sys/dev/acpica5/Makefile +++ b/sys/dev/acpica5/Makefile @@ -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 index 0000000000..c774706155 --- /dev/null +++ b/sys/dev/acpica5/acpi_video/Makefile @@ -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 diff --git a/sys/dev/acpica5/acpi_video/acpi_video.c b/sys/dev/acpica5/acpi_video/acpi_video.c new file mode 100644 index 0000000000..f9f8b0b721 --- /dev/null +++ b/sys/dev/acpica5/acpi_video/acpi_video.c @@ -0,0 +1,921 @@ +/*- + * Copyright (c) 2002-2003 Taku YAMAMOTO + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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)); +} -- 2.41.0