1/* $NetBSD: hpqlb_acpi.c,v 1.11 2021/01/29 15:49:55 thorpej 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.11 2021/01/29 15:49:55 thorpej 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 struct device_compatible_entry compat_data[] = {
99	{ .compat = "HPQ0006" },
100	{ .compat = "HPQ0007" },
101	DEVICE_COMPAT_EOL
102};
103
104static int
105hpqlb_match(device_t parent, cfdata_t match, void *opaque)
106{
107	struct acpi_attach_args *aa = opaque;
108
109	return acpi_compatible_match(aa, compat_data);
110}
111
112static void
113hpqlb_attach(device_t parent, device_t self, void *opaque)
114{
115	struct hpqlb_softc *sc = device_private(self);
116	struct acpi_attach_args *aa = opaque;
117
118	sc->sc_node = aa->aa_node;
119	sc->sc_dev = self;
120
121	aprint_naive("\n");
122	aprint_normal(": HP Quick Launch Buttons\n");
123
124	hpqlb_init(self);
125
126	if (config_finalize_register(self, hpqlb_finalize) != 0)
127		aprint_error_dev(self, "unable to register hpqlb finalizer\n");
128
129	sc->sc_smpsw_displaycycle_valid = true;
130
131	sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE].smpsw_name =
132	    PSWITCH_HK_DISPLAY_CYCLE;
133
134	sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE].smpsw_type =
135	    PSWITCH_TYPE_HOTKEY;
136
137	if (sysmon_pswitch_register(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE]) != 0)
138		sc->sc_smpsw_displaycycle_valid = false;
139
140	sc->sc_smpsw_sleep_valid = true;
141	sc->sc_smpsw[HP_PSW_SLEEP].smpsw_name = device_xname(self);
142	sc->sc_smpsw[HP_PSW_SLEEP].smpsw_type = PSWITCH_TYPE_SLEEP;
143
144	if (sysmon_pswitch_register(&sc->sc_smpsw[HP_PSW_SLEEP]) != 0)
145		sc->sc_smpsw_sleep_valid = false;
146
147	(void)pmf_device_register(self, NULL, hpqlb_resume);
148}
149
150static int
151hpqlb_detach(device_t self, int flags)
152{
153	struct hpqlb_softc *sc = device_private(self);
154
155	pmf_device_deregister(self);
156	wskbd_hotkey_deregister(sc->sc_wskbddev);
157
158	if (sc->sc_smpsw_sleep_valid != false)
159		sysmon_pswitch_unregister(&sc->sc_smpsw[HP_PSW_SLEEP]);
160
161	if (sc->sc_smpsw_displaycycle_valid != false)
162		sysmon_pswitch_unregister(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE]);
163
164	return 0;
165}
166
167static int
168hpqlb_hotkey_handler(struct wskbd_softc *wskbd_sc, void *cookie,
169		u_int type, int value)
170{
171	struct hpqlb_softc *sc = cookie;
172	int ret = 1;
173
174	switch (value) {
175	case HP_QLB_VolumeMute:
176		if (type != WSCONS_EVENT_KEY_DOWN)
177			break;
178		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_TOGGLE);
179		break;
180	case HP_QLB_VolumeDown:
181		if (type != WSCONS_EVENT_KEY_DOWN)
182			break;
183		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_DOWN);
184		break;
185	case HP_QLB_VolumeUp:
186		if (type != WSCONS_EVENT_KEY_DOWN)
187			break;
188		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_UP);
189		break;
190
191#if 0
192	case HP_QLB_DisplayCycle:		/* ??? */
193		if (type != WSCONS_EVENT_KEY_DOWN)
194			break;
195		if (sc->sc_smpsw_displaycycle_valid == false)
196			break;
197		sysmon_pswitch_event(&sc->sc_smpsw[HP_PSW_DISPLAY_CYCLE],
198			PSWITCH_EVENT_PRESSED);
199		break;
200#endif
201	case HP_QLB_Sleep:
202		if (type != WSCONS_EVENT_KEY_DOWN)
203			break;
204		if (sc->sc_smpsw_sleep_valid == false)
205			break;
206		sysmon_pswitch_event(&sc->sc_smpsw[HP_PSW_SLEEP],
207			PSWITCH_EVENT_PRESSED);
208		break;
209#if 0
210	case HP_QLB_BrightnessDown:	/* ??? */
211		if (type != WSCONS_EVENT_KEY_DOWN)
212			break;
213		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_DOWN);
214		break;
215	case HP_QLB_BrightnessUp:	/* ??? */
216		if (type != WSCONS_EVENT_KEY_DOWN)
217			break;
218		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_UP);
219		break;
220#endif
221	case HP_QLB_ChasisOpen:
222		if (type != WSCONS_EVENT_KEY_DOWN)
223			break;
224		pmf_event_inject(NULL, PMFE_CHASSIS_LID_OPEN);
225		break;
226	default:
227
228		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "unknown hotkey "
229			"0x%02x\n", value));
230		ret = 0; /* Assume, this is no hotkey */
231		break;
232	}
233
234	return ret;
235}
236
237static void
238hpqlb_init(device_t self)
239{
240
241	/* HPQ0006: HP Quick Launch Buttons */
242	/* HPQ0007: HP Remote Device */
243	/* val 0, 1 or 7 == HPQ0006 */
244	/* val not 0, 1 or 7 == HPQ0007 */
245
246	/* Turn on Quick Launch Buttons */
247	outb(IO_RTC+2, 0xaf);
248	outb(IO_RTC+3, 7 /* val */);
249}
250
251static int
252hpqlb_finalize(device_t self)
253{
254	device_t dv;
255	deviter_t di;
256	struct hpqlb_softc *sc = device_private(self);
257	static int done_once = 0;
258
259	/* Since we only handle real hardware, we only need to be
260	 * called once.
261	 */
262	if (done_once)
263		return 0;
264	done_once = 1;
265
266	for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL;
267	     dv = deviter_next(&di)) {
268		if (!device_is_a(dv, "wskbd"))
269			continue;
270
271		/* Make sure, we don't get a wskbd from a USB keyboard.
272		 * QLB only works on the wskbd attached on pckbd. */
273		if (!device_is_a(device_parent(dv), "pckbd"))
274			continue;
275
276		aprint_normal_dev(self, "registering on %s\n",
277				device_xname(dv));
278		break;
279	}
280	deviter_release(&di);
281
282	if (dv == NULL) {
283		aprint_error_dev(self, "WARNING: no matching wskbd found\n");
284		return 1;
285	}
286
287	sc->sc_wskbddev = dv;
288
289	wskbd_hotkey_register(sc->sc_wskbddev, sc, hpqlb_hotkey_handler);
290
291	return 0;
292}
293
294static bool
295hpqlb_resume(device_t self, const pmf_qual_t *qual)
296{
297
298	hpqlb_init(self);
299
300	return true;
301}
302
303MODULE(MODULE_CLASS_DRIVER, hpqlb, "sysmon_power");
304
305#ifdef _MODULE
306#include "ioconf.c"
307#endif
308
309static int
310hpqlb_modcmd(modcmd_t cmd, void *aux)
311{
312	int rv = 0;
313
314	switch (cmd) {
315
316	case MODULE_CMD_INIT:
317
318#ifdef _MODULE
319		rv = config_init_component(cfdriver_ioconf_hpqlb,
320		    cfattach_ioconf_hpqlb, cfdata_ioconf_hpqlb);
321#endif
322		break;
323
324	case MODULE_CMD_FINI:
325
326#ifdef _MODULE
327		rv = config_fini_component(cfdriver_ioconf_hpqlb,
328		    cfattach_ioconf_hpqlb, cfdata_ioconf_hpqlb);
329#endif
330		break;
331
332	default:
333		rv = ENOTTY;
334	}
335
336	return rv;
337}
338