/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Copyright 2015 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
*/
/*
* Solaris x86 Generic ACPI Video Extensions Hotkey driver
*/
#include <sys/hotkey_drv.h>
#include <sys/smbios.h>
/*
* Vendor specific hotkey support list
* 1. Toshiba: acpi_toshiba
*/
struct vendor_hotkey_drv vendor_hotkey_drv_list[] = {
/* vendor, module name, enable? */
{"Toshiba", "acpi_toshiba", B_TRUE},
/* Terminator */
{NULL, NULL, B_FALSE}
};
enum vga_output_type {
OUTPUT_OTHER,
OUTPUT_CRT,
OUTPUT_TV,
OUTPUT_DVI,
OUTPUT_LCD
};
struct acpi_video_output {
struct acpi_drv_dev dev;
uint32_t adr;
enum vga_output_type type;
struct acpi_video_output *next;
};
struct acpi_video_brightness {
struct acpi_drv_dev dev;
uint32_t adr;
uint32_t nlevel;
int *levels;
int cur_level;
uint32_t cur_level_index;
uint32_t output_index;
struct acpi_video_brightness *next;
};
struct acpi_video_switch {
struct acpi_drv_dev dev;
struct acpi_video_switch *next;
};
/* ACPI video extension hotkey for video switch and brightness control */
static struct acpi_video {
struct acpi_video_output *vid_outputs;
uint32_t total_outputs;
struct acpi_video_brightness *vid_brightness;
uint32_t total_brightness;
struct acpi_video_switch *vid_switch;
uint32_t total_switch;
} acpi_video_hotkey;
int hotkey_drv_debug = 0;
static struct acpi_video_smbios_info {
char *manufacturer;
char *product;
} acpi_brightness_get_blacklist[] = {
{ /* Dell AdamoXPS laptop */
"Dell Inc.",
"Adamo XPS"
},
{ /* termination entry */
NULL,
NULL
}
};
/*
* -1 = check acpi_brightness_get_blacklist[].
* 0 = enable brightness get.
* 1 = disable brightness get.
*/
int acpi_brightness_get_disable = -1;
#define ACPI_METHOD_DOS "_DOS"
#define ACPI_METHOD_DOD "_DOD"
#define ACPI_DEVNAME_CRT "CRT"
#define ACPI_DEVNAME_LCD "LCD"
#define ACPI_DEVNAME_TV "TV"
#define ACPI_METHOD_ADR "_ADR"
#define ACPI_METHOD_DDC "_DDC"
#define ACPI_METHOD_DCS "_DCS"
#define ACPI_METHOD_DGS "_DGS"
#define ACPI_METHOD_DSS "_DSS"
#define VIDEO_NOTIFY_SWITCH 0x80
#define VIDEO_NOTIFY_SWITCH_STATUS 0x81
#define VIDEO_NOTIFY_SWITCH_CYCLE 0x82
#define VIDEO_NOTIFY_SWITCH_NEXT 0x83
#define VIDEO_NOTIFY_SWITCH_PREV 0x84
#define VIDEO_NOTIFY_BRIGHTNESS_CYCLE 0x85
#define VIDEO_NOTIFY_BRIGHTNESS_INC 0x86
#define VIDEO_NOTIFY_BRIGHTNESS_DEC 0x87
#define VIDEO_NOTIFY_BRIGHTNESS_ZERO 0x88
/* Output device status */
#define ACPI_DRV_DCS_CONNECTOR_EXIST (1 << 0)
#define ACPI_DRV_DCS_ACTIVE (1 << 1)
#define ACPI_DRV_DCS_READY (1 << 2)
#define ACPI_DRV_DCS_FUNCTIONAL (1 << 3)
#define ACPI_DRV_DCS_ATTACHED (1 << 4)
/* _DOS default value is 1 */
/* _DOS bit 1:0 */
#define VIDEO_POLICY_SWITCH_OS 0x0
#define VIDEO_POLICY_SWITCH_BIOS 0x1
#define VIDEO_POLICY_SWITCH_LOCKED 0x2
#define VIDEO_POLICY_SWITCH_OS_EVENT 0x3
/* _DOS bit 2 */
#define VIDEO_POLICY_BRIGHTNESS_OS 0x4
#define VIDEO_POLICY_BRIGHTNESS_BIOS 0x0
/* Set _DOS for video control policy */
static void
acpi_video_set_dos(struct acpi_video *vidp, uint32_t policy)
{
struct acpi_video_switch *vidsp;
ACPI_STATUS status;
ACPI_OBJECT obj;
ACPI_OBJECT_LIST objlist;
obj.Type = ACPI_TYPE_INTEGER;
obj.Integer.Value = policy;
objlist.Count = 1;
objlist.Pointer = &obj;
vidsp = vidp->vid_switch;
while (vidsp != NULL) {
status = AcpiEvaluateObject(vidsp->dev.hdl, ACPI_METHOD_DOS,
&objlist, NULL);
if (ACPI_FAILURE(status))
cmn_err(CE_WARN, "!acpi_video_set_dos failed.");
vidsp = vidsp->next;
}
}
/*
* Get the current brightness level and index.
*/
static int
acpi_video_brightness_get(struct acpi_video_brightness *vidbp)
{
int i;
if (acpi_brightness_get_disable) {
/* simply initialize current brightness to the highest level */
vidbp->cur_level_index = vidbp->nlevel - 1;
vidbp->cur_level = vidbp->levels[vidbp->cur_level_index];
return (ACPI_DRV_OK);
}
if (acpica_eval_int(vidbp->dev.hdl, "_BQC", &vidbp->cur_level)
!= AE_OK) {
vidbp->cur_level = 0;
return (ACPI_DRV_ERR);
}
for (i = 0; i < vidbp->nlevel; i++) {
if (vidbp->levels[i] == vidbp->cur_level) {
vidbp->cur_level_index = i;
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!acpi_video_brightness_get():"
" cur_level = %d, cur_level_index = %d\n",
vidbp->cur_level, i);
}
break;
}
}
return (ACPI_DRV_OK);
}
static int
acpi_video_brightness_set(struct acpi_video_brightness *vidbp, uint32_t level)
{
if (acpi_drv_set_int(vidbp->dev.hdl, "_BCM", vidbp->levels[level])
!= AE_OK) {
return (ACPI_DRV_ERR);
}
vidbp->cur_level = vidbp->levels[level];
vidbp->cur_level_index = level;
return (ACPI_DRV_OK);
}
void
hotkey_drv_gen_sysevent(dev_info_t *dip, char *event)
{
int err;
/* Generate/log EC_ACPIEV sysevent */
err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_ACPIEV,
event, NULL, NULL, DDI_NOSLEEP);
if (err != DDI_SUCCESS) {
cmn_err(CE_WARN,
"!failed to log hotkey sysevent, err code %x\n", err);
}
}
/*ARGSUSED*/
static void
acpi_video_switch_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
{
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!acpi_video_switch_notify: got event 0x%x.\n",
notify);
}
mutex_enter(acpi_hotkey.hotkey_lock);
switch (notify) {
case VIDEO_NOTIFY_SWITCH:
case VIDEO_NOTIFY_SWITCH_CYCLE:
case VIDEO_NOTIFY_SWITCH_NEXT:
case VIDEO_NOTIFY_SWITCH_PREV:
hotkey_drv_gen_sysevent(acpi_hotkey.dip,
ESC_ACPIEV_DISPLAY_SWITCH);
break;
case VIDEO_NOTIFY_SWITCH_STATUS:
break;
default:
if (hotkey_drv_debug) {
cmn_err(CE_NOTE,
"!acpi_video_switch_notify: unknown event 0x%x.\n",
notify);
}
}
mutex_exit(acpi_hotkey.hotkey_lock);
}
/*ARGSUSED*/
static void
acpi_video_brightness_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
{
struct acpi_video_brightness *vidbp = ctx;
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE,
"!acpi_video_brightness_notify: got event 0x%x.\n",
notify);
}
mutex_enter(acpi_hotkey.hotkey_lock);
switch (notify) {
case VIDEO_NOTIFY_BRIGHTNESS_CYCLE:
case VIDEO_NOTIFY_BRIGHTNESS_INC:
if (vidbp->cur_level_index < vidbp->nlevel - 1) {
if (acpi_video_brightness_set(vidbp,
vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
break;
}
}
acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_UP, 0);
break;
case VIDEO_NOTIFY_BRIGHTNESS_DEC:
if (vidbp->cur_level_index > 0) {
if (acpi_video_brightness_set(vidbp,
vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
break;
}
}
acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
0);
break;
case VIDEO_NOTIFY_BRIGHTNESS_ZERO:
if (acpi_video_brightness_set(vidbp, 0) != ACPI_DRV_OK) {
break;
}
acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
0);
break;
default:
if (hotkey_drv_debug) {
cmn_err(CE_NOTE, "!acpi_video_brightness_notify: "
"unknown event 0x%x.\n", notify);
}
}
mutex_exit(acpi_hotkey.hotkey_lock);
}
static int
acpi_video_notify_intall(struct acpi_video *vidp)
{
ACPI_STATUS status;
struct acpi_video_switch *vidsp;
struct acpi_video_brightness *vidbp;
int i;
/* bind video switch notify */
vidsp = vidp->vid_switch;
for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
status = AcpiInstallNotifyHandler(vidsp->dev.hdl,
ACPI_DEVICE_NOTIFY, acpi_video_switch_notify, vidsp);
if (ACPI_FAILURE(status)) {
cmn_err(CE_WARN,
"!vids handler install failed = %d, vids = %p.",
status, (void *) vidsp);
}
vidsp = vidsp->next;
}
/* bind brightness control notify */
vidbp = vidp->vid_brightness;
for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
status = AcpiInstallNotifyHandler(vidbp->dev.hdl,
ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify, vidbp);
if (ACPI_FAILURE(status)) {
cmn_err(CE_WARN,
"!brightness handler install failed = %x, "
"brightness = %p.", status, (void *) vidbp);
}
vidbp = vidbp->next;
}
return (ACPI_DRV_OK);
}
static int
acpi_video_notify_unintall(struct acpi_video *vidp)
{
struct acpi_video_switch *vidsp;
struct acpi_video_brightness *vidbp;
int i;
/* unbind video switch notify */
vidsp = vidp->vid_switch;
for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
(void) AcpiRemoveNotifyHandler(vidsp->dev.hdl,
ACPI_DEVICE_NOTIFY, acpi_video_switch_notify);
vidsp = vidsp->next;
}
/* unbind brightness control notify */
vidbp = vidp->vid_brightness;
for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
(void) AcpiRemoveNotifyHandler(vidbp->dev.hdl,
ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify);
vidbp = vidbp->next;
}
return (ACPI_DRV_OK);
}
static int
acpi_video_free(struct acpi_video *vidp)
{
struct acpi_video_switch *vidsp;
struct acpi_video_switch *vidsp_next;
struct acpi_video_brightness *vidbp;
struct acpi_video_brightness *vidbp_next;
struct acpi_video_output *vidop;
struct acpi_video_output *vidop_next;
/* free video switch objects */
vidsp = vidp->vid_switch;
while (vidsp != NULL) {
vidsp_next = vidsp->next;
kmem_free(vidsp, sizeof (struct acpi_video_switch));
vidsp = vidsp_next;
}
/* free video brightness control objects */
vidbp = vidp->vid_brightness;
while (vidbp != NULL) {
vidbp_next = vidbp->next;
kmem_free(vidbp, sizeof (struct acpi_video_brightness));
vidbp = vidbp_next;
}
/* free video output objects */
vidop = vidp->vid_outputs;
while (vidop != NULL) {
vidop_next = vidop->next;
kmem_free(vidop, sizeof (struct acpi_video_output));
vidop = vidop_next;
}
return (ACPI_DRV_OK);
}
static int
acpi_video_fini(struct acpi_video *vidp)
{
(void) acpi_video_notify_unintall(vidp);
return (acpi_video_free(vidp));
}
static int
acpi_video_enum_output(ACPI_HANDLE hdl, struct acpi_video *vidp)
{
int adr;
struct acpi_video_brightness *vidbp;
struct acpi_video_output *vidop;
ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
ACPI_OBJECT *objp;
if (acpica_eval_int(hdl, "_ADR", &adr) != AE_OK)
return (ACPI_DRV_ERR);
/* Allocate object */
vidop = kmem_zalloc(sizeof (struct acpi_video_output), KM_SLEEP);
vidop->dev.hdl = hdl;
(void) acpi_drv_dev_init(&vidop->dev);
vidop->adr = adr;
vidop->type = adr;
vidop->next = vidp->vid_outputs;
vidp->vid_outputs = vidop;
if (ACPI_SUCCESS(AcpiEvaluateObjectTyped(hdl, "_BCL",
NULL, &buf, ACPI_TYPE_PACKAGE))) {
int i, j, k, l, m, nlev, tmp;
vidbp = kmem_zalloc(sizeof (struct acpi_video_brightness),
KM_SLEEP);
vidbp->dev = vidop->dev;
vidop->adr = adr;
vidbp->output_index = vidp->total_outputs;
objp = buf.Pointer;
/*
* op->nlev will be needed to free op->levels.
*/
vidbp->nlevel = nlev = objp->Package.Count;
vidbp->levels = kmem_zalloc(nlev * sizeof (uint32_t), KM_SLEEP);
/*
* Get all the supported brightness levels.
*/
for (i = 0; i < nlev; i++) {
ACPI_OBJECT *o = &objp->Package.Elements[i];
int lev = o->Integer.Value;
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!acpi_video_enum_output() "
"brlev=%d i=%d nlev=%d\n", lev, i, nlev);
}
if (o->Type != ACPI_TYPE_INTEGER) {
continue;
}
vidbp->levels[i] = lev;
}
/*
* Sort the brightness levels.
*/
for (j = 0; j < nlev; j++) {
for (k = 0; k < nlev - 1; k++) {
if (vidbp->levels[k] > vidbp->levels[k+1]) {
tmp = vidbp->levels[k+1];
vidbp->levels[k+1] = vidbp->levels[k];
vidbp->levels[k] = tmp;
}
}
}
/*
* The first two levels could be duplicated, so remove
* any duplicates.
*/
for (l = 0; l < nlev - 1; l++) {
if (vidbp->levels[l] == vidbp->levels[l+1]) {
for (m = l + 1; m < nlev - 1; m++) {
vidbp->levels[m] = vidbp->levels[m+1];
}
nlev--;
}
}
vidbp->nlevel = nlev;
(void) acpi_video_brightness_get(vidbp);
vidbp->next = vidp->vid_brightness;
vidp->vid_brightness = vidbp;
vidp->total_brightness++;
AcpiOsFree(objp);
}
vidp->total_outputs++;
return (ACPI_DRV_OK);
}
/*ARGSUSED*/
static ACPI_STATUS
acpi_video_find_and_alloc(ACPI_HANDLE hdl, UINT32 nest, void *ctx,
void **rv)
{
ACPI_HANDLE tmphdl;
ACPI_STATUS err;
ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
struct acpi_video *vidp;
struct acpi_video_switch *vidsp;
err = AcpiGetHandle(hdl, ACPI_METHOD_DOS, &tmphdl);
if (err != AE_OK)
return (AE_OK);
err = AcpiGetHandle(hdl, ACPI_METHOD_DOD, &tmphdl);
if (err != AE_OK)
return (AE_OK);
vidp = (struct acpi_video *)ctx;
vidsp = kmem_zalloc(sizeof (struct acpi_video_switch), KM_SLEEP);
vidsp->dev.hdl = hdl;
(void) acpi_drv_dev_init(&vidsp->dev);
vidsp->next = vidp->vid_switch;
vidp->vid_switch = vidsp;
vidp->total_switch++;
/*
* Enumerate the output devices.
*/
while (ACPI_SUCCESS(AcpiGetNextObject(ACPI_TYPE_DEVICE,
hdl, tmphdl, &tmphdl))) {
(void) acpi_video_enum_output(tmphdl, vidp);
}
if (!ACPI_FAILURE(AcpiGetName(hdl, ACPI_FULL_PATHNAME, &buf))) {
if (buf.Pointer) {
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE,
"!acpi video switch hdl = 0x%p, path = %s.",
hdl, (char *)buf.Pointer);
}
AcpiOsFree(buf.Pointer);
}
}
return (AE_OK);
}
int
hotkey_brightness_inc(hotkey_drv_t *htkp)
{
struct acpi_video *vidp;
struct acpi_video_brightness *vidbp;
vidp = (struct acpi_video *)htkp->acpi_video;
for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
if (vidbp->cur_level_index < vidbp->nlevel - 1) {
if (acpi_video_brightness_set(vidbp,
vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
return (ACPI_DRV_ERR);
}
}
}
return (ACPI_DRV_OK);
}
int
hotkey_brightness_dec(hotkey_drv_t *htkp)
{
struct acpi_video *vidp;
struct acpi_video_brightness *vidbp;
vidp = (struct acpi_video *)htkp->acpi_video;
for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
if (vidbp->cur_level_index > 0) {
if (acpi_video_brightness_set(vidbp,
vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
return (ACPI_DRV_ERR);
}
}
}
return (ACPI_DRV_OK);
}
/*ARGSUSED*/
int
acpi_video_ioctl(void *p, int cmd, intptr_t arg, int mode, cred_t *cr,
int *rval)
{
struct acpi_video *vidp = p;
struct acpi_video_brightness *vidbp;
int res = 0;
if (vidp == NULL)
return (ENXIO);
vidbp = vidp->vid_brightness;
if (vidbp == NULL)
return (ENXIO);
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!acpi_video_ioctl cmd %d\n", cmd);
}
switch (cmd) {
case ACPI_DRV_IOC_INFO:
{
struct acpi_drv_output_info inf;
inf.adr = vidbp->adr;
inf.nlev = vidbp->nlevel;
if (copyout(&inf, (void *)arg, sizeof (inf))) {
res = EFAULT;
}
break;
}
case ACPI_DRV_IOC_LEVELS:
if (copyout(vidbp->levels, (void *)arg,
sizeof (*vidbp->levels) * vidbp->nlevel)) {
res = EFAULT;
}
break;
case ACPI_DRV_IOC_STATUS:
{
/*
* Need to get the current levels through ACPI first
* then go through array of levels to find index.
*/
struct acpi_drv_output_status status;
int i;
status.state = 0;
status.num_levels = vidbp->nlevel;
status.cur_level = vidbp->cur_level;
for (i = 0; i < vidbp->nlevel; i++) {
if (vidbp->levels[i] == vidbp->cur_level) {
status.cur_level_index = i;
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!ACPI_DRV_IOC_STATUS "
"cur_level_index %d\n", i);
}
break;
}
}
if (copyout(&status, (void *)arg, sizeof (status))) {
res = EFAULT;
}
break;
}
case ACPI_DRV_IOC_SET_BRIGHTNESS: {
int level;
if (drv_priv(cr)) {
res = EPERM;
break;
}
if (copyin((void *)arg, &level, sizeof (level))) {
res = EFAULT;
break;
}
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE,
"!acpi_video_ioctl: set BRIGHTNESS level=%d\n",
level);
}
if (acpi_video_brightness_set(vidbp, level) != ACPI_DRV_OK) {
res = EFAULT;
}
break;
}
default:
res = EINVAL;
break;
}
return (res);
}
/*ARGSUSED*/
int
acpi_drv_hotkey_ioctl(int cmd, intptr_t arg, int mode, cred_t *cr,
int *rval)
{
hotkey_drv_t *htkp = &acpi_hotkey;
switch (htkp->hotkey_method) {
case HOTKEY_METHOD_ACPI_VIDEO:
return (acpi_video_ioctl(htkp->acpi_video, cmd, arg, mode,
cr, rval));
case HOTKEY_METHOD_MISC:
case HOTKEY_METHOD_VENDOR:
return (htkp->vendor_ioctl(htkp, cmd, arg, mode, cr, rval));
case HOTKEY_METHOD_NONE:
default:
return (ENXIO);
}
}
static void
acpi_video_check_blacklist(void)
{
smbios_hdl_t *smhdl = NULL;
id_t smid;
smbios_system_t smsys;
smbios_info_t sminfo;
char *mfg, *product;
struct acpi_video_smbios_info *pblacklist;
acpi_brightness_get_disable = 0;
smhdl = smbios_open(NULL, SMB_VERSION, ksmbios_flags, NULL);
if (smhdl == NULL ||
((smid = smbios_info_system(smhdl, &smsys)) == SMB_ERR) ||
(smbios_info_common(smhdl, smid, &sminfo) == SMB_ERR)) {
goto done;
}
mfg = (char *)sminfo.smbi_manufacturer;
product = (char *)sminfo.smbi_product;
for (pblacklist = acpi_brightness_get_blacklist;
pblacklist->manufacturer != NULL; pblacklist++) {
if ((strcmp(mfg, pblacklist->manufacturer) == 0) &&
(strcmp(product, pblacklist->product) == 0)) {
acpi_brightness_get_disable = 1;
}
}
done:
if (smhdl != NULL)
smbios_close(smhdl);
}
static int
hotkey_acpi_video_check(hotkey_drv_t *htkp)
{
struct acpi_video *vidp;
vidp = &acpi_video_hotkey;
bzero(vidp, sizeof (struct acpi_video));
if (acpi_brightness_get_disable == -1)
acpi_video_check_blacklist();
/* Find ACPI Video device handle */
if (ACPI_FAILURE(AcpiGetDevices(NULL, acpi_video_find_and_alloc,
vidp, NULL))) {
return (ACPI_DRV_ERR);
}
htkp->acpi_video = vidp;
if (htkp->hotkey_method == HOTKEY_METHOD_NONE) {
if (acpi_video_notify_intall(vidp) != ACPI_DRV_OK) {
(void) acpi_video_fini(vidp);
htkp->acpi_video = NULL;
return (ACPI_DRV_ERR);
}
}
htkp->hotkey_method |= HOTKEY_METHOD_ACPI_VIDEO;
acpi_video_set_dos(vidp, VIDEO_POLICY_BRIGHTNESS_OS |
VIDEO_POLICY_SWITCH_OS);
return (ACPI_DRV_OK);
}
int
hotkey_init(hotkey_drv_t *htkp)
{
int i;
int modid;
modctl_t *modp;
htkp->modid = -1;
/* Try to find vendor specific method */
for (i = 0; vendor_hotkey_drv_list[i].module != NULL; i++) {
if (!vendor_hotkey_drv_list[i].enable)
continue;
if ((modid = modload("drv", vendor_hotkey_drv_list[i].module))
== -1) {
continue;
}
htkp->modid = modid;
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
cmn_err(CE_NOTE, "!loaded %s specific method.\n",
vendor_hotkey_drv_list[i].vid);
}
}
/* Check availability of ACPI Video Extension method */
if (htkp->hotkey_method == HOTKEY_METHOD_NONE ||
htkp->check_acpi_video) {
if (hotkey_acpi_video_check(htkp) == ACPI_DRV_OK) {
if (hotkey_drv_debug & HOTKEY_DBG_NOTICE)
cmn_err(CE_NOTE, "!find ACPI video method.\n");
} else
goto fail;
}
if (htkp->modid != -1) {
modp = mod_hold_by_id(htkp->modid);
mutex_enter(&mod_lock);
modp->mod_ref = 1;
modp->mod_loadflags |= MOD_NOAUTOUNLOAD;
mutex_exit(&mod_lock);
mod_release_mod(modp);
}
/* Create minor node for hotkey device. */
if (ddi_create_minor_node(htkp->dip, "hotkey", S_IFCHR,
MINOR_HOTKEY(0), DDI_PSEUDO, 0) == DDI_FAILURE) {
if (hotkey_drv_debug & HOTKEY_DBG_WARN)
cmn_err(CE_WARN, "hotkey: minor node create failed");
goto fail;
}
return (ACPI_DRV_OK);
fail:
if (htkp->vendor_fini != NULL)
htkp->vendor_fini(htkp);
if (htkp->modid != -1)
(void) modunload(htkp->modid);
return (ACPI_DRV_ERR);
}
int
hotkey_fini(hotkey_drv_t *htkp)
{
modctl_t *modp;
if (htkp->vendor_fini != NULL)
htkp->vendor_fini(htkp);
if (htkp->acpi_video != NULL)
(void) acpi_video_fini(htkp->acpi_video);
if (htkp->modid != -1) {
modp = mod_hold_by_id(htkp->modid);
mutex_enter(&mod_lock);
modp->mod_ref = 0;
modp->mod_loadflags &= ~MOD_NOAUTOUNLOAD;
mutex_exit(&mod_lock);
mod_release_mod(modp);
(void) modunload(htkp->modid);
}
return (ACPI_DRV_OK);
}