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: releng/10.3/sys/dev/acpi_support/acpi_toshiba.c 249816 2013-04-23 18:30:33Z jkim $");
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
224	if (acpi_disabled("toshiba") ||
225	    ACPI_ID_PROBE(device_get_parent(dev), dev, tosh_ids) == NULL ||
226	    device_get_unit(dev) != 0)
227		return (ENXIO);
228
229	device_set_desc(dev, "Toshiba HCI Extras");
230	return (0);
231}
232
233static int
234acpi_toshiba_attach(device_t dev)
235{
236	struct		acpi_toshiba_softc *sc;
237	struct		acpi_softc *acpi_sc;
238	ACPI_STATUS	status;
239	int		i;
240
241	sc = device_get_softc(dev);
242	sc->dev = dev;
243	sc->handle = acpi_get_handle(dev);
244
245	acpi_sc = acpi_device_get_parent_softc(dev);
246	sysctl_ctx_init(&sc->sysctl_ctx);
247	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
248	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
249	    "toshiba", CTLFLAG_RD, 0, "");
250
251	for (i = 0; sysctl_table[i].name != NULL; i++) {
252		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
253		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
254		    sysctl_table[i].name,
255		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
256		    sc, i, acpi_toshiba_sysctl, "I", "");
257	}
258
259	if (enable_fn_keys != 0) {
260		status = AcpiEvaluateObject(sc->handle, METHOD_HCI_ENABLE,
261					    NULL, NULL);
262		if (ACPI_FAILURE(status)) {
263			device_printf(dev, "enable FN keys failed\n");
264			sysctl_ctx_free(&sc->sysctl_ctx);
265			return (ENXIO);
266		}
267		AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
268					 acpi_toshiba_notify, sc);
269	}
270
271	return (0);
272}
273
274static int
275acpi_toshiba_detach(device_t dev)
276{
277	struct		acpi_toshiba_softc *sc;
278
279	sc = device_get_softc(dev);
280	if (enable_fn_keys != 0) {
281		AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
282					acpi_toshiba_notify);
283	}
284	sysctl_ctx_free(&sc->sysctl_ctx);
285
286	return (0);
287}
288
289static int
290acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS)
291{
292	struct		acpi_toshiba_softc *sc;
293	UINT32		arg;
294	int		function, error = 0;
295	hci_fn_t	*handler;
296
297	sc = (struct acpi_toshiba_softc *)oidp->oid_arg1;
298	function = oidp->oid_arg2;
299	handler = sysctl_table[function].handler;
300
301	/* Get the current value from the appropriate function. */
302	ACPI_SERIAL_BEGIN(toshiba);
303	error = handler(sc->handle, HCI_GET, &arg);
304	if (error != 0)
305		goto out;
306
307	/* Send the current value to the user and return if no new value. */
308	error = sysctl_handle_int(oidp, &arg, 0, req);
309	if (error != 0 || req->newptr == NULL)
310		goto out;
311
312	/* Set the new value via the appropriate function. */
313	error = handler(sc->handle, HCI_SET, &arg);
314
315out:
316	ACPI_SERIAL_END(toshiba);
317	return (error);
318}
319
320static int
321hci_force_fan(ACPI_HANDLE h, int op, UINT32 *state)
322{
323	int		ret;
324
325	ACPI_SERIAL_ASSERT(toshiba);
326	if (op == HCI_SET) {
327		if (*state > 1)
328			return (EINVAL);
329		*state <<= HCI_FAN_SHIFT;
330	}
331	ret = hci_call(h, op, HCI_REG_FAN, state);
332	if (ret == 0 && op == HCI_GET)
333		*state >>= HCI_FAN_SHIFT;
334	return (ret);
335}
336
337static int
338hci_video_output(ACPI_HANDLE h, int op, UINT32 *video_output)
339{
340	int		ret;
341	ACPI_STATUS	status;
342
343	ACPI_SERIAL_ASSERT(toshiba);
344	if (op == HCI_SET) {
345		if (*video_output < 1 || *video_output > 7)
346			return (EINVAL);
347		if (h == NULL)
348			return (ENXIO);
349		*video_output |= HCI_VIDEO_OUTPUT_FLAG;
350		status = acpi_SetInteger(h, METHOD_VIDEO, *video_output);
351		if (ACPI_SUCCESS(status))
352			ret = 0;
353		else
354			ret = ENXIO;
355	} else {
356		ret = hci_call(h, op, HCI_REG_VIDEO_OUTPUT, video_output);
357		if (ret == 0)
358			*video_output &= 0xff;
359	}
360
361	return (ret);
362}
363
364static int
365hci_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *brightness)
366{
367	int		ret;
368
369	ACPI_SERIAL_ASSERT(toshiba);
370	if (op == HCI_SET) {
371		if (*brightness > HCI_LCD_BRIGHTNESS_MAX)
372			return (EINVAL);
373		*brightness <<= HCI_LCD_BRIGHTNESS_SHIFT;
374	}
375	ret = hci_call(h, op, HCI_REG_LCD_BRIGHTNESS, brightness);
376	if (ret == 0 && op == HCI_GET)
377		*brightness >>= HCI_LCD_BRIGHTNESS_SHIFT;
378	return (ret);
379}
380
381static int
382hci_lcd_backlight(ACPI_HANDLE h, int op, UINT32 *backlight)
383{
384
385	ACPI_SERIAL_ASSERT(toshiba);
386	if (op == HCI_SET) {
387		if (*backlight > 1)
388			return (EINVAL);
389	}
390	return (hci_call(h, op, HCI_REG_LCD_BACKLIGHT, backlight));
391}
392
393static int
394hci_cpu_speed(ACPI_HANDLE h, int op, UINT32 *speed)
395{
396	int		ret;
397
398	ACPI_SERIAL_ASSERT(toshiba);
399	if (op == HCI_SET) {
400		if (*speed > HCI_CPU_SPEED_MAX)
401			return (EINVAL);
402		*speed <<= HCI_CPU_SPEED_SHIFT;
403	}
404	ret = hci_call(h, op, HCI_REG_CPU_SPEED, speed);
405	if (ret == 0 && op == HCI_GET)
406		*speed >>= HCI_CPU_SPEED_SHIFT;
407	return (ret);
408}
409
410static int
411hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg)
412{
413	ACPI_OBJECT_LIST args;
414	ACPI_BUFFER	results;
415	ACPI_OBJECT	obj[HCI_WORDS];
416	ACPI_OBJECT	*res;
417	int		status, i, ret;
418
419	ACPI_SERIAL_ASSERT(toshiba);
420	status = ENXIO;
421
422	for (i = 0; i < HCI_WORDS; i++) {
423		obj[i].Type = ACPI_TYPE_INTEGER;
424		obj[i].Integer.Value = 0;
425	}
426	obj[HCI_REG_AX].Integer.Value = op;
427	obj[HCI_REG_BX].Integer.Value = function;
428	if (op == HCI_SET)
429		obj[HCI_REG_CX].Integer.Value = *arg;
430
431	args.Count = HCI_WORDS;
432	args.Pointer = obj;
433	results.Pointer = NULL;
434	results.Length = ACPI_ALLOCATE_BUFFER;
435	if (ACPI_FAILURE(AcpiEvaluateObject(h, METHOD_HCI, &args, &results)))
436		goto end;
437	res = (ACPI_OBJECT *)results.Pointer;
438	if (!ACPI_PKG_VALID(res, HCI_WORDS)) {
439		printf("toshiba: invalid package!\n");
440		return (ENXIO);
441	}
442
443	acpi_PkgInt32(res, HCI_REG_AX, &ret);
444	if (ret == HCI_SUCCESS) {
445		if (op == HCI_GET)
446			acpi_PkgInt32(res, HCI_REG_CX, arg);
447		status = 0;
448	} else if (function == HCI_REG_SYSTEM_EVENT && op == HCI_GET &&
449	    ret == HCI_NOT_SUPPORTED) {
450		/*
451		 * Sometimes system events are disabled without us requesting
452		 * it.  This workaround attempts to re-enable them.
453		 *
454		 * XXX This call probably shouldn't be recursive.  Queueing
455		 * a task via AcpiOsQueueForExecution() might be better.
456		 */
457		 i = 1;
458		 hci_call(h, HCI_SET, HCI_REG_SYSTEM_EVENT, &i);
459	}
460
461end:
462	if (results.Pointer != NULL)
463		AcpiOsFree(results.Pointer);
464
465	return (status);
466}
467
468/*
469 * Perform a few actions based on the keypress.  Users can extend this
470 * functionality by reading the keystrokes we send to devd(8).
471 */
472static void
473hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, UINT32 key)
474{
475	UINT32		arg;
476
477	ACPI_SERIAL_ASSERT(toshiba);
478	switch (key) {
479	case FN_F6_RELEASE:
480		/* Decrease LCD brightness. */
481		hci_lcd_brightness(h, HCI_GET, &arg);
482		if (arg-- == 0)
483			arg = 0;
484		else
485			hci_lcd_brightness(h, HCI_SET, &arg);
486		break;
487	case FN_F7_RELEASE:
488		/* Increase LCD brightness. */
489		hci_lcd_brightness(h, HCI_GET, &arg);
490		if (arg++ == 7)
491			arg = 7;
492		else
493			hci_lcd_brightness(h, HCI_SET, &arg);
494		break;
495	case FN_F5_RELEASE:
496		/* Cycle through video outputs. */
497		hci_video_output(h, HCI_GET, &arg);
498		arg = (arg + 1) % 7;
499		hci_video_output(sc->video_handle, HCI_SET, &arg);
500		break;
501	case FN_F8_RELEASE:
502		/* Toggle LCD backlight. */
503		hci_lcd_backlight(h, HCI_GET, &arg);
504		arg = (arg != 0) ? 0 : 1;
505		hci_lcd_backlight(h, HCI_SET, &arg);
506		break;
507	case FN_ESC_RELEASE:
508		/* Toggle forcing fan on. */
509		hci_force_fan(h, HCI_GET, &arg);
510		arg = (arg != 0) ? 0 : 1;
511		hci_force_fan(h, HCI_SET, &arg);
512		break;
513	}
514}
515
516static void
517acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, void *context)
518{
519	struct		acpi_toshiba_softc *sc;
520	UINT32		key;
521
522	sc = (struct acpi_toshiba_softc *)context;
523
524	if (notify == 0x80) {
525		ACPI_SERIAL_BEGIN(toshiba);
526		while (hci_call(h, HCI_GET, HCI_REG_SYSTEM_EVENT, &key) == 0) {
527			hci_key_action(sc, h, key);
528			acpi_UserNotify("TOSHIBA", h, (uint8_t)key);
529		}
530		ACPI_SERIAL_END(toshiba);
531	} else
532		device_printf(sc->dev, "unknown notify: 0x%x\n", notify);
533}
534
535/*
536 * Toshiba video pseudo-device to provide the DSSX method.
537 *
538 * HID      Model
539 * -------------------------------------
540 * TOS6201  Libretto L Series
541 */
542static int
543acpi_toshiba_video_probe(device_t dev)
544{
545	static char *vid_ids[] = { "TOS6201", NULL };
546
547	if (acpi_disabled("toshiba") ||
548	    ACPI_ID_PROBE(device_get_parent(dev), dev, vid_ids) == NULL ||
549	    device_get_unit(dev) != 0)
550		return (ENXIO);
551
552	device_quiet(dev);
553	device_set_desc(dev, "Toshiba Video");
554	return (0);
555}
556
557static int
558acpi_toshiba_video_attach(device_t dev)
559{
560	struct		acpi_toshiba_softc *sc;
561
562	sc = devclass_get_softc(acpi_toshiba_devclass, 0);
563	if (sc == NULL)
564		return (ENXIO);
565	sc->video_handle = acpi_get_handle(dev);
566	return (0);
567}
568