xref: /freebsd/sys/dev/acpi_support/acpi_panasonic.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
1 /*-
2  * Copyright (c) 2003 OGAWA Takaya <t-ogawa@triaez.kaisei.org>
3  * Copyright (c) 2004 TAKAHASHI Yoshihiro <nyan@FreeBSD.org>
4  * All rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include "opt_acpi.h"
33 #include <sys/param.h>
34 #include <sys/kernel.h>
35 #include <sys/malloc.h>
36 #include <sys/module.h>
37 #include <sys/bus.h>
38 #include <sys/power.h>
39 
40 #include <contrib/dev/acpica/acpi.h>
41 #include <dev/acpica/acpivar.h>
42 
43 #define _COMPONENT	ACPI_OEM
44 ACPI_MODULE_NAME("Panasonic")
45 
46 /* Debug */
47 #undef	ACPI_PANASONIC_DEBUG
48 
49 /* Operations */
50 #define	HKEY_SET	0
51 #define	HKEY_GET	1
52 
53 /* Functions */
54 #define	HKEY_REG_LCD_BRIGHTNESS_MAX_AC	0x02
55 #define	HKEY_REG_LCD_BRIGHTNESS_MIN_AC	0x03
56 #define	HKEY_REG_LCD_BRIGHTNESS_AC	0x04
57 #define	HKEY_REG_LCD_BRIGHTNESS_MAX_DC	0x05
58 #define	HKEY_REG_LCD_BRIGHTNESS_MIN_DC	0x06
59 #define	HKEY_REG_LCD_BRIGHTNESS_DC	0x07
60 #define	HKEY_REG_SOUND_MUTE		0x08
61 
62 /* Field definitions */
63 #define	HKEY_LCD_BRIGHTNESS_BITS	4
64 #define	HKEY_LCD_BRIGHTNESS_DIV		((1 << HKEY_LCD_BRIGHTNESS_BITS) - 1)
65 
66 struct acpi_panasonic_softc {
67 	device_t	dev;
68 	ACPI_HANDLE	handle;
69 
70 	struct sysctl_ctx_list	sysctl_ctx;
71 	struct sysctl_oid	*sysctl_tree;
72 
73 	eventhandler_tag	power_evh;
74 };
75 
76 /* Prototype for HKEY functions for getting/setting a value. */
77 typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *);
78 
79 static int	acpi_panasonic_probe(device_t dev);
80 static int	acpi_panasonic_attach(device_t dev);
81 static int	acpi_panasonic_detach(device_t dev);
82 static int	acpi_panasonic_shutdown(device_t dev);
83 static int	acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS);
84 static ACPI_INTEGER acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index);
85 static void	acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index,
86 		    ACPI_INTEGER val);
87 static int	acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc,
88 		    ACPI_HANDLE h, UINT32 *arg);
89 static void	acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc,
90 		    ACPI_HANDLE h, UINT32 key);
91 static void	acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify,
92 		    void *context);
93 static void	acpi_panasonic_power_profile(void *arg);
94 
95 static hkey_fn_t	hkey_lcd_brightness_max;
96 static hkey_fn_t	hkey_lcd_brightness_min;
97 static hkey_fn_t	hkey_lcd_brightness;
98 static hkey_fn_t	hkey_sound_mute;
99 ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras");
100 
101 /* Table of sysctl names and HKEY functions to call. */
102 static struct {
103 	char		*name;
104 	hkey_fn_t	*handler;
105 } sysctl_table[] = {
106 	/* name,		handler */
107 	{"lcd_brightness_max",	hkey_lcd_brightness_max},
108 	{"lcd_brightness_min",	hkey_lcd_brightness_min},
109 	{"lcd_brightness",	hkey_lcd_brightness},
110 	{"sound_mute",		hkey_sound_mute},
111 	{NULL, NULL}
112 };
113 
114 static device_method_t acpi_panasonic_methods[] = {
115 	DEVMETHOD(device_probe,		acpi_panasonic_probe),
116 	DEVMETHOD(device_attach,	acpi_panasonic_attach),
117 	DEVMETHOD(device_detach,	acpi_panasonic_detach),
118 	DEVMETHOD(device_shutdown,	acpi_panasonic_shutdown),
119 
120 	{0, 0}
121 };
122 
123 static driver_t acpi_panasonic_driver = {
124 	"acpi_panasonic",
125 	acpi_panasonic_methods,
126 	sizeof(struct acpi_panasonic_softc),
127 };
128 
129 static devclass_t acpi_panasonic_devclass;
130 
131 DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver,
132     acpi_panasonic_devclass, 0, 0);
133 MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1);
134 
135 static int
136 acpi_panasonic_probe(device_t dev)
137 {
138 	static char *mat_ids[] = { "MAT0019", NULL };
139 
140 	if (acpi_disabled("panasonic") ||
141 	    ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids) == NULL ||
142 	    device_get_unit(dev) != 0)
143 		return (ENXIO);
144 
145 	device_set_desc(dev, "Panasonic Notebook Hotkeys");
146 	return (0);
147 }
148 
149 static int
150 acpi_panasonic_attach(device_t dev)
151 {
152 	struct acpi_panasonic_softc *sc;
153 	struct acpi_softc *acpi_sc;
154 	ACPI_STATUS status;
155 	int i;
156 
157 	sc = device_get_softc(dev);
158 	sc->dev = dev;
159 	sc->handle = acpi_get_handle(dev);
160 
161 	acpi_sc = acpi_device_get_parent_softc(dev);
162 
163 	/* Build sysctl tree */
164 	sysctl_ctx_init(&sc->sysctl_ctx);
165 	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
166 	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
167 	    "panasonic", CTLFLAG_RD, 0, "");
168 	for (i = 0; sysctl_table[i].name != NULL; i++) {
169 		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
170 		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
171 		    sysctl_table[i].name,
172 		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
173 		    sc, i, acpi_panasonic_sysctl, "I", "");
174 	}
175 
176 #if 0
177 	/* Activate hotkeys */
178 	status = AcpiEvaluateObject(sc->handle, "", NULL, NULL);
179 	if (ACPI_FAILURE(status)) {
180 		device_printf(dev, "enable FN keys failed\n");
181 		sysctl_ctx_free(&sc->sysctl_ctx);
182 		return (ENXIO);
183 	}
184 #endif
185 
186 	/* Handle notifies */
187 	status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
188 	    acpi_panasonic_notify, sc);
189 	if (ACPI_FAILURE(status)) {
190 		device_printf(dev, "couldn't install notify handler - %s\n",
191 		    AcpiFormatException(status));
192 		sysctl_ctx_free(&sc->sysctl_ctx);
193 		return (ENXIO);
194 	}
195 
196 	/* Install power profile event handler */
197 	sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change,
198 	    acpi_panasonic_power_profile, sc->handle, 0);
199 
200 	return (0);
201 }
202 
203 static int
204 acpi_panasonic_detach(device_t dev)
205 {
206 	struct acpi_panasonic_softc *sc;
207 
208 	sc = device_get_softc(dev);
209 
210 	/* Remove power profile event handler */
211 	EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh);
212 
213 	/* Remove notify handler */
214 	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
215 	    acpi_panasonic_notify);
216 
217 	/* Free sysctl tree */
218 	sysctl_ctx_free(&sc->sysctl_ctx);
219 
220 	return (0);
221 }
222 
223 static int
224 acpi_panasonic_shutdown(device_t dev)
225 {
226 	struct acpi_panasonic_softc *sc;
227 	int mute;
228 
229 	/* Mute the main audio during reboot to prevent static burst to speaker. */
230 	sc = device_get_softc(dev);
231 	mute = 1;
232 	hkey_sound_mute(sc->handle, HKEY_SET, &mute);
233 	return (0);
234 }
235 
236 static int
237 acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS)
238 {
239 	struct acpi_panasonic_softc *sc;
240 	UINT32 arg;
241 	int function, error;
242 	hkey_fn_t *handler;
243 
244 	sc = (struct acpi_panasonic_softc *)oidp->oid_arg1;
245 	function = oidp->oid_arg2;
246 	handler = sysctl_table[function].handler;
247 
248 	/* Get the current value from the appropriate function. */
249 	ACPI_SERIAL_BEGIN(panasonic);
250 	error = handler(sc->handle, HKEY_GET, &arg);
251 	if (error != 0)
252 		goto out;
253 
254 	/* Send the current value to the user and return if no new value. */
255 	error = sysctl_handle_int(oidp, &arg, 0, req);
256 	if (error != 0 || req->newptr == NULL)
257 		goto out;
258 
259 	/* Set the new value via the appropriate function. */
260 	error = handler(sc->handle, HKEY_SET, &arg);
261 
262 out:
263 	ACPI_SERIAL_END(panasonic);
264 	return (error);
265 }
266 
267 static ACPI_INTEGER
268 acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index)
269 {
270 	ACPI_BUFFER buf;
271 	ACPI_OBJECT *res;
272 	ACPI_INTEGER ret;
273 
274 	ACPI_SERIAL_ASSERT(panasonic);
275 	ret = -1;
276 	buf.Length = ACPI_ALLOCATE_BUFFER;
277 	buf.Pointer = NULL;
278 	AcpiEvaluateObject(h, "SINF", NULL, &buf);
279 	res = (ACPI_OBJECT *)buf.Pointer;
280 	if (res->Type == ACPI_TYPE_PACKAGE)
281 		ret = res->Package.Elements[index].Integer.Value;
282 	AcpiOsFree(buf.Pointer);
283 
284 	return (ret);
285 }
286 
287 static void
288 acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, ACPI_INTEGER val)
289 {
290 	ACPI_OBJECT_LIST args;
291 	ACPI_OBJECT obj[2];
292 
293 	ACPI_SERIAL_ASSERT(panasonic);
294 	obj[0].Type = ACPI_TYPE_INTEGER;
295 	obj[0].Integer.Value = index;
296 	obj[1].Type = ACPI_TYPE_INTEGER;
297 	obj[1].Integer.Value = val;
298 	args.Count = 2;
299 	args.Pointer = obj;
300 	AcpiEvaluateObject(h, "SSET", &args, NULL);
301 }
302 
303 static int
304 hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val)
305 {
306 	int reg;
307 
308 	ACPI_SERIAL_ASSERT(panasonic);
309 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
310 	    HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC;
311 
312 	switch (op) {
313 	case HKEY_SET:
314 		return (EPERM);
315 		break;
316 	case HKEY_GET:
317 		*val = acpi_panasonic_sinf(h, reg);
318 		break;
319 	}
320 
321 	return (0);
322 }
323 
324 static int
325 hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val)
326 {
327 	int reg;
328 
329 	ACPI_SERIAL_ASSERT(panasonic);
330 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
331 	    HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC;
332 
333 	switch (op) {
334 	case HKEY_SET:
335 		return (EPERM);
336 		break;
337 	case HKEY_GET:
338 		*val = acpi_panasonic_sinf(h, reg);
339 		break;
340 	}
341 
342 	return (0);
343 }
344 
345 static int
346 hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val)
347 {
348 	int reg;
349 	UINT32 max, min;
350 
351 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
352 	    HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC;
353 
354 	ACPI_SERIAL_ASSERT(panasonic);
355 	switch (op) {
356 	case HKEY_SET:
357 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
358 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
359 		if (*val < min || *val > max)
360 			return (EINVAL);
361 		acpi_panasonic_sset(h, reg, *val);
362 		break;
363 	case HKEY_GET:
364 		*val = acpi_panasonic_sinf(h, reg);
365 		break;
366 	}
367 
368 	return (0);
369 }
370 
371 static int
372 hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val)
373 {
374 
375 	ACPI_SERIAL_ASSERT(panasonic);
376 	switch (op) {
377 	case HKEY_SET:
378 		if (*val != 0 && *val != 1)
379 			return (EINVAL);
380 		acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val);
381 		break;
382 	case HKEY_GET:
383 		*val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE);
384 		break;
385 	}
386 
387 	return (0);
388 }
389 
390 static int
391 acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
392     UINT32 *arg)
393 {
394 	ACPI_BUFFER buf;
395 	ACPI_OBJECT *res;
396 	ACPI_INTEGER val;
397 	int status;
398 
399 	ACPI_SERIAL_ASSERT(panasonic);
400 	status = ENXIO;
401 
402 	buf.Length = ACPI_ALLOCATE_BUFFER;
403 	buf.Pointer = NULL;
404 	AcpiEvaluateObject(h, "HINF", NULL, &buf);
405 	res = (ACPI_OBJECT *)buf.Pointer;
406 	if (res->Type != ACPI_TYPE_INTEGER) {
407 		device_printf(sc->dev, "HINF returned non-integer\n");
408 		goto end;
409 	}
410 	val = res->Integer.Value;
411 #ifdef ACPI_PANASONIC_DEBUG
412 	device_printf(sc->dev, "%s button Fn+F%d\n",
413 		      (val & 0x80) ? "Pressed" : "Released",
414 		      (int)(val & 0x7f));
415 #endif
416 	if ((val & 0x7f) > 0 && (val & 0x7f) < 11) {
417 		*arg = val;
418 		status = 0;
419 	}
420 end:
421 	if (buf.Pointer)
422 		AcpiOsFree(buf.Pointer);
423 
424 	return (status);
425 }
426 
427 static void
428 acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
429     UINT32 key)
430 {
431 	struct acpi_softc *acpi_sc;
432 	int arg, max, min;
433 
434 	acpi_sc = acpi_device_get_parent_softc(sc->dev);
435 
436 	ACPI_SERIAL_ASSERT(panasonic);
437 	switch (key) {
438 	case 1:
439 		/* Decrease LCD brightness. */
440 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
441 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
442 		hkey_lcd_brightness(h, HKEY_GET, &arg);
443 		arg -= max / HKEY_LCD_BRIGHTNESS_DIV;
444 		if (arg < min)
445 			arg = min;
446 		else if (arg > max)
447 			arg = max;
448 		hkey_lcd_brightness(h, HKEY_SET, &arg);
449 		break;
450 	case 2:
451 		/* Increase LCD brightness. */
452 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
453 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
454 		hkey_lcd_brightness(h, HKEY_GET, &arg);
455 		arg += max / HKEY_LCD_BRIGHTNESS_DIV;
456 		if (arg < min)
457 			arg = min;
458 		else if (arg > max)
459 			arg = max;
460 		hkey_lcd_brightness(h, HKEY_SET, &arg);
461 		break;
462 	case 4:
463 		/* Toggle sound mute. */
464 		hkey_sound_mute(h, HKEY_GET, &arg);
465 		if (arg)
466 			arg = 0;
467 		else
468 			arg = 1;
469 		hkey_sound_mute(h, HKEY_SET, &arg);
470 		break;
471 	case 7:
472 		/* Suspend. */
473 		acpi_event_sleep_button_sleep(acpi_sc);
474 		break;
475 	}
476 }
477 
478 static void
479 acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context)
480 {
481 	struct acpi_panasonic_softc *sc;
482 	UINT32 key = 0;
483 
484 	sc = (struct acpi_panasonic_softc *)context;
485 
486 	switch (notify) {
487 	case 0x80:
488 		ACPI_SERIAL_BEGIN(panasonic);
489 		if (acpi_panasonic_hkey_event(sc, h, &key) == 0) {
490 			acpi_panasonic_hkey_action(sc, h, key);
491 			acpi_UserNotify("Panasonic", h, (uint8_t)key);
492 		}
493 		ACPI_SERIAL_END(panasonic);
494 		break;
495 	default:
496 		device_printf(sc->dev, "unknown notify: %#x\n", notify);
497 		break;
498 	}
499 }
500 
501 static void
502 acpi_panasonic_power_profile(void *arg)
503 {
504 	ACPI_HANDLE handle;
505 	UINT32 brightness;
506 
507 	handle = (ACPI_HANDLE)arg;
508 
509 	/* Reset current brightness according to new power state. */
510 	ACPI_SERIAL_BEGIN(panasonic);
511 	hkey_lcd_brightness(handle, HKEY_GET, &brightness);
512 	hkey_lcd_brightness(handle, HKEY_SET, &brightness);
513 	ACPI_SERIAL_END(panasonic);
514 }
515