acpi_panasonic.c revision 150003
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: head/sys/dev/acpi_support/acpi_panasonic.c 150003 2005-09-11 18:39:03Z obrien $");
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
44ACPI_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
66struct 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. */
77typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *);
78
79static int	acpi_panasonic_probe(device_t dev);
80static int	acpi_panasonic_attach(device_t dev);
81static int	acpi_panasonic_detach(device_t dev);
82static int	acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS);
83static ACPI_INTEGER acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index);
84static void	acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index,
85		    ACPI_INTEGER val);
86static int	acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc,
87		    ACPI_HANDLE h, UINT32 *arg);
88static void	acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc,
89		    ACPI_HANDLE h, UINT32 key);
90static void	acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify,
91		    void *context);
92static void	acpi_panasonic_power_profile(void *arg);
93
94static hkey_fn_t	hkey_lcd_brightness_max;
95static hkey_fn_t	hkey_lcd_brightness_min;
96static hkey_fn_t	hkey_lcd_brightness;
97static hkey_fn_t	hkey_sound_mute;
98ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras");
99
100/* Table of sysctl names and HKEY functions to call. */
101static struct {
102	char		*name;
103	hkey_fn_t	*handler;
104} sysctl_table[] = {
105	/* name,		handler */
106	{"lcd_brightness_max",	hkey_lcd_brightness_max},
107	{"lcd_brightness_min",	hkey_lcd_brightness_min},
108	{"lcd_brightness",	hkey_lcd_brightness},
109	{"sound_mute",		hkey_sound_mute},
110	{NULL, NULL}
111};
112
113static device_method_t acpi_panasonic_methods[] = {
114	DEVMETHOD(device_probe,		acpi_panasonic_probe),
115	DEVMETHOD(device_attach,	acpi_panasonic_attach),
116	DEVMETHOD(device_detach,	acpi_panasonic_detach),
117
118	{0, 0}
119};
120
121static driver_t acpi_panasonic_driver = {
122	"acpi_panasonic",
123	acpi_panasonic_methods,
124	sizeof(struct acpi_panasonic_softc),
125};
126
127static devclass_t acpi_panasonic_devclass;
128
129DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver,
130    acpi_panasonic_devclass, 0, 0);
131MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1);
132
133static int
134acpi_panasonic_probe(device_t dev)
135{
136	static char *mat_ids[] = { "MAT0019", NULL };
137
138	if (acpi_disabled("panasonic") ||
139	    ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids) == NULL ||
140	    device_get_unit(dev) != 0)
141		return (ENXIO);
142
143	device_set_desc(dev, "Panasonic Notebook Hotkeys");
144	return (0);
145}
146
147static int
148acpi_panasonic_attach(device_t dev)
149{
150	struct acpi_panasonic_softc *sc;
151	struct acpi_softc *acpi_sc;
152	ACPI_STATUS status;
153	int i;
154
155	sc = device_get_softc(dev);
156	sc->dev = dev;
157	sc->handle = acpi_get_handle(dev);
158
159	acpi_sc = acpi_device_get_parent_softc(dev);
160
161	/* Build sysctl tree */
162	sysctl_ctx_init(&sc->sysctl_ctx);
163	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
164	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
165	    "panasonic", CTLFLAG_RD, 0, "");
166	for (i = 0; sysctl_table[i].name != NULL; i++) {
167		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
168		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
169		    sysctl_table[i].name,
170		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
171		    sc, i, acpi_panasonic_sysctl, "I", "");
172	}
173
174#if 0
175	/* Activate hotkeys */
176	status = AcpiEvaluateObject(sc->handle, "", NULL, NULL);
177	if (ACPI_FAILURE(status)) {
178		device_printf(dev, "enable FN keys failed\n");
179		sysctl_ctx_free(&sc->sysctl_ctx);
180		return (ENXIO);
181	}
182#endif
183
184        /* Handle notifies */
185	status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
186	    acpi_panasonic_notify, sc);
187	if (ACPI_FAILURE(status)) {
188		device_printf(dev, "couldn't install notify handler - %s\n",
189		    AcpiFormatException(status));
190		sysctl_ctx_free(&sc->sysctl_ctx);
191		return (ENXIO);
192	}
193
194	/* Install power profile event handler */
195	sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change,
196	    acpi_panasonic_power_profile, sc->handle, 0);
197
198	return (0);
199}
200
201static int
202acpi_panasonic_detach(device_t dev)
203{
204	struct acpi_panasonic_softc *sc;
205
206	sc = device_get_softc(dev);
207
208	/* Remove power profile event handler */
209	EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh);
210
211	/* Remove notify handler */
212	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
213	    acpi_panasonic_notify);
214
215	/* Free sysctl tree */
216	sysctl_ctx_free(&sc->sysctl_ctx);
217
218	return (0);
219}
220
221static int
222acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS)
223{
224	struct acpi_panasonic_softc *sc;
225	UINT32 arg;
226	int function, error;
227	hkey_fn_t *handler;
228
229	sc = (struct acpi_panasonic_softc *)oidp->oid_arg1;
230	function = oidp->oid_arg2;
231	handler = sysctl_table[function].handler;
232
233        /* Get the current value from the appropriate function. */
234	ACPI_SERIAL_BEGIN(panasonic);
235	error = handler(sc->handle, HKEY_GET, &arg);
236	if (error != 0)
237		goto out;
238
239	/* Send the current value to the user and return if no new value. */
240	error = sysctl_handle_int(oidp, &arg, 0, req);
241	if (error != 0 || req->newptr == NULL)
242		goto out;
243
244	/* Set the new value via the appropriate function. */
245	error = handler(sc->handle, HKEY_SET, &arg);
246
247out:
248	ACPI_SERIAL_END(panasonic);
249	return (error);
250}
251
252static ACPI_INTEGER
253acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index)
254{
255	ACPI_BUFFER buf;
256	ACPI_OBJECT *res;
257	ACPI_INTEGER ret;
258
259	ACPI_SERIAL_ASSERT(panasonic);
260	ret = -1;
261	buf.Length = ACPI_ALLOCATE_BUFFER;
262	buf.Pointer = NULL;
263	AcpiEvaluateObject(h, "SINF", NULL, &buf);
264	res = (ACPI_OBJECT *)buf.Pointer;
265	if (res->Type == ACPI_TYPE_PACKAGE)
266		ret = res->Package.Elements[index].Integer.Value;
267	AcpiOsFree(buf.Pointer);
268
269	return (ret);
270}
271
272static void
273acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, ACPI_INTEGER val)
274{
275	ACPI_OBJECT_LIST args;
276	ACPI_OBJECT obj[2];
277
278	ACPI_SERIAL_ASSERT(panasonic);
279	obj[0].Type = ACPI_TYPE_INTEGER;
280	obj[0].Integer.Value = index;
281	obj[1].Type = ACPI_TYPE_INTEGER;
282	obj[1].Integer.Value = val;
283	args.Count = 2;
284	args.Pointer = obj;
285	AcpiEvaluateObject(h, "SSET", &args, NULL);
286}
287
288static int
289hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val)
290{
291	int reg;
292
293	ACPI_SERIAL_ASSERT(panasonic);
294	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
295	    HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC;
296
297	switch (op) {
298	case HKEY_SET:
299		return (EPERM);
300		break;
301	case HKEY_GET:
302		*val = acpi_panasonic_sinf(h, reg);
303		break;
304	}
305
306	return (0);
307}
308
309static int
310hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val)
311{
312	int reg;
313
314	ACPI_SERIAL_ASSERT(panasonic);
315	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
316	    HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC;
317
318	switch (op) {
319	case HKEY_SET:
320		return (EPERM);
321		break;
322	case HKEY_GET:
323		*val = acpi_panasonic_sinf(h, reg);
324		break;
325	}
326
327	return (0);
328}
329
330static int
331hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val)
332{
333	int reg;
334	UINT32 max, min;
335
336	reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ?
337	    HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC;
338
339	ACPI_SERIAL_ASSERT(panasonic);
340	switch (op) {
341	case HKEY_SET:
342		hkey_lcd_brightness_max(h, HKEY_GET, &max);
343		hkey_lcd_brightness_min(h, HKEY_GET, &min);
344		if (*val < min || *val > max)
345			return (EINVAL);
346		acpi_panasonic_sset(h, reg, *val);
347		break;
348	case HKEY_GET:
349		*val = acpi_panasonic_sinf(h, reg);
350		break;
351	}
352
353	return (0);
354}
355
356static int
357hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val)
358{
359
360	ACPI_SERIAL_ASSERT(panasonic);
361	switch (op) {
362	case HKEY_SET:
363		if (*val != 0 && *val != 1)
364			return (EINVAL);
365		acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val);
366		break;
367	case HKEY_GET:
368		*val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE);
369		break;
370	}
371
372	return (0);
373}
374
375static int
376acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
377    UINT32 *arg)
378{
379	ACPI_BUFFER buf;
380	ACPI_OBJECT *res;
381	ACPI_INTEGER val;
382	int status;
383
384	ACPI_SERIAL_ASSERT(panasonic);
385	status = ENXIO;
386
387	buf.Length = ACPI_ALLOCATE_BUFFER;
388	buf.Pointer = NULL;
389	AcpiEvaluateObject(h, "HINF", NULL, &buf);
390	res = (ACPI_OBJECT *)buf.Pointer;
391	if (res->Type != ACPI_TYPE_INTEGER) {
392		device_printf(sc->dev, "HINF returned non-integer\n");
393		goto end;
394	}
395	val = res->Integer.Value;
396#ifdef ACPI_PANASONIC_DEBUG
397	device_printf(sc->dev, "%s button Fn+F%d\n",
398		      (val & 0x80) ? "Pressed" : "Released",
399		      (int)(val & 0x7f));
400#endif
401	if ((val & 0x7f) > 0 && (val & 0x7f) < 11) {
402		*arg = val;
403		status = 0;
404	}
405end:
406	if (buf.Pointer)
407		AcpiOsFree(buf.Pointer);
408
409	return (status);
410}
411
412static void
413acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h,
414    UINT32 key)
415{
416	int arg, max, min;
417
418	ACPI_SERIAL_ASSERT(panasonic);
419	switch (key) {
420	case 1:
421		/* Decrease LCD brightness. */
422		hkey_lcd_brightness_max(h, HKEY_GET, &max);
423		hkey_lcd_brightness_min(h, HKEY_GET, &min);
424		hkey_lcd_brightness(h, HKEY_GET, &arg);
425		arg -= max / HKEY_LCD_BRIGHTNESS_DIV;
426		if (arg < min)
427			arg = min;
428		else if (arg > max)
429			arg = max;
430		hkey_lcd_brightness(h, HKEY_SET, &arg);
431		break;
432	case 2:
433		/* Increase LCD brightness. */
434		hkey_lcd_brightness_max(h, HKEY_GET, &max);
435		hkey_lcd_brightness_min(h, HKEY_GET, &min);
436		hkey_lcd_brightness(h, HKEY_GET, &arg);
437		arg += max / HKEY_LCD_BRIGHTNESS_DIV;
438		if (arg < min)
439			arg = min;
440		else if (arg > max)
441			arg = max;
442		hkey_lcd_brightness(h, HKEY_SET, &arg);
443		break;
444	case 4:
445		/* Toggle sound mute. */
446		hkey_sound_mute(h, HKEY_GET, &arg);
447		if (arg)
448			arg = 0;
449		else
450			arg = 1;
451		hkey_sound_mute(h, HKEY_SET, &arg);
452		break;
453	}
454}
455
456static void
457acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context)
458{
459	struct acpi_panasonic_softc *sc;
460	UINT32 key;
461
462	sc = (struct acpi_panasonic_softc *)context;
463
464	switch (notify) {
465	case 0x80:
466		ACPI_SERIAL_BEGIN(panasonic);
467		if (acpi_panasonic_hkey_event(sc, h, &key) == 0) {
468			acpi_panasonic_hkey_action(sc, h, key);
469			acpi_UserNotify("Panasonic", h, (uint8_t)key);
470		}
471		ACPI_SERIAL_END(panasonic);
472		break;
473	default:
474		device_printf(sc->dev, "unknown notify: %#x\n", notify);
475		break;
476	}
477}
478
479static void
480acpi_panasonic_power_profile(void *arg)
481{
482	ACPI_HANDLE handle;
483	UINT32 brightness;
484
485	handle = (ACPI_HANDLE)arg;
486
487	/* Reset current brightness according to new power state. */
488	ACPI_SERIAL_BEGIN(panasonic);
489	hkey_lcd_brightness(handle, HKEY_GET, &brightness);
490	hkey_lcd_brightness(handle, HKEY_SET, &brightness);
491	ACPI_SERIAL_END(panasonic);
492}
493