1/* $NetBSD: hpqlb_acpi.c,v 1.8 2010/10/26 05:28:29 jruoho Exp $ */
2
3/*-
4 * Copyright (c) 2008  Christoph Egger <cegger@netbsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: hpqlb_acpi.c,v 1.8 2010/10/26 05:28:29 jruoho Exp $");
31
32#include <sys/param.h>
33#include <sys/device.h>
34#include <sys/module.h>
35#include <sys/systm.h>
36
37#include <machine/pio.h>
38
39#include <dev/acpi/acpireg.h>
40#include <dev/acpi/acpivar.h>
41
42#include <dev/isa/isareg.h>
43
44#include <dev/wscons/wsconsio.h>
45#include <dev/wscons/wskbdvar.h>
46
47#define _COMPONENT		ACPI_RESOURCE_COMPONENT
48ACPI_MODULE_NAME		("hpqlb_acpi")
49
50struct hpqlb_softc {
51	device_t sc_dev;
52	struct acpi_devnode *sc_node;
53
54	device_t sc_wskbddev;
55
56#define HP_PSW_DISPLAY_CYCLE	0
57#define HP_PSW_BRIGHTNESS_UP	1
58#define HP_PSW_BRIGHTNESS_DOWN	2
59#define HP_PSW_SLEEP		3
60#define HP_PSW_LAST		4
61	struct sysmon_pswitch sc_smpsw[HP_PSW_LAST];
62	bool sc_smpsw_displaycycle_valid;
63	bool sc_smpsw_sleep_valid;
64};
65
66#define HP_QLB_Quick			0x88
67#define HP_QLB_DVD			0x8e
68#define HP_QLB_FullBackward		0x90
69#define HP_QLB_Play			0xa2
70#define HP_QLB_FullForward		0x99
71#define HP_QLB_Stop			0xa4
72#define HP_QLB_VolumeMute		0xa0
73#define HP_QLB_VolumeDown		0xae
74#define HP_QLB_VolumeUp			0xb0
75
76#define HP_QLB_Help			0xb1
77#define HP_QLB_WWW			0xb2
78#define HP_QLB_DisplayCycle		/* ??? */
79#define HP_QLB_Sleep			0xdf
80#define HP_QLB_Lock			0x8a
81#define HP_QLB_BrightnessDown		/* ??? */
82#define HP_QLB_BrightnessUp		/* ??? */
83#define HP_QLB_ChasisOpen		0xe3
84
85static int hpqlb_match(device_t, cfdata_t, void *);
86static void hpqlb_attach(device_t, device_t, void *);
87static int hpqlb_detach(device_t, int);
88
89static int hpqlb_finalize(device_t);
90static int hpqlb_hotkey_handler(struct wskbd_softc *, void *, u_int, int);
91
92static void hpqlb_init(device_t);
93static bool hpqlb_resume(device_t, const pmf_qual_t *);
94
95CFATTACH_DECL_NEW(hpqlb, sizeof(struct hpqlb_softc),
96    hpqlb_match, hpqlb_attach, hpqlb_detach, NULL);
97
98static const char * const hpqlb_ids[] = {
99	"HPQ0006",
100	"HPQ0007",
101	NULL
102};
103
104static int
105hpqlb_match(device_t parent, cfdata_t match, void *opaque)
106{
107	struct acpi_attach_args *aa = opaque;
108
109	if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
110		return 0;
111
112	return acpi_match_hid(aa->aa_node->ad_devinfo, hpqlb_ids);
113}
114
115static void
116hpqlb_attach(device_t parent, device_t self, void *opaque)
117{
118	struct hpqlb_softc *sc = device_private(self);
119	struct acpi_attach_args *aa = opaque;
120
121	sc->sc_node = aa->aa_node;
122	sc->sc_dev = self;
123
124	aprint_naive("\n");
125	aprint_normal(": HP Quick Launch Buttons\n");
126
127	hpqlb_init(self);
128
129	if (config_finalize_register(self, hpqlb_finalize) != 0)
130		aprint_error_dev(self, "unable to register hpqlb finalizer\n");
131
132	sc->sc_smpsw_displaycycle_valid = true;
133
134	sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE].smpsw_name =
135	    PSWITCH_HK_DISPLAY_CYCLE;
136
137	sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE].smpsw_type =
138	    PSWITCH_TYPE_HOTKEY;
139
140	if (sysmon_pswitch_register(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE]) != 0)
141		sc->sc_smpsw_displaycycle_valid = false;
142
143	sc->sc_smpsw_sleep_valid = true;
144	sc->sc_smpsw[HP_PSW_SLEEP].smpsw_name = device_xname(self);
145	sc->sc_smpsw[HP_PSW_SLEEP].smpsw_type = PSWITCH_TYPE_SLEEP;
146
147	if (sysmon_pswitch_register(&sc->sc_smpsw[HP_PSW_SLEEP]) != 0)
148		sc->sc_smpsw_sleep_valid = false;
149
150	(void)pmf_device_register(self, NULL, hpqlb_resume);
151}
152
153static int
154hpqlb_detach(device_t self, int flags)
155{
156	struct hpqlb_softc *sc = device_private(self);
157
158	pmf_device_deregister(self);
159	wskbd_hotkey_deregister(sc->sc_wskbddev);
160
161	if (sc->sc_smpsw_sleep_valid != false)
162		sysmon_pswitch_unregister(&sc->sc_smpsw[HP_PSW_SLEEP]);
163
164	if (sc->sc_smpsw_displaycycle_valid != false)
165		sysmon_pswitch_unregister(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE]);
166
167	return 0;
168}
169
170static int
171hpqlb_hotkey_handler(struct wskbd_softc *wskbd_sc, void *cookie,
172		u_int type, int value)
173{
174	struct hpqlb_softc *sc = cookie;
175	int ret = 1;
176
177	switch (value) {
178	case HP_QLB_VolumeMute:
179		if (type != WSCONS_EVENT_KEY_DOWN)
180			break;
181		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_TOGGLE);
182		break;
183	case HP_QLB_VolumeDown:
184		if (type != WSCONS_EVENT_KEY_DOWN)
185			break;
186		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_DOWN);
187		break;
188	case HP_QLB_VolumeUp:
189		if (type != WSCONS_EVENT_KEY_DOWN)
190			break;
191		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_UP);
192		break;
193
194#if 0
195	case HP_QLB_DisplayCycle:		/* ??? */
196		if (type != WSCONS_EVENT_KEY_DOWN)
197			break;
198		if (sc->sc_smpsw_displaycycle_valid == false)
199			break;
200		sysmon_pswitch_event(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE],
201			PSWITCH_EVENT_PRESSED);
202		break;
203#endif
204	case HP_QLB_Sleep:
205		if (type != WSCONS_EVENT_KEY_DOWN)
206			break;
207		if (sc->sc_smpsw_sleep_valid == false)
208			break;
209		sysmon_pswitch_event(&sc->sc_smpsw[HP_PSW_SLEEP],
210			PSWITCH_EVENT_PRESSED);
211		break;
212#if 0
213	case HP_QLB_BrightnessDown:	/* ??? */
214		if (type != WSCONS_EVENT_KEY_DOWN)
215			break;
216		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_DOWN);
217		break;
218	case HP_QLB_BrightnessUp:	/* ??? */
219		if (type != WSCONS_EVENT_KEY_DOWN)
220			break;
221		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_UP);
222		break;
223#endif
224	case HP_QLB_ChasisOpen:
225		if (type != WSCONS_EVENT_KEY_DOWN)
226			break;
227		pmf_event_inject(NULL, PMFE_CHASSIS_LID_OPEN);
228		break;
229	default:
230
231		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "unknown hotkey "
232			"0x%02x\n", value));
233		ret = 0; /* Assume, this is no hotkey */
234		break;
235	}
236
237	return ret;
238}
239
240static void
241hpqlb_init(device_t self)
242{
243
244	/* HPQ0006: HP Quick Launch Buttons */
245	/* HPQ0007: HP Remote Device */
246	/* val 0, 1 or 7 == HPQ0006 */
247	/* val not 0, 1 or 7 == HPQ0007 */
248
249	/* Turn on Quick Launch Buttons */
250	outb(IO_RTC+2, 0xaf);
251	outb(IO_RTC+3, 7 /* val */);
252}
253
254static int
255hpqlb_finalize(device_t self)
256{
257	device_t dv;
258	deviter_t di;
259	struct hpqlb_softc *sc = device_private(self);
260	static int done_once = 0;
261
262	/* Since we only handle real hardware, we only need to be
263	 * called once.
264	 */
265	if (done_once)
266		return 0;
267	done_once = 1;
268
269	for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL;
270	     dv = deviter_next(&di)) {
271		if (!device_is_a(dv, "wskbd"))
272			continue;
273
274		/* Make sure, we don't get a wskbd from a USB keyboard.
275		 * QLB only works on the wskbd attached on pckbd. */
276		if (!device_is_a(device_parent(dv), "pckbd"))
277			continue;
278
279		aprint_normal_dev(self, "registering on %s\n",
280				device_xname(dv));
281		break;
282	}
283	deviter_release(&di);
284
285	if (dv == NULL) {
286		aprint_error_dev(self, "WARNING: no matching wskbd found\n");
287		return 1;
288	}
289
290	sc->sc_wskbddev = dv;
291
292	wskbd_hotkey_register(sc->sc_wskbddev, sc, hpqlb_hotkey_handler);
293
294	return 0;
295}
296
297static bool
298hpqlb_resume(device_t self, const pmf_qual_t *qual)
299{
300
301	hpqlb_init(self);
302
303	return true;
304}
305
306MODULE(MODULE_CLASS_DRIVER, hpqlb, NULL);
307
308#ifdef _MODULE
309#include "ioconf.c"
310#endif
311
312static int
313hpqlb_modcmd(modcmd_t cmd, void *aux)
314{
315	int rv = 0;
316
317	switch (cmd) {
318
319	case MODULE_CMD_INIT:
320
321#ifdef _MODULE
322		rv = config_init_component(cfdriver_ioconf_hpqlb,
323		    cfattach_ioconf_hpqlb, cfdata_ioconf_hpqlb);
324#endif
325		break;
326
327	case MODULE_CMD_FINI:
328
329#ifdef _MODULE
330		rv = config_fini_component(cfdriver_ioconf_hpqlb,
331		    cfattach_ioconf_hpqlb, cfdata_ioconf_hpqlb);
332#endif
333		break;
334
335	default:
336		rv = ENOTTY;
337	}
338
339	return rv;
340}
341