xref: /freebsd/sys/dev/acpi_support/acpi_panasonic.c (revision c2dee7786bf32cb66cedec226e42e79e06457c51)
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 #include "opt_acpi.h"
31 #include <sys/param.h>
32 #include <sys/bus.h>
33 #include <sys/eventhandler.h>
34 #include <sys/kernel.h>
35 #include <sys/malloc.h>
36 #include <sys/module.h>
37 #include <sys/power.h>
38 
39 #include <contrib/dev/acpica/include/acpi.h>
40 
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_resume(device_t dev);
84 static void	acpi_panasonic_clear_rfkill(device_t dev);
85 static int	acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS);
86 static UINT64	acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index);
87 static void	acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index,
88 		    UINT64 val);
89 static int	acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc,
90 		    ACPI_HANDLE h, UINT32 *arg);
91 static void	acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc,
92 		    ACPI_HANDLE h, UINT32 key);
93 static void	acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify,
94 		    void *context);
95 static void	acpi_panasonic_power_profile(void *arg);
96 
97 static hkey_fn_t	hkey_lcd_brightness_max;
98 static hkey_fn_t	hkey_lcd_brightness_min;
99 static hkey_fn_t	hkey_lcd_brightness;
100 static hkey_fn_t	hkey_sound_mute;
101 ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras");
102 
103 /* Table of sysctl names and HKEY functions to call. */
104 static struct {
105 	char		*name;
106 	hkey_fn_t	*handler;
107 } sysctl_table[] = {
108 	/* name,		handler */
109 	{"lcd_brightness_max",	hkey_lcd_brightness_max},
110 	{"lcd_brightness_min",	hkey_lcd_brightness_min},
111 	{"lcd_brightness",	hkey_lcd_brightness},
112 	{"sound_mute",		hkey_sound_mute},
113 	{NULL, NULL}
114 };
115 
116 static device_method_t acpi_panasonic_methods[] = {
117 	DEVMETHOD(device_probe,		acpi_panasonic_probe),
118 	DEVMETHOD(device_attach,	acpi_panasonic_attach),
119 	DEVMETHOD(device_detach,	acpi_panasonic_detach),
120 	DEVMETHOD(device_shutdown,	acpi_panasonic_shutdown),
121 	DEVMETHOD(device_resume,	acpi_panasonic_resume),
122 
123 	DEVMETHOD_END
124 };
125 
126 static driver_t acpi_panasonic_driver = {
127 	"acpi_panasonic",
128 	acpi_panasonic_methods,
129 	sizeof(struct acpi_panasonic_softc),
130 };
131 
132 DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver, 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 	int rv;
140 
141 	if (acpi_disabled("panasonic") ||
142 	    device_get_unit(dev) != 0)
143 		return (ENXIO);
144 	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids, NULL);
145 
146 	if (rv <= 0)
147 		device_set_desc(dev, "Panasonic Notebook Hotkeys");
148 	return (rv);
149 }
150 
151 static int
152 acpi_panasonic_attach(device_t dev)
153 {
154 	struct acpi_panasonic_softc *sc;
155 	struct acpi_softc *acpi_sc;
156 	ACPI_STATUS status;
157 	int i;
158 
159 	sc = device_get_softc(dev);
160 	sc->dev = dev;
161 	sc->handle = acpi_get_handle(dev);
162 
163 	acpi_sc = acpi_device_get_parent_softc(dev);
164 
165 	/* Build sysctl tree */
166 	sysctl_ctx_init(&sc->sysctl_ctx);
167 	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
168 	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
169 	    "panasonic", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "");
170 	for (i = 0; sysctl_table[i].name != NULL; i++) {
171 		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
172 		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
173 		    sysctl_table[i].name,
174 		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY |
175 		    CTLFLAG_MPSAFE, sc, i, acpi_panasonic_sysctl, "I", "");
176 	}
177 
178 	acpi_panasonic_clear_rfkill(dev);
179 
180 #if 0
181 	/* Activate hotkeys */
182 	status = AcpiEvaluateObject(sc->handle, "", NULL, NULL);
183 	if (ACPI_FAILURE(status)) {
184 		device_printf(dev, "enable FN keys failed\n");
185 		sysctl_ctx_free(&sc->sysctl_ctx);
186 		return (ENXIO);
187 	}
188 #endif
189 
190 	/* Handle notifies */
191 	status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
192 	    acpi_panasonic_notify, sc);
193 	if (ACPI_FAILURE(status)) {
194 		device_printf(dev, "couldn't install notify handler - %s\n",
195 		    AcpiFormatException(status));
196 		sysctl_ctx_free(&sc->sysctl_ctx);
197 		return (ENXIO);
198 	}
199 
200 	/* Install power profile event handler */
201 	sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change,
202 	    acpi_panasonic_power_profile, sc->handle, 0);
203 
204 	return (0);
205 }
206 
207 static int
208 acpi_panasonic_detach(device_t dev)
209 {
210 	struct acpi_panasonic_softc *sc;
211 
212 	sc = device_get_softc(dev);
213 
214 	/* Remove power profile event handler */
215 	EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh);
216 
217 	/* Remove notify handler */
218 	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
219 	    acpi_panasonic_notify);
220 
221 	/* Free sysctl tree */
222 	sysctl_ctx_free(&sc->sysctl_ctx);
223 
224 	return (0);
225 }
226 
227 static int
228 acpi_panasonic_shutdown(device_t dev)
229 {
230 	struct acpi_panasonic_softc *sc;
231 	int mute;
232 
233 	/* Mute the main audio during reboot to prevent static burst to speaker. */
234 	sc = device_get_softc(dev);
235 	mute = 1;
236 	hkey_sound_mute(sc->handle, HKEY_SET, &mute);
237 	return (0);
238 }
239 
240 static int
241 acpi_panasonic_resume(device_t dev)
242 {
243 
244 	acpi_panasonic_clear_rfkill(dev);
245 	return (0);
246 }
247 
248 static void
249 acpi_panasonic_clear_rfkill(device_t dev)
250 {
251 	ACPI_HANDLE wlsw_handle;
252 	ACPI_STATUS status;
253 
254 	/*
255 	 * Call WLSW.SHRF to clear wireless RF_KILL on models that have it.
256 	 * On FZ-Y1 and similar models, the EC latches RF_KILL on shutdown
257 	 * and suspend, causing the wireless card to boot with hard block
258 	 * enabled.  The SHRF method sets the EC state to deassert RF_KILL
259 	 * GPIO on mini-PCIe pin 20 via SMI (ASRV call with function
260 	 * 0x0F/0x03).
261 	 */
262 	status = AcpiGetHandle(NULL, "\\_SB.WLSW", &wlsw_handle);
263 	if (ACPI_SUCCESS(status)) {
264 		status = AcpiEvaluateObject(wlsw_handle, "SHRF", NULL, NULL);
265 		if (ACPI_FAILURE(status) && bootverbose)
266 			device_printf(dev, "WLSW.SHRF failed: %s\n",
267 			    AcpiFormatException(status));
268 	}
269 }
270 
271 static int
272 acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS)
273 {
274 	struct acpi_panasonic_softc *sc;
275 	UINT32 arg;
276 	int function, error;
277 	hkey_fn_t *handler;
278 
279 	sc = (struct acpi_panasonic_softc *)oidp->oid_arg1;
280 	function = oidp->oid_arg2;
281 	handler = sysctl_table[function].handler;
282 
283 	/* Get the current value from the appropriate function. */
284 	ACPI_SERIAL_BEGIN(panasonic);
285 	error = handler(sc->handle, HKEY_GET, &arg);
286 	if (error != 0)
287 		goto out;
288 
289 	/* Send the current value to the user and return if no new value. */
290 	error = sysctl_handle_int(oidp, &arg, 0, req);
291 	if (error != 0 || req->newptr == NULL)
292 		goto out;
293 
294 	/* Set the new value via the appropriate function. */
295 	error = handler(sc->handle, HKEY_SET, &arg);
296 
297 out:
298 	ACPI_SERIAL_END(panasonic);
299 	return (error);
300 }
301 
302 static UINT64
303 acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index)
304 {
305 	ACPI_BUFFER buf;
306 	ACPI_OBJECT *res;
307 	UINT64 ret;
308 
309 	ACPI_SERIAL_ASSERT(panasonic);
310 	ret = -1;
311 	buf.Length = ACPI_ALLOCATE_BUFFER;
312 	buf.Pointer = NULL;
313 	AcpiEvaluateObject(h, "SINF", NULL, &buf);
314 	res = (ACPI_OBJECT *)buf.Pointer;
315 	if (res->Type == ACPI_TYPE_PACKAGE)
316 		ret = res->Package.Elements[index].Integer.Value;
317 	AcpiOsFree(buf.Pointer);
318 
319 	return (ret);
320 }
321 
322 static void
323 acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index, UINT64 val)
324 {
325 	ACPI_OBJECT_LIST args;
326 	ACPI_OBJECT obj[2];
327 
328 	ACPI_SERIAL_ASSERT(panasonic);
329 	obj[0].Type = ACPI_TYPE_INTEGER;
330 	obj[0].Integer.Value = index;
331 	obj[1].Type = ACPI_TYPE_INTEGER;
332 	obj[1].Integer.Value = val;
333 	args.Count = 2;
334 	args.Pointer = obj;
335 	AcpiEvaluateObject(h, "SSET", &args, NULL);
336 }
337 
338 static int
339 hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val)
340 {
341 	int reg;
342 
343 	ACPI_SERIAL_ASSERT(panasonic);
344 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
345 	    HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC;
346 
347 	switch (op) {
348 	case HKEY_SET:
349 		return (EPERM);
350 		break;
351 	case HKEY_GET:
352 		*val = acpi_panasonic_sinf(h, reg);
353 		break;
354 	}
355 
356 	return (0);
357 }
358 
359 static int
360 hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val)
361 {
362 	int reg;
363 
364 	ACPI_SERIAL_ASSERT(panasonic);
365 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
366 	    HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC;
367 
368 	switch (op) {
369 	case HKEY_SET:
370 		return (EPERM);
371 		break;
372 	case HKEY_GET:
373 		*val = acpi_panasonic_sinf(h, reg);
374 		break;
375 	}
376 
377 	return (0);
378 }
379 
380 static int
381 hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val)
382 {
383 	int reg;
384 	UINT32 max, min;
385 
386 	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
387 	    HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC;
388 
389 	ACPI_SERIAL_ASSERT(panasonic);
390 	switch (op) {
391 	case HKEY_SET:
392 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
393 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
394 		if (*val < min || *val > max)
395 			return (EINVAL);
396 		acpi_panasonic_sset(h, reg, *val);
397 		break;
398 	case HKEY_GET:
399 		*val = acpi_panasonic_sinf(h, reg);
400 		break;
401 	}
402 
403 	return (0);
404 }
405 
406 static int
407 hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val)
408 {
409 
410 	ACPI_SERIAL_ASSERT(panasonic);
411 	switch (op) {
412 	case HKEY_SET:
413 		if (*val != 0 && *val != 1)
414 			return (EINVAL);
415 		acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val);
416 		break;
417 	case HKEY_GET:
418 		*val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE);
419 		break;
420 	}
421 
422 	return (0);
423 }
424 
425 static int
426 acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
427     UINT32 *arg)
428 {
429 	ACPI_BUFFER buf;
430 	ACPI_OBJECT *res;
431 	UINT64 val;
432 	int status;
433 
434 	ACPI_SERIAL_ASSERT(panasonic);
435 	status = ENXIO;
436 
437 	buf.Length = ACPI_ALLOCATE_BUFFER;
438 	buf.Pointer = NULL;
439 	AcpiEvaluateObject(h, "HINF", NULL, &buf);
440 	res = (ACPI_OBJECT *)buf.Pointer;
441 	if (res->Type != ACPI_TYPE_INTEGER) {
442 		device_printf(sc->dev, "HINF returned non-integer\n");
443 		goto end;
444 	}
445 	val = res->Integer.Value;
446 #ifdef ACPI_PANASONIC_DEBUG
447 	device_printf(sc->dev, "%s button Fn+F%d\n",
448 		      (val & 0x80) ? "Pressed" : "Released",
449 		      (int)(val & 0x7f));
450 #endif
451 	if ((val & 0x7f) > 0 && (val & 0x7f) < 11) {
452 		*arg = val;
453 		status = 0;
454 	}
455 end:
456 	if (buf.Pointer)
457 		AcpiOsFree(buf.Pointer);
458 
459 	return (status);
460 }
461 
462 static void
463 acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
464     UINT32 key)
465 {
466 	struct acpi_softc *acpi_sc;
467 	int arg, max, min;
468 
469 	acpi_sc = acpi_device_get_parent_softc(sc->dev);
470 
471 	ACPI_SERIAL_ASSERT(panasonic);
472 	switch (key) {
473 	case 1:
474 		/* Decrease LCD brightness. */
475 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
476 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
477 		hkey_lcd_brightness(h, HKEY_GET, &arg);
478 		arg -= max / HKEY_LCD_BRIGHTNESS_DIV;
479 		if (arg < min)
480 			arg = min;
481 		else if (arg > max)
482 			arg = max;
483 		hkey_lcd_brightness(h, HKEY_SET, &arg);
484 		break;
485 	case 2:
486 		/* Increase LCD brightness. */
487 		hkey_lcd_brightness_max(h, HKEY_GET, &max);
488 		hkey_lcd_brightness_min(h, HKEY_GET, &min);
489 		hkey_lcd_brightness(h, HKEY_GET, &arg);
490 		arg += max / HKEY_LCD_BRIGHTNESS_DIV;
491 		if (arg < min)
492 			arg = min;
493 		else if (arg > max)
494 			arg = max;
495 		hkey_lcd_brightness(h, HKEY_SET, &arg);
496 		break;
497 	case 4:
498 		/* Toggle sound mute. */
499 		hkey_sound_mute(h, HKEY_GET, &arg);
500 		if (arg)
501 			arg = 0;
502 		else
503 			arg = 1;
504 		hkey_sound_mute(h, HKEY_SET, &arg);
505 		break;
506 	case 7:
507 		/* Suspend. */
508 		acpi_event_sleep_button_sleep(acpi_sc);
509 		break;
510 	}
511 }
512 
513 static void
514 acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context)
515 {
516 	struct acpi_panasonic_softc *sc;
517 	UINT32 key = 0;
518 
519 	sc = (struct acpi_panasonic_softc *)context;
520 
521 	switch (notify) {
522 	case 0x80:
523 		ACPI_SERIAL_BEGIN(panasonic);
524 		if (acpi_panasonic_hkey_event(sc, h, &key) == 0) {
525 			acpi_panasonic_hkey_action(sc, h, key);
526 			acpi_UserNotify("Panasonic", h, (uint8_t)key);
527 		}
528 		ACPI_SERIAL_END(panasonic);
529 		break;
530 	case 0x81:
531 		if (!bootverbose)
532 			break;
533 		/* FALLTHROUGH */
534 	default:
535 		device_printf(sc->dev, "unknown notify: %#x\n", notify);
536 		break;
537 	}
538 }
539 
540 static void
541 acpi_panasonic_power_profile(void *arg)
542 {
543 	ACPI_HANDLE handle;
544 	UINT32 brightness;
545 
546 	handle = (ACPI_HANDLE)arg;
547 
548 	/* Reset current brightness according to new power state. */
549 	ACPI_SERIAL_BEGIN(panasonic);
550 	hkey_lcd_brightness(handle, HKEY_GET, &brightness);
551 	hkey_lcd_brightness(handle, HKEY_SET, &brightness);
552 	ACPI_SERIAL_END(panasonic);
553 }
554