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