xref: /illumos-gate/usr/src/uts/i86pc/io/acpi_drv/acpi_video.c (revision 07a48826732249fcd3aa8dd53c8389595e9f1fbc)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Solaris x86 Generic ACPI Video Extensions Hotkey driver
28  */
29 #include <sys/hotkey_drv.h>
30 
31 /*
32  * Vendor specific hotkey support list
33  * 	1. Toshiba: acpi_toshiba
34  */
35 struct vendor_hotkey_drv vendor_hotkey_drv_list[] = {
36 /* vendor,	module name,		enable? */
37 {"Toshiba",	"acpi_toshiba",		B_TRUE},
38 /* Terminator */
39 {NULL,		NULL,			B_FALSE}
40 };
41 
42 enum vga_output_type {
43 	OUTPUT_OTHER,
44 	OUTPUT_CRT,
45 	OUTPUT_TV,
46 	OUTPUT_DVI,
47 	OUTPUT_LCD
48 };
49 
50 struct acpi_video_output {
51 	struct acpi_drv_dev dev;
52 	uint32_t			adr;
53 	enum vga_output_type		type;
54 	struct acpi_video_output	*next;
55 };
56 
57 struct acpi_video_brightness {
58 	struct acpi_drv_dev dev;
59 	uint32_t			adr;
60 	uint32_t			nlevel;
61 	int				*levels;
62 	int				cur_level;
63 	uint32_t			cur_level_index;
64 	uint32_t			output_index;
65 	struct acpi_video_brightness	*next;
66 };
67 
68 struct acpi_video_switch {
69 	struct acpi_drv_dev		dev;
70 	struct acpi_video_switch	*next;
71 };
72 
73 /* ACPI video extension hotkey for video switch and brightness control */
74 static struct acpi_video {
75 	struct acpi_video_output	*vid_outputs;
76 	uint32_t			total_outputs;
77 	struct acpi_video_brightness	*vid_brightness;
78 	uint32_t			total_brightness;
79 	struct acpi_video_switch	*vid_switch;
80 	uint32_t			total_switch;
81 } acpi_video_hotkey;
82 
83 int hotkey_drv_debug = 0;
84 
85 #define	ACPI_METHOD_DOS			"_DOS"
86 #define	ACPI_METHOD_DOD			"_DOD"
87 
88 #define	ACPI_DEVNAME_CRT		"CRT"
89 #define	ACPI_DEVNAME_LCD		"LCD"
90 #define	ACPI_DEVNAME_TV			"TV"
91 #define	ACPI_METHOD_ADR			"_ADR"
92 #define	ACPI_METHOD_DDC			"_DDC"
93 #define	ACPI_METHOD_DCS			"_DCS"
94 #define	ACPI_METHOD_DGS			"_DGS"
95 #define	ACPI_METHOD_DSS			"_DSS"
96 
97 #define	VIDEO_NOTIFY_SWITCH		0x80
98 #define	VIDEO_NOTIFY_SWITCH_STATUS	0x81
99 #define	VIDEO_NOTIFY_SWITCH_CYCLE	0x82
100 #define	VIDEO_NOTIFY_SWITCH_NEXT	0x83
101 #define	VIDEO_NOTIFY_SWITCH_PREV	0x84
102 
103 #define	VIDEO_NOTIFY_BRIGHTNESS_CYCLE	0x85
104 #define	VIDEO_NOTIFY_BRIGHTNESS_INC	0x86
105 #define	VIDEO_NOTIFY_BRIGHTNESS_DEC	0x87
106 #define	VIDEO_NOTIFY_BRIGHTNESS_ZERO	0x88
107 
108 /* Output device status */
109 #define	ACPI_DRV_DCS_CONNECTOR_EXIST	(1 << 0)
110 #define	ACPI_DRV_DCS_ACTIVE		(1 << 1)
111 #define	ACPI_DRV_DCS_READY		(1 << 2)
112 #define	ACPI_DRV_DCS_FUNCTIONAL		(1 << 3)
113 #define	ACPI_DRV_DCS_ATTACHED		(1 << 4)
114 
115 /* _DOS default value is 1 */
116 /* _DOS bit 1:0 */
117 #define	VIDEO_POLICY_SWITCH_OS		0x0
118 #define	VIDEO_POLICY_SWITCH_BIOS	0x1
119 #define	VIDEO_POLICY_SWITCH_LOCKED	0x2
120 #define	VIDEO_POLICY_SWITCH_OS_EVENT	0x3
121 
122 /* _DOS bit 2 */
123 #define	VIDEO_POLICY_BRIGHTNESS_OS	0x4
124 #define	VIDEO_POLICY_BRIGHTNESS_BIOS	0x0
125 
126 /* Set _DOS for video control policy */
127 static void
128 acpi_video_set_dos(struct acpi_video *vidp, uint32_t policy)
129 {
130 	struct acpi_video_switch *vidsp;
131 	ACPI_STATUS status;
132 	ACPI_OBJECT obj;
133 	ACPI_OBJECT_LIST objlist;
134 
135 	obj.Type = ACPI_TYPE_INTEGER;
136 	obj.Integer.Value = policy;
137 	objlist.Count = 1;
138 	objlist.Pointer = &obj;
139 
140 	vidsp = vidp->vid_switch;
141 	while (vidsp != NULL) {
142 		status = AcpiEvaluateObject(vidsp->dev.hdl, ACPI_METHOD_DOS,
143 		    &objlist, NULL);
144 		if (ACPI_FAILURE(status))
145 			cmn_err(CE_WARN, "!acpi_video_set_dos failed.");
146 		vidsp = vidsp->next;
147 	}
148 }
149 
150 /*
151  * Get the current brightness level and index.
152  */
153 static int
154 acpi_video_brightness_get(struct acpi_video_brightness *vidbp)
155 {
156 	int i;
157 
158 	if (acpica_eval_int(vidbp->dev.hdl, "_BQC", &vidbp->cur_level)
159 	    != AE_OK) {
160 		vidbp->cur_level = 0;
161 		return (ACPI_DRV_ERR);
162 	}
163 
164 	for (i = 0; i < vidbp->nlevel; i++) {
165 		if (vidbp->levels[i] == vidbp->cur_level) {
166 			vidbp->cur_level_index = i;
167 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
168 				cmn_err(CE_NOTE, "!acpi_video_brightness_get():"
169 				    " cur_level = %d, cur_level_index = %d\n",
170 				    vidbp->cur_level, i);
171 			}
172 			break;
173 		}
174 	}
175 
176 	return (ACPI_DRV_OK);
177 }
178 
179 static int
180 acpi_video_brightness_set(struct acpi_video_brightness *vidbp, uint32_t level)
181 {
182 	if (acpi_drv_set_int(vidbp->dev.hdl, "_BCM", vidbp->levels[level])
183 	    != AE_OK) {
184 		return (ACPI_DRV_ERR);
185 	}
186 
187 	vidbp->cur_level = vidbp->levels[level];
188 	vidbp->cur_level_index = level;
189 
190 	return (ACPI_DRV_OK);
191 }
192 
193 void
194 hotkey_drv_gen_sysevent(dev_info_t *dip, char *event)
195 {
196 	int err;
197 
198 	/* Generate/log EC_ACPIEV sysevent */
199 	err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_ACPIEV,
200 	    event, NULL, NULL, DDI_NOSLEEP);
201 
202 	if (err != DDI_SUCCESS) {
203 		cmn_err(CE_WARN,
204 		    "!failed to log hotkey sysevent, err code %x\n", err);
205 	}
206 }
207 
208 /*ARGSUSED*/
209 static void
210 acpi_video_switch_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
211 {
212 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
213 		cmn_err(CE_NOTE, "!acpi_video_switch_notify: got event 0x%x.\n",
214 		    notify);
215 	}
216 
217 	mutex_enter(acpi_hotkey.hotkey_lock);
218 	switch (notify) {
219 	case VIDEO_NOTIFY_SWITCH:
220 	case VIDEO_NOTIFY_SWITCH_CYCLE:
221 	case VIDEO_NOTIFY_SWITCH_NEXT:
222 	case VIDEO_NOTIFY_SWITCH_PREV:
223 		hotkey_drv_gen_sysevent(acpi_hotkey.dip,
224 		    ESC_ACPIEV_DISPLAY_SWITCH);
225 		break;
226 
227 	case VIDEO_NOTIFY_SWITCH_STATUS:
228 		break;
229 
230 	default:
231 		if (hotkey_drv_debug) {
232 			cmn_err(CE_NOTE,
233 			    "!acpi_video_switch_notify: unknown event 0x%x.\n",
234 			    notify);
235 		}
236 	}
237 	mutex_exit(acpi_hotkey.hotkey_lock);
238 }
239 
240 /*ARGSUSED*/
241 static void
242 acpi_video_brightness_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
243 {
244 	struct acpi_video_brightness *vidbp = ctx;
245 
246 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
247 		cmn_err(CE_NOTE,
248 		    "!acpi_video_brightness_notify: got event 0x%x.\n",
249 		    notify);
250 	}
251 
252 	mutex_enter(acpi_hotkey.hotkey_lock);
253 	switch (notify) {
254 	case VIDEO_NOTIFY_BRIGHTNESS_CYCLE:
255 	case VIDEO_NOTIFY_BRIGHTNESS_INC:
256 		if (vidbp->cur_level_index < vidbp->nlevel - 1) {
257 			if (acpi_video_brightness_set(vidbp,
258 			    vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
259 				break;
260 			}
261 		}
262 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_UP, 0);
263 		break;
264 	case VIDEO_NOTIFY_BRIGHTNESS_DEC:
265 		if (vidbp->cur_level_index > 0) {
266 			if (acpi_video_brightness_set(vidbp,
267 			    vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
268 				break;
269 			}
270 		}
271 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
272 		    0);
273 		break;
274 	case VIDEO_NOTIFY_BRIGHTNESS_ZERO:
275 		if (acpi_video_brightness_set(vidbp, 0) != ACPI_DRV_OK) {
276 			break;
277 		}
278 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
279 		    0);
280 		break;
281 
282 	default:
283 		if (hotkey_drv_debug) {
284 			cmn_err(CE_NOTE, "!acpi_video_brightness_notify: "
285 			    "unknown event 0x%x.\n", notify);
286 		}
287 	}
288 	mutex_exit(acpi_hotkey.hotkey_lock);
289 }
290 
291 static int
292 acpi_video_notify_intall(struct acpi_video *vidp)
293 {
294 	ACPI_STATUS status;
295 	struct acpi_video_switch *vidsp;
296 	struct acpi_video_brightness *vidbp;
297 	int i;
298 
299 	/* bind video switch notify */
300 	vidsp = vidp->vid_switch;
301 	for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
302 		status = AcpiInstallNotifyHandler(vidsp->dev.hdl,
303 		    ACPI_DEVICE_NOTIFY, acpi_video_switch_notify, vidsp);
304 		if (ACPI_FAILURE(status)) {
305 			cmn_err(CE_WARN,
306 			    "!vids handler install failed = %d, vids = %p.",
307 			    status, (void *) vidsp);
308 		}
309 		vidsp = vidsp->next;
310 	}
311 
312 	/* bind brightness control notify */
313 	vidbp = vidp->vid_brightness;
314 	for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
315 		status = AcpiInstallNotifyHandler(vidbp->dev.hdl,
316 		    ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify, vidbp);
317 		if (ACPI_FAILURE(status)) {
318 			cmn_err(CE_WARN,
319 			    "!brightness handler install failed = %x, "
320 			    "brightness = %p.", status, (void *) vidbp);
321 		}
322 		vidbp = vidbp->next;
323 	}
324 
325 	return (ACPI_DRV_OK);
326 }
327 
328 static int
329 acpi_video_notify_unintall(struct acpi_video *vidp)
330 {
331 	struct acpi_video_switch *vidsp;
332 	struct acpi_video_brightness *vidbp;
333 	int i;
334 
335 	/* unbind video switch notify */
336 	vidsp = vidp->vid_switch;
337 	for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
338 		(void) AcpiRemoveNotifyHandler(vidsp->dev.hdl,
339 		    ACPI_DEVICE_NOTIFY, acpi_video_switch_notify);
340 		vidsp = vidsp->next;
341 	}
342 
343 	/* unbind brightness control notify */
344 	vidbp = vidp->vid_brightness;
345 	for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
346 		(void) AcpiRemoveNotifyHandler(vidbp->dev.hdl,
347 		    ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify);
348 		vidbp = vidbp->next;
349 	}
350 
351 	return (ACPI_DRV_OK);
352 }
353 
354 static int
355 acpi_video_free(struct acpi_video *vidp)
356 {
357 	struct acpi_video_switch *vidsp;
358 	struct acpi_video_switch *vidsp_next;
359 	struct acpi_video_brightness *vidbp;
360 	struct acpi_video_brightness *vidbp_next;
361 	struct acpi_video_output *vidop;
362 	struct acpi_video_output *vidop_next;
363 
364 	/* free video switch objects */
365 	vidsp = vidp->vid_switch;
366 	while (vidsp != NULL) {
367 		vidsp_next = vidsp->next;
368 		kmem_free(vidsp, sizeof (struct acpi_video_switch));
369 		vidsp = vidsp_next;
370 	}
371 
372 	/* free video brightness control objects */
373 	vidbp = vidp->vid_brightness;
374 	while (vidbp != NULL) {
375 		vidbp_next = vidbp->next;
376 		kmem_free(vidbp, sizeof (struct acpi_video_brightness));
377 		vidbp = vidbp_next;
378 	}
379 
380 	/* free video output objects */
381 	vidop = vidp->vid_outputs;
382 	while (vidop != NULL) {
383 		vidop_next = vidop->next;
384 		kmem_free(vidop, sizeof (struct acpi_video_output));
385 		vidop = vidop_next;
386 	}
387 
388 	return (ACPI_DRV_OK);
389 }
390 
391 static int
392 acpi_video_fini(struct acpi_video *vidp)
393 {
394 	(void) acpi_video_notify_unintall(vidp);
395 
396 	return (acpi_video_free(vidp));
397 }
398 
399 static int
400 acpi_video_enum_output(ACPI_HANDLE hdl, struct acpi_video *vidp)
401 {
402 	int adr;
403 	struct acpi_video_brightness *vidbp;
404 	struct acpi_video_output *vidop;
405 	ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
406 	ACPI_OBJECT *objp;
407 
408 
409 	if (acpica_eval_int(hdl, "_ADR", &adr) != AE_OK)
410 		return (ACPI_DRV_ERR);
411 
412 	/* Allocate object */
413 	vidop = kmem_zalloc(sizeof (struct acpi_video_output), KM_SLEEP);
414 	vidop->dev.hdl = hdl;
415 	(void) acpi_drv_dev_init(&vidop->dev);
416 	vidop->adr = adr;
417 	vidop->type = adr;
418 	vidop->next = vidp->vid_outputs;
419 	vidp->vid_outputs = vidop;
420 
421 	if (ACPI_SUCCESS(AcpiEvaluateObjectTyped(hdl, "_BCL",
422 	    NULL, &buf, ACPI_TYPE_PACKAGE))) {
423 		int i, j, k, l, m, nlev, tmp;
424 
425 		vidbp = kmem_zalloc(sizeof (struct acpi_video_brightness),
426 		    KM_SLEEP);
427 		vidbp->dev = vidop->dev;
428 		vidop->adr = adr;
429 		vidbp->output_index = vidp->total_outputs;
430 		objp = buf.Pointer;
431 
432 		/*
433 		 * op->nlev will be needed to free op->levels.
434 		 */
435 		vidbp->nlevel = nlev = objp->Package.Count;
436 		vidbp->levels = kmem_zalloc(nlev * sizeof (uint32_t), KM_SLEEP);
437 
438 		/*
439 		 * Get all the supported brightness levels.
440 		 */
441 		for (i = 0; i < nlev; i++) {
442 			ACPI_OBJECT *o = &objp->Package.Elements[i];
443 			int lev = o->Integer.Value;
444 
445 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
446 				cmn_err(CE_NOTE, "!acpi_video_enum_output() "
447 				    "brlev=%d i=%d nlev=%d\n", lev, i, nlev);
448 			}
449 			if (o->Type != ACPI_TYPE_INTEGER) {
450 				continue;
451 			}
452 			vidbp->levels[i] = lev;
453 		}
454 
455 		/*
456 		 * Sort the brightness levels.
457 		 */
458 		for (j = 0; j < nlev; j++) {
459 			for (k = 0; k < nlev - 1; k++) {
460 				if (vidbp->levels[k] > vidbp->levels[k+1]) {
461 					tmp = vidbp->levels[k+1];
462 					vidbp->levels[k+1] = vidbp->levels[k];
463 					vidbp->levels[k] = tmp;
464 				}
465 			}
466 		}
467 
468 		/*
469 		 * The first two levels could be duplicated, so remove
470 		 * any duplicates.
471 		 */
472 		for (l = 0; l < nlev - 1; l++) {
473 			if (vidbp->levels[l] == vidbp->levels[l+1]) {
474 				for (m = l + 1; m < nlev - 1; m++) {
475 					vidbp->levels[m] = vidbp->levels[m+1];
476 				}
477 				nlev--;
478 			}
479 		}
480 
481 		vidbp->nlevel = nlev;
482 		(void) acpi_video_brightness_get(vidbp);
483 		vidbp->next = vidp->vid_brightness;
484 		vidp->vid_brightness = vidbp;
485 		vidp->total_brightness++;
486 
487 		AcpiOsFree(objp);
488 	}
489 
490 	vidp->total_outputs++;
491 
492 	return (ACPI_DRV_OK);
493 }
494 
495 /*ARGSUSED*/
496 static ACPI_STATUS
497 acpi_video_find_and_alloc(ACPI_HANDLE hdl, UINT32 nest, void *ctx,
498     void **rv)
499 {
500 	ACPI_HANDLE tmphdl;
501 	ACPI_STATUS err;
502 	ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
503 	struct acpi_video *vidp;
504 	struct acpi_video_switch *vidsp;
505 
506 	err = AcpiGetHandle(hdl, ACPI_METHOD_DOS, &tmphdl);
507 	if (err != AE_OK)
508 		return (AE_OK);
509 
510 	err = AcpiGetHandle(hdl, ACPI_METHOD_DOD, &tmphdl);
511 	if (err != AE_OK)
512 		return (AE_OK);
513 
514 	vidp = (struct acpi_video *)ctx;
515 	vidsp = kmem_zalloc(sizeof (struct acpi_video_switch), KM_SLEEP);
516 	vidsp->dev.hdl = hdl;
517 	(void) acpi_drv_dev_init(&vidsp->dev);
518 	vidsp->next = vidp->vid_switch;
519 	vidp->vid_switch = vidsp;
520 	vidp->total_switch++;
521 
522 	/*
523 	 * Enumerate the output devices.
524 	 */
525 	while (ACPI_SUCCESS(AcpiGetNextObject(ACPI_TYPE_DEVICE,
526 	    hdl, tmphdl, &tmphdl))) {
527 		(void) acpi_video_enum_output(tmphdl, vidp);
528 	}
529 
530 	if (!ACPI_FAILURE(AcpiGetName(hdl, ACPI_FULL_PATHNAME, &buf))) {
531 		if (buf.Pointer) {
532 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
533 				cmn_err(CE_NOTE,
534 				    "!acpi video switch hdl = 0x%p, path = %s.",
535 				    hdl, (char *)buf.Pointer);
536 			}
537 			AcpiOsFree(buf.Pointer);
538 		}
539 	}
540 
541 	return (AE_OK);
542 }
543 
544 int
545 hotkey_brightness_inc(hotkey_drv_t *htkp)
546 {
547 	struct acpi_video *vidp;
548 	struct acpi_video_brightness *vidbp;
549 
550 	vidp = (struct acpi_video *)htkp->acpi_video;
551 
552 	for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
553 		if (vidbp->cur_level_index < vidbp->nlevel - 1) {
554 			if (acpi_video_brightness_set(vidbp,
555 			    vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
556 				return (ACPI_DRV_ERR);
557 			}
558 		}
559 	}
560 	return (ACPI_DRV_OK);
561 }
562 
563 int
564 hotkey_brightness_dec(hotkey_drv_t *htkp)
565 {
566 	struct acpi_video *vidp;
567 	struct acpi_video_brightness *vidbp;
568 
569 	vidp = (struct acpi_video *)htkp->acpi_video;
570 
571 	for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
572 		if (vidbp->cur_level_index > 0) {
573 			if (acpi_video_brightness_set(vidbp,
574 			    vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
575 				return (ACPI_DRV_ERR);
576 			}
577 		}
578 	}
579 
580 	return (ACPI_DRV_OK);
581 }
582 
583 /*ARGSUSED*/
584 int
585 acpi_video_ioctl(void *p, int cmd, intptr_t arg, int mode, cred_t *cr,
586     int *rval)
587 {
588 	struct acpi_video *vidp = p;
589 	struct acpi_video_brightness *vidbp;
590 	int res = 0;
591 
592 	if (vidp == NULL)
593 		return (ENXIO);
594 
595 	vidbp = vidp->vid_brightness;
596 	if (vidbp == NULL)
597 		return (ENXIO);
598 
599 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
600 		cmn_err(CE_NOTE, "!acpi_video_ioctl cmd %d\n", cmd);
601 	}
602 
603 	switch (cmd) {
604 	case ACPI_DRV_IOC_INFO:
605 	{
606 		struct acpi_drv_output_info inf;
607 
608 		inf.adr = vidbp->adr;
609 		inf.nlev = vidbp->nlevel;
610 		if (copyout(&inf, (void *)arg, sizeof (inf))) {
611 			res = EFAULT;
612 		}
613 		break;
614 	}
615 
616 	case ACPI_DRV_IOC_LEVELS:
617 		if (copyout(vidbp->levels, (void *)arg,
618 		    sizeof (*vidbp->levels) * vidbp->nlevel)) {
619 			res = EFAULT;
620 		}
621 		break;
622 
623 	case ACPI_DRV_IOC_STATUS:
624 	{
625 		/*
626 		 * Need to get the current levels through ACPI first
627 		 * then go through array of levels to find index.
628 		 */
629 		struct acpi_drv_output_status status;
630 		int i;
631 
632 		status.state = 0;
633 		status.num_levels = vidbp->nlevel;
634 		status.cur_level = vidbp->cur_level;
635 		for (i = 0; i < vidbp->nlevel; i++) {
636 			if (vidbp->levels[i] == vidbp->cur_level) {
637 				status.cur_level_index = i;
638 				if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
639 					cmn_err(CE_NOTE, "!ACPI_DRV_IOC_STATUS "
640 					    "cur_level_index %d\n", i);
641 				}
642 				break;
643 			}
644 		}
645 		if (copyout(&status, (void *)arg, sizeof (status))) {
646 			res = EFAULT;
647 		}
648 		break;
649 	}
650 
651 	case ACPI_DRV_IOC_SET_BRIGHTNESS: {
652 		int level;
653 
654 		if (drv_priv(cr)) {
655 			res = EPERM;
656 			break;
657 		}
658 		if (copyin((void *)arg, &level, sizeof (level))) {
659 			res = EFAULT;
660 			break;
661 		}
662 		if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
663 			cmn_err(CE_NOTE,
664 			    "!acpi_video_ioctl: set BRIGHTNESS level=%d\n",
665 			    level);
666 		}
667 		if (acpi_video_brightness_set(vidbp, level) != ACPI_DRV_OK) {
668 			res = EFAULT;
669 		}
670 		break;
671 	}
672 
673 	default:
674 		res = EINVAL;
675 		break;
676 	}
677 
678 	return (res);
679 }
680 
681 /*ARGSUSED*/
682 int
683 acpi_drv_hotkey_ioctl(int cmd, intptr_t arg, int mode, cred_t *cr,
684     int *rval)
685 {
686 	hotkey_drv_t *htkp = &acpi_hotkey;
687 
688 	switch (htkp->hotkey_method) {
689 	case HOTKEY_METHOD_ACPI_VIDEO:
690 		return (acpi_video_ioctl(htkp->acpi_video, cmd, arg, mode,
691 		    cr, rval));
692 	case HOTKEY_METHOD_MISC:
693 	case HOTKEY_METHOD_VENDOR:
694 		return (htkp->vendor_ioctl(htkp, cmd, arg, mode, cr, rval));
695 	case HOTKEY_METHOD_NONE:
696 	default:
697 		return (ENXIO);
698 	}
699 }
700 
701 static int
702 hotkey_acpi_video_check(hotkey_drv_t *htkp)
703 {
704 	struct acpi_video *vidp;
705 
706 	vidp = &acpi_video_hotkey;
707 	bzero(vidp, sizeof (struct acpi_video));
708 	/* Find ACPI Video device handle */
709 	if (ACPI_FAILURE(AcpiGetDevices(NULL, acpi_video_find_and_alloc,
710 	    vidp, NULL))) {
711 		return (ACPI_DRV_ERR);
712 	}
713 
714 	htkp->acpi_video = vidp;
715 	if (htkp->hotkey_method == HOTKEY_METHOD_NONE) {
716 		if (acpi_video_notify_intall(vidp) != ACPI_DRV_OK) {
717 			(void) acpi_video_fini(vidp);
718 			htkp->acpi_video = NULL;
719 			return (ACPI_DRV_ERR);
720 		}
721 	}
722 	htkp->hotkey_method |= HOTKEY_METHOD_ACPI_VIDEO;
723 
724 	acpi_video_set_dos(vidp, VIDEO_POLICY_BRIGHTNESS_OS |
725 	    VIDEO_POLICY_SWITCH_OS);
726 
727 	return (ACPI_DRV_OK);
728 }
729 
730 int
731 hotkey_init(hotkey_drv_t *htkp)
732 {
733 	int i;
734 	int modid;
735 	modctl_t *modp;
736 
737 	htkp->modid = -1;
738 	/* Try to find vendor specific method */
739 	for (i = 0; vendor_hotkey_drv_list[i].module != NULL; i++) {
740 		if (!vendor_hotkey_drv_list[i].enable)
741 			continue;
742 
743 		if ((modid = modload("drv", vendor_hotkey_drv_list[i].module))
744 		    == -1) {
745 			continue;
746 		}
747 
748 		htkp->modid = modid;
749 		if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
750 			cmn_err(CE_NOTE, "!loaded %s specific method.\n",
751 			    vendor_hotkey_drv_list[i].vid);
752 		}
753 	}
754 
755 	/* Check availability of ACPI Video Extension method */
756 	if (htkp->hotkey_method == HOTKEY_METHOD_NONE ||
757 	    htkp->check_acpi_video) {
758 		if (hotkey_acpi_video_check(htkp) == ACPI_DRV_OK) {
759 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE)
760 				cmn_err(CE_NOTE, "!find ACPI video method.\n");
761 		} else
762 			goto fail;
763 	}
764 
765 	if (htkp->modid != -1) {
766 		modp = mod_hold_by_id(htkp->modid);
767 		mutex_enter(&mod_lock);
768 		modp->mod_ref = 1;
769 		modp->mod_loadflags |= MOD_NOAUTOUNLOAD;
770 		mutex_exit(&mod_lock);
771 		mod_release_mod(modp);
772 	}
773 
774 	return (ACPI_DRV_OK);
775 
776 fail:
777 	if (htkp->vendor_fini != NULL)
778 		htkp->vendor_fini(htkp);
779 	if (htkp->modid != -1)
780 		(void) modunload(htkp->modid);
781 
782 	return (ACPI_DRV_ERR);
783 }
784 
785 
786 int
787 hotkey_fini(hotkey_drv_t *htkp)
788 {
789 	modctl_t *modp;
790 
791 	if (htkp->vendor_fini != NULL)
792 		htkp->vendor_fini(htkp);
793 	if (htkp->acpi_video != NULL)
794 		(void) acpi_video_fini(htkp->acpi_video);
795 	if (htkp->modid != -1) {
796 		modp = mod_hold_by_id(htkp->modid);
797 		mutex_enter(&mod_lock);
798 		modp->mod_ref = 0;
799 		modp->mod_loadflags &= ~MOD_NOAUTOUNLOAD;
800 		mutex_exit(&mod_lock);
801 		mod_release_mod(modp);
802 		(void) modunload(htkp->modid);
803 	}
804 
805 	return (ACPI_DRV_OK);
806 }
807