1/*-
2 * Copyright (c) 2003 Hiroyuki Aizu <aizu@navi.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31#include "opt_acpi.h"
32#include <sys/param.h>
33#include <sys/kernel.h>
34#include <sys/module.h>
35#include <sys/bus.h>
36
37#include <contrib/dev/acpica/include/acpi.h>
38
39#include <dev/acpica/acpivar.h>
40
41#define _COMPONENT	ACPI_OEM
42ACPI_MODULE_NAME("Toshiba")
43
44/*
45 * Toshiba HCI interface definitions
46 *
47 * HCI is Toshiba's "Hardware Control Interface" which is supposed to
48 * be uniform across all their models.  Ideally we would just call
49 * dedicated ACPI methods instead of using this primitive interface.
50 * However, the ACPI methods seem to be incomplete in some areas (for
51 * example they allow setting, but not reading, the LCD brightness
52 * value), so this is still useful.
53 */
54
55#define METHOD_HCI		"GHCI"
56#define METHOD_HCI_ENABLE	"ENAB"
57#define METHOD_VIDEO		"DSSX"
58
59/* Operations */
60#define HCI_SET				0xFF00
61#define HCI_GET				0xFE00
62
63/* Return codes */
64#define HCI_SUCCESS			0x0000
65#define HCI_FAILURE			0x1000
66#define HCI_NOT_SUPPORTED		0x8000
67#define HCI_EMPTY			0x8C00
68
69/* Functions */
70#define HCI_REG_LCD_BACKLIGHT		0x0002
71#define HCI_REG_FAN			0x0004
72#define HCI_REG_SYSTEM_EVENT		0x0016
73#define HCI_REG_VIDEO_OUTPUT		0x001C
74#define HCI_REG_HOTKEY_EVENT		0x001E
75#define HCI_REG_LCD_BRIGHTNESS		0x002A
76#define HCI_REG_CPU_SPEED		0x0032
77
78/* Field definitions */
79#define HCI_FAN_SHIFT			7
80#define HCI_LCD_BRIGHTNESS_BITS		3
81#define HCI_LCD_BRIGHTNESS_SHIFT	(16 - HCI_LCD_BRIGHTNESS_BITS)
82#define HCI_LCD_BRIGHTNESS_MAX		((1 << HCI_LCD_BRIGHTNESS_BITS) - 1)
83#define HCI_VIDEO_OUTPUT_FLAG		0x0100
84#define HCI_VIDEO_OUTPUT_LCD		0x1
85#define HCI_VIDEO_OUTPUT_CRT		0x2
86#define HCI_VIDEO_OUTPUT_TV		0x4
87#define HCI_CPU_SPEED_BITS		3
88#define HCI_CPU_SPEED_SHIFT		(16 - HCI_CPU_SPEED_BITS)
89#define HCI_CPU_SPEED_MAX		((1 << HCI_CPU_SPEED_BITS) - 1)
90
91/* Key press/release events. */
92#define FN_F1_PRESS	0x013B
93#define FN_F1_RELEASE	0x01BB
94#define FN_F2_PRESS	0x013C
95#define FN_F2_RELEASE	0x01BC
96#define FN_F3_PRESS	0x013D
97#define FN_F3_RELEASE	0x01BD
98#define FN_F4_PRESS	0x013E
99#define FN_F4_RELEASE	0x01BE
100#define FN_F5_PRESS	0x013F
101#define FN_F5_RELEASE	0x01BF
102#define FN_F6_PRESS	0x0140
103#define FN_F6_RELEASE	0x01C0
104#define FN_F7_PRESS	0x0141
105#define FN_F7_RELEASE	0x01C1
106#define FN_F8_PRESS	0x0142
107#define FN_F8_RELEASE	0x01C2
108#define FN_F9_PRESS	0x0143
109#define FN_F9_RELEASE	0x01C3
110#define FN_BS_PRESS	0x010E
111#define FN_BS_RELEASE	0x018E
112#define FN_ESC_PRESS	0x0101
113#define FN_ESC_RELEASE	0x0181
114#define FN_KNJ_PRESS	0x0129
115#define FN_KNJ_RELEASE	0x01A9
116
117/* HCI register definitions. */
118#define HCI_WORDS	6		/* Number of registers */
119#define HCI_REG_AX	0		/* Operation, then return value */
120#define HCI_REG_BX	1		/* Function */
121#define HCI_REG_CX	2		/* Argument (in or out) */
122#define HCI_REG_DX	3		/* Unused? */
123#define HCI_REG_SI	4		/* Unused? */
124#define HCI_REG_DI	5		/* Unused? */
125
126struct acpi_toshiba_softc {
127	device_t	dev;
128	ACPI_HANDLE	handle;
129	ACPI_HANDLE	video_handle;
130	struct		sysctl_ctx_list sysctl_ctx;
131	struct		sysctl_oid *sysctl_tree;
132};
133
134/* Prototype for HCI functions for getting/setting a value. */
135typedef int	hci_fn_t(ACPI_HANDLE, int, UINT32 *);
136
137static int	acpi_toshiba_probe(device_t dev);
138static int	acpi_toshiba_attach(device_t dev);
139static int	acpi_toshiba_detach(device_t dev);
140static int	acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS);
141static hci_fn_t	hci_force_fan;
142static hci_fn_t	hci_video_output;
143static hci_fn_t	hci_lcd_brightness;
144static hci_fn_t	hci_lcd_backlight;
145static hci_fn_t	hci_cpu_speed;
146static int	hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg);
147static void	hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h,
148		    UINT32 key);
149static void	acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify,
150		    void *context);
151static int	acpi_toshiba_video_probe(device_t dev);
152static int	acpi_toshiba_video_attach(device_t dev);
153
154ACPI_SERIAL_DECL(toshiba, "ACPI Toshiba Extras");
155
156/* Table of sysctl names and HCI functions to call. */
157static struct {
158	char		*name;
159	hci_fn_t	*handler;
160} sysctl_table[] = {
161	/* name,		handler */
162	{"force_fan",		hci_force_fan},
163	{"video_output",	hci_video_output},
164	{"lcd_brightness",	hci_lcd_brightness},
165	{"lcd_backlight",	hci_lcd_backlight},
166	{"cpu_speed",		hci_cpu_speed},
167	{NULL, NULL}
168};
169
170static device_method_t acpi_toshiba_methods[] = {
171	DEVMETHOD(device_probe,		acpi_toshiba_probe),
172	DEVMETHOD(device_attach,	acpi_toshiba_attach),
173	DEVMETHOD(device_detach,	acpi_toshiba_detach),
174
175	DEVMETHOD_END
176};
177
178static driver_t acpi_toshiba_driver = {
179	"acpi_toshiba",
180	acpi_toshiba_methods,
181	sizeof(struct acpi_toshiba_softc),
182};
183
184static devclass_t acpi_toshiba_devclass;
185DRIVER_MODULE(acpi_toshiba, acpi, acpi_toshiba_driver, acpi_toshiba_devclass,
186    0, 0);
187MODULE_DEPEND(acpi_toshiba, acpi, 1, 1, 1);
188
189static device_method_t acpi_toshiba_video_methods[] = {
190	DEVMETHOD(device_probe,		acpi_toshiba_video_probe),
191	DEVMETHOD(device_attach,	acpi_toshiba_video_attach),
192
193	DEVMETHOD_END
194};
195
196static driver_t acpi_toshiba_video_driver = {
197	"acpi_toshiba_video",
198	acpi_toshiba_video_methods,
199	0,
200};
201
202static devclass_t acpi_toshiba_video_devclass;
203DRIVER_MODULE(acpi_toshiba_video, acpi, acpi_toshiba_video_driver,
204    acpi_toshiba_video_devclass, 0, 0);
205MODULE_DEPEND(acpi_toshiba_video, acpi, 1, 1, 1);
206
207static int	enable_fn_keys = 1;
208TUNABLE_INT("hw.acpi.toshiba.enable_fn_keys", &enable_fn_keys);
209
210/*
211 * HID      Model
212 * -------------------------------------
213 * TOS6200  Libretto L Series
214 *          Dynabook Satellite 2455
215 *          Dynabook SS 3500
216 * TOS6207  Dynabook SS2110 Series
217 * TOS6208  SPA40
218 */
219static int
220acpi_toshiba_probe(device_t dev)
221{
222	static char *tosh_ids[] = { "TOS6200", "TOS6207", "TOS6208", NULL };
223	int rv;
224
225	if (acpi_disabled("toshiba") ||
226	    device_get_unit(dev) != 0)
227		return (ENXIO);
228	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, tosh_ids, NULL);
229	if (rv <= 0)
230		device_set_desc(dev, "Toshiba HCI Extras");
231	return (rv);
232}
233
234static int
235acpi_toshiba_attach(device_t dev)
236{
237	struct		acpi_toshiba_softc *sc;
238	struct		acpi_softc *acpi_sc;
239	ACPI_STATUS	status;
240	int		i;
241
242	sc = device_get_softc(dev);
243	sc->dev = dev;
244	sc->handle = acpi_get_handle(dev);
245
246	acpi_sc = acpi_device_get_parent_softc(dev);
247	sysctl_ctx_init(&sc->sysctl_ctx);
248	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
249	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
250	    "toshiba", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "");
251
252	for (i = 0; sysctl_table[i].name != NULL; i++) {
253		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
254		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
255		    sysctl_table[i].name,
256		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY |
257		    CTLFLAG_NEEDGIANT, sc, i, acpi_toshiba_sysctl, "I", "");
258	}
259
260	if (enable_fn_keys != 0) {
261		status = AcpiEvaluateObject(sc->handle, METHOD_HCI_ENABLE,
262					    NULL, NULL);
263		if (ACPI_FAILURE(status)) {
264			device_printf(dev, "enable FN keys failed\n");
265			sysctl_ctx_free(&sc->sysctl_ctx);
266			return (ENXIO);
267		}
268		AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
269					 acpi_toshiba_notify, sc);
270	}
271
272	return (0);
273}
274
275static int
276acpi_toshiba_detach(device_t dev)
277{
278	struct		acpi_toshiba_softc *sc;
279
280	sc = device_get_softc(dev);
281	if (enable_fn_keys != 0) {
282		AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
283					acpi_toshiba_notify);
284	}
285	sysctl_ctx_free(&sc->sysctl_ctx);
286
287	return (0);
288}
289
290static int
291acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS)
292{
293	struct		acpi_toshiba_softc *sc;
294	UINT32		arg;
295	int		function, error = 0;
296	hci_fn_t	*handler;
297
298	sc = (struct acpi_toshiba_softc *)oidp->oid_arg1;
299	function = oidp->oid_arg2;
300	handler = sysctl_table[function].handler;
301
302	/* Get the current value from the appropriate function. */
303	ACPI_SERIAL_BEGIN(toshiba);
304	error = handler(sc->handle, HCI_GET, &arg);
305	if (error != 0)
306		goto out;
307
308	/* Send the current value to the user and return if no new value. */
309	error = sysctl_handle_int(oidp, &arg, 0, req);
310	if (error != 0 || req->newptr == NULL)
311		goto out;
312
313	/* Set the new value via the appropriate function. */
314	error = handler(sc->handle, HCI_SET, &arg);
315
316out:
317	ACPI_SERIAL_END(toshiba);
318	return (error);
319}
320
321static int
322hci_force_fan(ACPI_HANDLE h, int op, UINT32 *state)
323{
324	int		ret;
325
326	ACPI_SERIAL_ASSERT(toshiba);
327	if (op == HCI_SET) {
328		if (*state > 1)
329			return (EINVAL);
330		*state <<= HCI_FAN_SHIFT;
331	}
332	ret = hci_call(h, op, HCI_REG_FAN, state);
333	if (ret == 0 && op == HCI_GET)
334		*state >>= HCI_FAN_SHIFT;
335	return (ret);
336}
337
338static int
339hci_video_output(ACPI_HANDLE h, int op, UINT32 *video_output)
340{
341	int		ret;
342	ACPI_STATUS	status;
343
344	ACPI_SERIAL_ASSERT(toshiba);
345	if (op == HCI_SET) {
346		if (*video_output < 1 || *video_output > 7)
347			return (EINVAL);
348		if (h == NULL)
349			return (ENXIO);
350		*video_output |= HCI_VIDEO_OUTPUT_FLAG;
351		status = acpi_SetInteger(h, METHOD_VIDEO, *video_output);
352		if (ACPI_SUCCESS(status))
353			ret = 0;
354		else
355			ret = ENXIO;
356	} else {
357		ret = hci_call(h, op, HCI_REG_VIDEO_OUTPUT, video_output);
358		if (ret == 0)
359			*video_output &= 0xff;
360	}
361
362	return (ret);
363}
364
365static int
366hci_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *brightness)
367{
368	int		ret;
369
370	ACPI_SERIAL_ASSERT(toshiba);
371	if (op == HCI_SET) {
372		if (*brightness > HCI_LCD_BRIGHTNESS_MAX)
373			return (EINVAL);
374		*brightness <<= HCI_LCD_BRIGHTNESS_SHIFT;
375	}
376	ret = hci_call(h, op, HCI_REG_LCD_BRIGHTNESS, brightness);
377	if (ret == 0 && op == HCI_GET)
378		*brightness >>= HCI_LCD_BRIGHTNESS_SHIFT;
379	return (ret);
380}
381
382static int
383hci_lcd_backlight(ACPI_HANDLE h, int op, UINT32 *backlight)
384{
385
386	ACPI_SERIAL_ASSERT(toshiba);
387	if (op == HCI_SET) {
388		if (*backlight > 1)
389			return (EINVAL);
390	}
391	return (hci_call(h, op, HCI_REG_LCD_BACKLIGHT, backlight));
392}
393
394static int
395hci_cpu_speed(ACPI_HANDLE h, int op, UINT32 *speed)
396{
397	int		ret;
398
399	ACPI_SERIAL_ASSERT(toshiba);
400	if (op == HCI_SET) {
401		if (*speed > HCI_CPU_SPEED_MAX)
402			return (EINVAL);
403		*speed <<= HCI_CPU_SPEED_SHIFT;
404	}
405	ret = hci_call(h, op, HCI_REG_CPU_SPEED, speed);
406	if (ret == 0 && op == HCI_GET)
407		*speed >>= HCI_CPU_SPEED_SHIFT;
408	return (ret);
409}
410
411static int
412hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg)
413{
414	ACPI_OBJECT_LIST args;
415	ACPI_BUFFER	results;
416	ACPI_OBJECT	obj[HCI_WORDS];
417	ACPI_OBJECT	*res;
418	int		status, i, ret;
419
420	ACPI_SERIAL_ASSERT(toshiba);
421	status = ENXIO;
422
423	for (i = 0; i < HCI_WORDS; i++) {
424		obj[i].Type = ACPI_TYPE_INTEGER;
425		obj[i].Integer.Value = 0;
426	}
427	obj[HCI_REG_AX].Integer.Value = op;
428	obj[HCI_REG_BX].Integer.Value = function;
429	if (op == HCI_SET)
430		obj[HCI_REG_CX].Integer.Value = *arg;
431
432	args.Count = HCI_WORDS;
433	args.Pointer = obj;
434	results.Pointer = NULL;
435	results.Length = ACPI_ALLOCATE_BUFFER;
436	if (ACPI_FAILURE(AcpiEvaluateObject(h, METHOD_HCI, &args, &results)))
437		goto end;
438	res = (ACPI_OBJECT *)results.Pointer;
439	if (!ACPI_PKG_VALID(res, HCI_WORDS)) {
440		printf("toshiba: invalid package!\n");
441		return (ENXIO);
442	}
443
444	acpi_PkgInt32(res, HCI_REG_AX, &ret);
445	if (ret == HCI_SUCCESS) {
446		if (op == HCI_GET)
447			acpi_PkgInt32(res, HCI_REG_CX, arg);
448		status = 0;
449	} else if (function == HCI_REG_SYSTEM_EVENT && op == HCI_GET &&
450	    ret == HCI_NOT_SUPPORTED) {
451		/*
452		 * Sometimes system events are disabled without us requesting
453		 * it.  This workaround attempts to re-enable them.
454		 *
455		 * XXX This call probably shouldn't be recursive.  Queueing
456		 * a task via AcpiOsQueueForExecution() might be better.
457		 */
458		 i = 1;
459		 hci_call(h, HCI_SET, HCI_REG_SYSTEM_EVENT, &i);
460	}
461
462end:
463	if (results.Pointer != NULL)
464		AcpiOsFree(results.Pointer);
465
466	return (status);
467}
468
469/*
470 * Perform a few actions based on the keypress.  Users can extend this
471 * functionality by reading the keystrokes we send to devd(8).
472 */
473static void
474hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, UINT32 key)
475{
476	UINT32		arg;
477
478	ACPI_SERIAL_ASSERT(toshiba);
479	switch (key) {
480	case FN_F6_RELEASE:
481		/* Decrease LCD brightness. */
482		hci_lcd_brightness(h, HCI_GET, &arg);
483		if (arg-- == 0)
484			arg = 0;
485		else
486			hci_lcd_brightness(h, HCI_SET, &arg);
487		break;
488	case FN_F7_RELEASE:
489		/* Increase LCD brightness. */
490		hci_lcd_brightness(h, HCI_GET, &arg);
491		if (arg++ == 7)
492			arg = 7;
493		else
494			hci_lcd_brightness(h, HCI_SET, &arg);
495		break;
496	case FN_F5_RELEASE:
497		/* Cycle through video outputs. */
498		hci_video_output(h, HCI_GET, &arg);
499		arg = (arg + 1) % 7;
500		hci_video_output(sc->video_handle, HCI_SET, &arg);
501		break;
502	case FN_F8_RELEASE:
503		/* Toggle LCD backlight. */
504		hci_lcd_backlight(h, HCI_GET, &arg);
505		arg = (arg != 0) ? 0 : 1;
506		hci_lcd_backlight(h, HCI_SET, &arg);
507		break;
508	case FN_ESC_RELEASE:
509		/* Toggle forcing fan on. */
510		hci_force_fan(h, HCI_GET, &arg);
511		arg = (arg != 0) ? 0 : 1;
512		hci_force_fan(h, HCI_SET, &arg);
513		break;
514	}
515}
516
517static void
518acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, void *context)
519{
520	struct		acpi_toshiba_softc *sc;
521	UINT32		key;
522
523	sc = (struct acpi_toshiba_softc *)context;
524
525	if (notify == 0x80) {
526		ACPI_SERIAL_BEGIN(toshiba);
527		while (hci_call(h, HCI_GET, HCI_REG_SYSTEM_EVENT, &key) == 0) {
528			hci_key_action(sc, h, key);
529			acpi_UserNotify("TOSHIBA", h, (uint8_t)key);
530		}
531		ACPI_SERIAL_END(toshiba);
532	} else
533		device_printf(sc->dev, "unknown notify: 0x%x\n", notify);
534}
535
536/*
537 * Toshiba video pseudo-device to provide the DSSX method.
538 *
539 * HID      Model
540 * -------------------------------------
541 * TOS6201  Libretto L Series
542 */
543static int
544acpi_toshiba_video_probe(device_t dev)
545{
546	static char *vid_ids[] = { "TOS6201", NULL };
547	int rv;
548
549	if (acpi_disabled("toshiba") ||
550	    device_get_unit(dev) != 0)
551		return (ENXIO);
552
553	device_quiet(dev);
554	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, vid_ids, NULL);
555	if (rv <= 0)
556		device_set_desc(dev, "Toshiba Video");
557	return (rv);
558}
559
560static int
561acpi_toshiba_video_attach(device_t dev)
562{
563	struct		acpi_toshiba_softc *sc;
564
565	sc = devclass_get_softc(acpi_toshiba_devclass, 0);
566	if (sc == NULL)
567		return (ENXIO);
568	sc->video_handle = acpi_get_handle(dev);
569	return (0);
570}
571