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