xref: /netbsd-src/sys/dev/acpi/thinkpad_acpi.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /* $NetBSD: thinkpad_acpi.c,v 1.52 2021/05/29 16:49:57 riastradh Exp $ */
2 
3 /*-
4  * Copyright (c) 2007 Jared D. McNeill <jmcneill@invisible.ca>
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: thinkpad_acpi.c,v 1.52 2021/05/29 16:49:57 riastradh Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/device.h>
34 #include <sys/module.h>
35 #include <sys/sdt.h>
36 #include <sys/systm.h>
37 
38 #include <dev/acpi/acpireg.h>
39 #include <dev/acpi/acpivar.h>
40 #include <dev/acpi/acpi_ecvar.h>
41 #include <dev/acpi/acpi_power.h>
42 
43 #include <dev/isa/isareg.h>
44 
45 #define _COMPONENT		ACPI_RESOURCE_COMPONENT
46 ACPI_MODULE_NAME		("thinkpad_acpi")
47 
48 #define	THINKPAD_NTEMPSENSORS	8
49 #define	THINKPAD_NFANSENSORS	1
50 #define	THINKPAD_NSENSORS	(THINKPAD_NTEMPSENSORS + THINKPAD_NFANSENSORS)
51 
52 typedef struct thinkpad_softc {
53 	device_t		sc_dev;
54 	device_t		sc_ecdev;
55 	struct acpi_devnode	*sc_node;
56 	ACPI_HANDLE		sc_powhdl;
57 	ACPI_HANDLE		sc_cmoshdl;
58 
59 #define	TP_PSW_SLEEP		0	/* FnF4 */
60 #define	TP_PSW_HIBERNATE	1	/* FnF12 */
61 #define	TP_PSW_DISPLAY_CYCLE	2	/* FnF7 */
62 #define	TP_PSW_LOCK_SCREEN	3	/* FnF2 */
63 #define	TP_PSW_BATTERY_INFO	4	/* FnF3 */
64 #define	TP_PSW_EJECT_BUTTON	5	/* FnF9 */
65 #define	TP_PSW_ZOOM_BUTTON	6	/* FnSPACE */
66 #define	TP_PSW_VENDOR_BUTTON	7	/* ThinkVantage */
67 #define	TP_PSW_FNF1_BUTTON	8	/* FnF1 */
68 #define	TP_PSW_WIRELESS_BUTTON	9	/* FnF5 */
69 #define	TP_PSW_WWAN_BUTTON	10	/* FnF6 */
70 #define	TP_PSW_POINTER_BUTTON	11	/* FnF8 */
71 #define	TP_PSW_FNF10_BUTTON	12	/* FnF10 */
72 #define	TP_PSW_FNF11_BUTTON	13	/* FnF11 */
73 #define	TP_PSW_BRIGHTNESS_UP	14
74 #define	TP_PSW_BRIGHTNESS_DOWN	15
75 #define	TP_PSW_THINKLIGHT	16
76 #define	TP_PSW_VOLUME_UP	17
77 #define	TP_PSW_VOLUME_DOWN	18
78 #define	TP_PSW_VOLUME_MUTE	19
79 #define	TP_PSW_STAR_BUTTON	20
80 #define	TP_PSW_SCISSORS_BUTTON	21
81 #define	TP_PSW_BLUETOOTH_BUTTON	22
82 #define	TP_PSW_KEYBOARD_BUTTON	23
83 #define	TP_PSW_LAST		24
84 
85 	struct sysmon_pswitch	sc_smpsw[TP_PSW_LAST];
86 	bool			sc_smpsw_valid;
87 
88 	struct sysmon_envsys	*sc_sme;
89 	envsys_data_t		sc_sensor[THINKPAD_NSENSORS];
90 
91 	int			sc_display_state;
92 } thinkpad_softc_t;
93 
94 /* Hotkey events */
95 #define	THINKPAD_NOTIFY_FnF1		0x001
96 #define	THINKPAD_NOTIFY_LockScreen	0x002
97 #define	THINKPAD_NOTIFY_BatteryInfo	0x003
98 #define	THINKPAD_NOTIFY_SleepButton	0x004
99 #define	THINKPAD_NOTIFY_WirelessSwitch	0x005
100 #define	THINKPAD_NOTIFY_wWANSwitch	0x006
101 #define	THINKPAD_NOTIFY_DisplayCycle	0x007
102 #define	THINKPAD_NOTIFY_PointerSwitch	0x008
103 #define	THINKPAD_NOTIFY_EjectButton	0x009
104 #define	THINKPAD_NOTIFY_FnF10		0x00a	/* XXX: Not seen on T61 */
105 #define	THINKPAD_NOTIFY_FnF11		0x00b
106 #define	THINKPAD_NOTIFY_HibernateButton	0x00c
107 #define	THINKPAD_NOTIFY_BrightnessUp	0x010
108 #define	THINKPAD_NOTIFY_BrightnessDown	0x011
109 #define	THINKPAD_NOTIFY_ThinkLight	0x012
110 #define	THINKPAD_NOTIFY_Zoom		0x014
111 #define	THINKPAD_NOTIFY_VolumeUp	0x015	/* XXX: Not seen on T61 */
112 #define	THINKPAD_NOTIFY_VolumeDown	0x016	/* XXX: Not seen on T61 */
113 #define	THINKPAD_NOTIFY_VolumeMute	0x017	/* XXX: Not seen on T61 */
114 #define	THINKPAD_NOTIFY_ThinkVantage	0x018
115 #define	THINKPAD_NOTIFY_Star		0x311
116 #define	THINKPAD_NOTIFY_Scissors	0x312
117 #define	THINKPAD_NOTIFY_Bluetooth	0x314
118 #define	THINKPAD_NOTIFY_Keyboard	0x315
119 
120 #define	THINKPAD_CMOS_BRIGHTNESS_UP	0x04
121 #define	THINKPAD_CMOS_BRIGHTNESS_DOWN	0x05
122 
123 #define	THINKPAD_HKEY_VERSION_1		0x0100
124 #define	THINKPAD_HKEY_VERSION_2		0x0200
125 
126 #define	THINKPAD_DISPLAY_LCD		0x01
127 #define	THINKPAD_DISPLAY_CRT		0x02
128 #define	THINKPAD_DISPLAY_DVI		0x08
129 #define	THINKPAD_DISPLAY_ALL \
130 	(THINKPAD_DISPLAY_LCD | THINKPAD_DISPLAY_CRT | THINKPAD_DISPLAY_DVI)
131 
132 #define THINKPAD_BLUETOOTH_HWPRESENT	0x01
133 #define THINKPAD_BLUETOOTH_RADIOSSW	0x02
134 #define THINKPAD_BLUETOOTH_RESUMECTRL	0x04
135 
136 #define THINKPAD_WWAN_HWPRESENT		0x01
137 #define THINKPAD_WWAN_RADIOSSW		0x02
138 #define THINKPAD_WWAN_RESUMECTRL	0x04
139 
140 #define THINKPAD_UWB_HWPRESENT	0x01
141 #define THINKPAD_UWB_RADIOSSW	0x02
142 
143 #define THINKPAD_RFK_BLUETOOTH		0
144 #define THINKPAD_RFK_WWAN		1
145 #define THINKPAD_RFK_UWB		2
146 
147 static int	thinkpad_match(device_t, cfdata_t, void *);
148 static void	thinkpad_attach(device_t, device_t, void *);
149 static int	thinkpad_detach(device_t, int);
150 
151 static ACPI_STATUS thinkpad_mask_init(thinkpad_softc_t *, uint32_t);
152 static void	thinkpad_notify_handler(ACPI_HANDLE, uint32_t, void *);
153 static void	thinkpad_get_hotkeys(void *);
154 
155 static void	thinkpad_sensors_init(thinkpad_softc_t *);
156 static void	thinkpad_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
157 static void	thinkpad_temp_refresh(struct sysmon_envsys *, envsys_data_t *);
158 static void	thinkpad_fan_refresh(struct sysmon_envsys *, envsys_data_t *);
159 
160 static void	thinkpad_uwb_toggle(thinkpad_softc_t *);
161 static void	thinkpad_wwan_toggle(thinkpad_softc_t *);
162 static void	thinkpad_bluetooth_toggle(thinkpad_softc_t *);
163 
164 static bool	thinkpad_resume(device_t, const pmf_qual_t *);
165 static void	thinkpad_brightness_up(device_t);
166 static void	thinkpad_brightness_down(device_t);
167 static uint8_t	thinkpad_brightness_read(thinkpad_softc_t *sc);
168 static void	thinkpad_cmos(thinkpad_softc_t *, uint8_t);
169 
170 CFATTACH_DECL3_NEW(thinkpad, sizeof(thinkpad_softc_t),
171     thinkpad_match, thinkpad_attach, thinkpad_detach, NULL, NULL, NULL,
172     DVF_DETACH_SHUTDOWN);
173 
174 static const struct device_compatible_entry compat_data[] = {
175 	{ .compat = "IBM0068" },
176 	{ .compat = "LEN0068" },
177 	{ .compat = "LEN0268" },
178 	DEVICE_COMPAT_EOL
179 };
180 
181 static int
182 thinkpad_match(device_t parent, cfdata_t match, void *opaque)
183 {
184 	struct acpi_attach_args *aa = (struct acpi_attach_args *)opaque;
185 	ACPI_INTEGER ver;
186 	int ret;
187 
188 	ret = acpi_compatible_match(aa, compat_data);
189 	if (ret == 0)
190 		return 0;
191 
192 	/* We only support hotkey versions 0x0100 and 0x0200 */
193 	if (ACPI_FAILURE(acpi_eval_integer(aa->aa_node->ad_handle, "MHKV",
194 	    &ver)))
195 		return 0;
196 
197 	switch (ver) {
198 	case THINKPAD_HKEY_VERSION_1:
199 	case THINKPAD_HKEY_VERSION_2:
200 		break;
201 	default:
202 		return 0;
203 	}
204 
205 	/* Cool, looks like we're good to go */
206 	return ret;
207 }
208 
209 static void
210 thinkpad_attach(device_t parent, device_t self, void *opaque)
211 {
212 	thinkpad_softc_t *sc = device_private(self);
213 	struct acpi_attach_args *aa = (struct acpi_attach_args *)opaque;
214 	struct sysmon_pswitch *psw;
215 	device_t curdev;
216 	deviter_t di;
217 	ACPI_STATUS rv;
218 	ACPI_INTEGER val;
219 	int i;
220 
221 	sc->sc_dev = self;
222 	sc->sc_powhdl = NULL;
223 	sc->sc_cmoshdl = NULL;
224 	sc->sc_node = aa->aa_node;
225 	sc->sc_display_state = THINKPAD_DISPLAY_LCD;
226 
227 	aprint_naive("\n");
228 	aprint_normal("\n");
229 
230 	sc->sc_ecdev = NULL;
231 	for (curdev = deviter_first(&di, DEVITER_F_ROOT_FIRST);
232 	     curdev != NULL; curdev = deviter_next(&di))
233 		if (device_is_a(curdev, "acpiecdt") ||
234 		    device_is_a(curdev, "acpiec")) {
235 			sc->sc_ecdev = curdev;
236 			break;
237 		}
238 	deviter_release(&di);
239 
240 	if (sc->sc_ecdev)
241 		aprint_debug_dev(self, "using EC at %s\n",
242 		    device_xname(sc->sc_ecdev));
243 
244 	/* Get the supported event mask */
245 	rv = acpi_eval_integer(sc->sc_node->ad_handle, "MHKA", &val);
246 	if (ACPI_FAILURE(rv)) {
247 		aprint_error_dev(self, "couldn't evaluate MHKA: %s\n",
248 		    AcpiFormatException(rv));
249 		goto fail;
250 	}
251 
252 	/* Enable all supported events */
253 	rv = thinkpad_mask_init(sc, val);
254 	if (ACPI_FAILURE(rv)) {
255 		aprint_error_dev(self, "couldn't set event mask: %s\n",
256 		    AcpiFormatException(rv));
257 		goto fail;
258 	}
259 
260 	(void)acpi_register_notify(sc->sc_node, thinkpad_notify_handler);
261 
262 	/*
263 	 * Obtain a handle for CMOS commands. This is used by T61.
264 	 */
265 	(void)AcpiGetHandle(NULL, "\\UCMS", &sc->sc_cmoshdl);
266 
267 	/*
268 	 * Obtain a handle to the power resource available on many models.
269 	 * Since pmf(9) is not yet integrated with the ACPI power resource
270 	 * code, this must be turned on manually upon resume. Otherwise the
271 	 * system may, for instance, resume from S3 with usb(4) powered down.
272 	 */
273 	(void)AcpiGetHandle(NULL, "\\_SB.PCI0.LPC.EC.PUBS", &sc->sc_powhdl);
274 
275 	/* Register power switches with sysmon */
276 	psw = sc->sc_smpsw;
277 	sc->sc_smpsw_valid = true;
278 
279 	psw[TP_PSW_SLEEP].smpsw_name = device_xname(self);
280 	psw[TP_PSW_SLEEP].smpsw_type = PSWITCH_TYPE_SLEEP;
281 #if notyet
282 	psw[TP_PSW_HIBERNATE].smpsw_name = device_xname(self);
283 	mpsw[TP_PSW_HIBERNATE].smpsw_type = PSWITCH_TYPE_HIBERNATE;
284 #endif
285 	for (i = TP_PSW_DISPLAY_CYCLE; i < TP_PSW_LAST; i++)
286 		sc->sc_smpsw[i].smpsw_type = PSWITCH_TYPE_HOTKEY;
287 	psw[TP_PSW_DISPLAY_CYCLE].smpsw_name = PSWITCH_HK_DISPLAY_CYCLE;
288 	psw[TP_PSW_LOCK_SCREEN].smpsw_name = PSWITCH_HK_LOCK_SCREEN;
289 	psw[TP_PSW_BATTERY_INFO].smpsw_name = PSWITCH_HK_BATTERY_INFO;
290 	psw[TP_PSW_EJECT_BUTTON].smpsw_name = PSWITCH_HK_EJECT_BUTTON;
291 	psw[TP_PSW_ZOOM_BUTTON].smpsw_name = PSWITCH_HK_ZOOM_BUTTON;
292 	psw[TP_PSW_VENDOR_BUTTON].smpsw_name = PSWITCH_HK_VENDOR_BUTTON;
293 #ifndef THINKPAD_NORMAL_HOTKEYS
294 	psw[TP_PSW_FNF1_BUTTON].smpsw_name     = PSWITCH_HK_FNF1_BUTTON;
295 	psw[TP_PSW_WIRELESS_BUTTON].smpsw_name = PSWITCH_HK_WIRELESS_BUTTON;
296 	psw[TP_PSW_WWAN_BUTTON].smpsw_name     = PSWITCH_HK_WWAN_BUTTON;
297 	psw[TP_PSW_POINTER_BUTTON].smpsw_name  = PSWITCH_HK_POINTER_BUTTON;
298 	psw[TP_PSW_FNF10_BUTTON].smpsw_name    = PSWITCH_HK_FNF10_BUTTON;
299 	psw[TP_PSW_FNF11_BUTTON].smpsw_name    = PSWITCH_HK_FNF11_BUTTON;
300 	psw[TP_PSW_BRIGHTNESS_UP].smpsw_name   = PSWITCH_HK_BRIGHTNESS_UP;
301 	psw[TP_PSW_BRIGHTNESS_DOWN].smpsw_name = PSWITCH_HK_BRIGHTNESS_DOWN;
302 	psw[TP_PSW_THINKLIGHT].smpsw_name      = PSWITCH_HK_THINKLIGHT;
303 	psw[TP_PSW_VOLUME_UP].smpsw_name       = PSWITCH_HK_VOLUME_UP;
304 	psw[TP_PSW_VOLUME_DOWN].smpsw_name     = PSWITCH_HK_VOLUME_DOWN;
305 	psw[TP_PSW_VOLUME_MUTE].smpsw_name     = PSWITCH_HK_VOLUME_MUTE;
306 	psw[TP_PSW_STAR_BUTTON].smpsw_name     = PSWITCH_HK_STAR_BUTTON;
307 	psw[TP_PSW_SCISSORS_BUTTON].smpsw_name = PSWITCH_HK_SCISSORS_BUTTON;
308 	psw[TP_PSW_BLUETOOTH_BUTTON].smpsw_name = PSWITCH_HK_BLUETOOTH_BUTTON;
309 	psw[TP_PSW_KEYBOARD_BUTTON].smpsw_name = PSWITCH_HK_KEYBOARD_BUTTON;
310 #endif /* THINKPAD_NORMAL_HOTKEYS */
311 
312 	for (i = 0; i < TP_PSW_LAST; i++) {
313 		/* not supported yet */
314 		if (i == TP_PSW_HIBERNATE)
315 			continue;
316 		if (sysmon_pswitch_register(&sc->sc_smpsw[i]) != 0) {
317 			aprint_error_dev(self,
318 			    "couldn't register with sysmon\n");
319 			sc->sc_smpsw_valid = false;
320 			break;
321 		}
322 	}
323 
324 	/* Register temperature and fan sensors with envsys */
325 	thinkpad_sensors_init(sc);
326 
327 fail:
328 	if (!pmf_device_register(self, NULL, thinkpad_resume))
329 		aprint_error_dev(self, "couldn't establish power handler\n");
330 	if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_UP,
331 	    thinkpad_brightness_up, true))
332 		aprint_error_dev(self, "couldn't register event handler\n");
333 	if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_DOWN,
334 	    thinkpad_brightness_down, true))
335 		aprint_error_dev(self, "couldn't register event handler\n");
336 }
337 
338 static int
339 thinkpad_detach(device_t self, int flags)
340 {
341 	struct thinkpad_softc *sc = device_private(self);
342 	int i;
343 
344 	acpi_deregister_notify(sc->sc_node);
345 
346 	for (i = 0; i < TP_PSW_LAST; i++)
347 		sysmon_pswitch_unregister(&sc->sc_smpsw[i]);
348 
349 	if (sc->sc_sme != NULL)
350 		sysmon_envsys_unregister(sc->sc_sme);
351 
352 	pmf_device_deregister(self);
353 
354 	pmf_event_deregister(self, PMFE_DISPLAY_BRIGHTNESS_UP,
355 	    thinkpad_brightness_up, true);
356 
357 	pmf_event_deregister(self, PMFE_DISPLAY_BRIGHTNESS_DOWN,
358 	    thinkpad_brightness_down, true);
359 
360 	return 0;
361 }
362 
363 static void
364 thinkpad_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
365 {
366 	device_t self = opaque;
367 	thinkpad_softc_t *sc;
368 
369 	sc = device_private(self);
370 
371 	if (notify != 0x80) {
372 		aprint_debug_dev(self, "unknown notify 0x%02x\n", notify);
373 		return;
374 	}
375 
376 	(void)AcpiOsExecute(OSL_NOTIFY_HANDLER, thinkpad_get_hotkeys, sc);
377 }
378 
379 SDT_PROBE_DEFINE2(sdt, thinkpad, hotkey, MHKP,
380     "struct thinkpad_softc *"/*sc*/,
381     "ACPI_INTEGER"/*val*/);
382 
383 static void
384 thinkpad_get_hotkeys(void *opaque)
385 {
386 	thinkpad_softc_t *sc = (thinkpad_softc_t *)opaque;
387 	device_t self = sc->sc_dev;
388 	ACPI_STATUS rv;
389 	ACPI_INTEGER val;
390 	int type, event;
391 
392 	for (;;) {
393 		rv = acpi_eval_integer(sc->sc_node->ad_handle, "MHKP", &val);
394 		if (ACPI_FAILURE(rv)) {
395 			aprint_error_dev(self, "couldn't evaluate MHKP: %s\n",
396 			    AcpiFormatException(rv));
397 			return;
398 		}
399 		SDT_PROBE2(sdt, thinkpad, hotkey, MHKP,  sc, val);
400 
401 		if (val == 0)
402 			return;
403 
404 		type = (val & 0xf000) >> 12;
405 		event = val & 0x0fff;
406 
407 		if (type != 1)
408 			/* Only type 1 events are supported for now */
409 			continue;
410 
411 		switch (event) {
412 		case THINKPAD_NOTIFY_BrightnessUp:
413 			thinkpad_brightness_up(self);
414 #ifndef THINKPAD_NORMAL_HOTKEYS
415 			if (sc->sc_smpsw_valid == false)
416 				break;
417 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_BRIGHTNESS_UP],
418 			    PSWITCH_EVENT_PRESSED);
419 #endif
420 			break;
421 		case THINKPAD_NOTIFY_BrightnessDown:
422 			thinkpad_brightness_down(self);
423 #ifndef THINKPAD_NORMAL_HOTKEYS
424 			if (sc->sc_smpsw_valid == false)
425 				break;
426 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_BRIGHTNESS_DOWN],
427 			    PSWITCH_EVENT_PRESSED);
428 #endif
429 			break;
430 		case THINKPAD_NOTIFY_WirelessSwitch:
431 			thinkpad_uwb_toggle(sc);
432 			thinkpad_wwan_toggle(sc);
433 			thinkpad_bluetooth_toggle(sc);
434 #ifndef THINKPAD_NORMAL_HOTKEYS
435 			if (sc->sc_smpsw_valid == false)
436 				break;
437 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_WIRELESS_BUTTON],
438 			    PSWITCH_EVENT_PRESSED);
439 #endif
440 			break;
441 		case THINKPAD_NOTIFY_Bluetooth:
442 			thinkpad_bluetooth_toggle(sc);
443 #ifndef THINKPAD_NORMAL_HOTKEYS
444 			if (sc->sc_smpsw_valid == false)
445 				break;
446 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_BLUETOOTH_BUTTON],
447 			    PSWITCH_EVENT_PRESSED);
448 #endif
449 			break;
450 		case THINKPAD_NOTIFY_wWANSwitch:
451 			thinkpad_wwan_toggle(sc);
452 #ifndef THINKPAD_NORMAL_HOTKEYS
453 			if (sc->sc_smpsw_valid == false)
454 				break;
455 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_WWAN_BUTTON],
456 			    PSWITCH_EVENT_PRESSED);
457 #endif
458 			break;
459 		case THINKPAD_NOTIFY_SleepButton:
460 			if (sc->sc_smpsw_valid == false)
461 				break;
462 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_SLEEP],
463 			    PSWITCH_EVENT_PRESSED);
464 			break;
465 		case THINKPAD_NOTIFY_HibernateButton:
466 #if notyet
467 			if (sc->sc_smpsw_valid == false)
468 				break;
469 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_HIBERNATE],
470 			    PSWITCH_EVENT_PRESSED);
471 #endif
472 			break;
473 		case THINKPAD_NOTIFY_DisplayCycle:
474 			if (sc->sc_smpsw_valid == false)
475 				break;
476 			sysmon_pswitch_event(
477 			    &sc->sc_smpsw[TP_PSW_DISPLAY_CYCLE],
478 			    PSWITCH_EVENT_PRESSED);
479 			break;
480 		case THINKPAD_NOTIFY_LockScreen:
481 			if (sc->sc_smpsw_valid == false)
482 				break;
483 			sysmon_pswitch_event(
484 			    &sc->sc_smpsw[TP_PSW_LOCK_SCREEN],
485 			    PSWITCH_EVENT_PRESSED);
486 			break;
487 		case THINKPAD_NOTIFY_BatteryInfo:
488 			if (sc->sc_smpsw_valid == false)
489 				break;
490 			sysmon_pswitch_event(
491 			    &sc->sc_smpsw[TP_PSW_BATTERY_INFO],
492 			    PSWITCH_EVENT_PRESSED);
493 			break;
494 		case THINKPAD_NOTIFY_EjectButton:
495 			if (sc->sc_smpsw_valid == false)
496 				break;
497 			sysmon_pswitch_event(
498 			    &sc->sc_smpsw[TP_PSW_EJECT_BUTTON],
499 			    PSWITCH_EVENT_PRESSED);
500 			break;
501 		case THINKPAD_NOTIFY_Zoom:
502 			if (sc->sc_smpsw_valid == false)
503 				break;
504 			sysmon_pswitch_event(
505 			    &sc->sc_smpsw[TP_PSW_ZOOM_BUTTON],
506 			    PSWITCH_EVENT_PRESSED);
507 			break;
508 		case THINKPAD_NOTIFY_ThinkVantage:
509 			if (sc->sc_smpsw_valid == false)
510 				break;
511 			sysmon_pswitch_event(
512 			    &sc->sc_smpsw[TP_PSW_VENDOR_BUTTON],
513 			    PSWITCH_EVENT_PRESSED);
514 			break;
515 #ifndef THINKPAD_NORMAL_HOTKEYS
516 		case THINKPAD_NOTIFY_FnF1:
517 			if (sc->sc_smpsw_valid == false)
518 				break;
519 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_FNF1_BUTTON],
520 			    PSWITCH_EVENT_PRESSED);
521 			break;
522 		case THINKPAD_NOTIFY_PointerSwitch:
523 			if (sc->sc_smpsw_valid == false)
524 				break;
525 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_POINTER_BUTTON],
526 			    PSWITCH_EVENT_PRESSED);
527 			break;
528 		case THINKPAD_NOTIFY_FnF11:
529 			if (sc->sc_smpsw_valid == false)
530 				break;
531 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_FNF11_BUTTON],
532 			    PSWITCH_EVENT_PRESSED);
533 			break;
534 		case THINKPAD_NOTIFY_ThinkLight:
535 			if (sc->sc_smpsw_valid == false)
536 				break;
537 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_THINKLIGHT],
538 			    PSWITCH_EVENT_PRESSED);
539 			break;
540 		/*
541 		 * For some reason the next four aren't seen on my T61.
542 		 */
543 		case THINKPAD_NOTIFY_FnF10:
544 			if (sc->sc_smpsw_valid == false)
545 				break;
546 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_FNF10_BUTTON],
547 			    PSWITCH_EVENT_PRESSED);
548 			break;
549 		case THINKPAD_NOTIFY_VolumeUp:
550 			if (sc->sc_smpsw_valid == false)
551 				break;
552 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_VOLUME_UP],
553 			    PSWITCH_EVENT_PRESSED);
554 			break;
555 		case THINKPAD_NOTIFY_VolumeDown:
556 			if (sc->sc_smpsw_valid == false)
557 				break;
558 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_VOLUME_DOWN],
559 			    PSWITCH_EVENT_PRESSED);
560 			break;
561 		case THINKPAD_NOTIFY_VolumeMute:
562 			if (sc->sc_smpsw_valid == false)
563 				break;
564 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_VOLUME_MUTE],
565 			    PSWITCH_EVENT_PRESSED);
566 			break;
567 		case THINKPAD_NOTIFY_Star:
568 			if (sc->sc_smpsw_valid == false)
569 				break;
570 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_STAR_BUTTON],
571 			    PSWITCH_EVENT_PRESSED);
572 			break;
573 		case THINKPAD_NOTIFY_Scissors:
574 			if (sc->sc_smpsw_valid == false)
575 				break;
576 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_SCISSORS_BUTTON],
577 			    PSWITCH_EVENT_PRESSED);
578 			break;
579 		case THINKPAD_NOTIFY_Keyboard:
580 			if (sc->sc_smpsw_valid == false)
581 				break;
582 			sysmon_pswitch_event(&sc->sc_smpsw[TP_PSW_KEYBOARD_BUTTON],
583 			    PSWITCH_EVENT_PRESSED);
584 			break;
585 #else
586 		case THINKPAD_NOTIFY_FnF1:
587 		case THINKPAD_NOTIFY_PointerSwitch:
588 		case THINKPAD_NOTIFY_FnF10:
589 		case THINKPAD_NOTIFY_FnF11:
590 		case THINKPAD_NOTIFY_ThinkLight:
591 		case THINKPAD_NOTIFY_VolumeUp:
592 		case THINKPAD_NOTIFY_VolumeDown:
593 		case THINKPAD_NOTIFY_VolumeMute:
594 		case THINKPAD_NOTIFY_Star:
595 		case THINKPAD_NOTIFY_Scissors:
596 		case THINKPAD_NOTIFY_Keyboard:
597 			/* XXXJDM we should deliver hotkeys as keycodes */
598 			break;
599 #endif /* THINKPAD_NORMAL_HOTKEYS */
600 		default:
601 			aprint_debug_dev(self, "notify event 0x%03x\n", event);
602 			break;
603 		}
604 	}
605 }
606 
607 static ACPI_STATUS
608 thinkpad_mask_init(thinkpad_softc_t *sc, uint32_t mask)
609 {
610 	ACPI_OBJECT param[2];
611 	ACPI_OBJECT_LIST params;
612 	ACPI_STATUS rv;
613 	int i;
614 
615 	/* Update hotkey mask */
616 	params.Count = 2;
617 	params.Pointer = param;
618 	param[0].Type = param[1].Type = ACPI_TYPE_INTEGER;
619 
620 	for (i = 0; i < 32; i++) {
621 		param[0].Integer.Value = i + 1;
622 		param[1].Integer.Value = ((__BIT(i) & mask) != 0);
623 
624 		rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "MHKM",
625 		    &params, NULL);
626 		if (ACPI_FAILURE(rv))
627 			return rv;
628 	}
629 
630 	/* Enable hotkey events */
631 	rv = acpi_eval_set_integer(sc->sc_node->ad_handle, "MHKC", 1);
632 	if (ACPI_FAILURE(rv)) {
633 		aprint_error_dev(sc->sc_dev, "couldn't enable hotkeys: %s\n",
634 		    AcpiFormatException(rv));
635 		return rv;
636 	}
637 
638 	/* Claim ownership of brightness control */
639 	(void)acpi_eval_set_integer(sc->sc_node->ad_handle, "PWMS", 0);
640 
641 	return AE_OK;
642 }
643 
644 static void
645 thinkpad_sensors_init(thinkpad_softc_t *sc)
646 {
647 	int i, j;
648 
649 	if (sc->sc_ecdev == NULL)
650 		return;	/* no chance of this working */
651 
652 	sc->sc_sme = sysmon_envsys_create();
653 
654 	for (i = j = 0; i < THINKPAD_NTEMPSENSORS; i++) {
655 
656 		sc->sc_sensor[i].units = ENVSYS_STEMP;
657 		sc->sc_sensor[i].state = ENVSYS_SINVALID;
658 		sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
659 
660 		(void)snprintf(sc->sc_sensor[i].desc,
661 		    sizeof(sc->sc_sensor[i].desc), "temperature %d", i);
662 
663 		if (sysmon_envsys_sensor_attach(sc->sc_sme,
664 			&sc->sc_sensor[i]) != 0)
665 			goto fail;
666 	}
667 
668 	for (i = THINKPAD_NTEMPSENSORS; i < THINKPAD_NSENSORS; i++, j++) {
669 
670 		sc->sc_sensor[i].units = ENVSYS_SFANRPM;
671 		sc->sc_sensor[i].state = ENVSYS_SINVALID;
672 		sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
673 
674 		(void)snprintf(sc->sc_sensor[i].desc,
675 		    sizeof(sc->sc_sensor[i].desc), "fan speed %d", j);
676 
677 		if (sysmon_envsys_sensor_attach(sc->sc_sme,
678 			&sc->sc_sensor[i]) != 0)
679 			goto fail;
680 	}
681 
682 	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
683 	sc->sc_sme->sme_cookie = sc;
684 	sc->sc_sme->sme_refresh = thinkpad_sensors_refresh;
685 
686 	if (sysmon_envsys_register(sc->sc_sme) != 0)
687 		goto fail;
688 
689 	return;
690 
691 fail:
692 	aprint_error_dev(sc->sc_dev, "failed to initialize sysmon\n");
693 	sysmon_envsys_destroy(sc->sc_sme);
694 	sc->sc_sme = NULL;
695 }
696 
697 static void
698 thinkpad_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
699 {
700 	switch (edata->units) {
701 	case ENVSYS_STEMP:
702 		thinkpad_temp_refresh(sme, edata);
703 		break;
704 	case ENVSYS_SFANRPM:
705 		thinkpad_fan_refresh(sme, edata);
706 		break;
707 	default:
708 		break;
709 	}
710 }
711 
712 static void
713 thinkpad_temp_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
714 {
715 	thinkpad_softc_t *sc = sme->sme_cookie;
716 	char sname[5] = "TMP?";
717 	ACPI_INTEGER val;
718 	ACPI_STATUS rv;
719 	int temp;
720 
721 	sname[3] = '0' + edata->sensor;
722 	rv = acpi_eval_integer(acpiec_get_handle(sc->sc_ecdev), sname, &val);
723 	if (ACPI_FAILURE(rv)) {
724 		edata->state = ENVSYS_SINVALID;
725 		return;
726 	}
727 	temp = (int)val;
728 	if (temp > 127 || temp < -127) {
729 		edata->state = ENVSYS_SINVALID;
730 		return;
731 	}
732 
733 	edata->value_cur = temp * 1000000 + 273150000;
734 	edata->state = ENVSYS_SVALID;
735 }
736 
737 static void
738 thinkpad_fan_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
739 {
740 	thinkpad_softc_t *sc = sme->sme_cookie;
741 	ACPI_INTEGER lo;
742 	ACPI_INTEGER hi;
743 	ACPI_STATUS rv;
744 	int rpm;
745 
746 	/*
747 	 * Read the low byte first to avoid a firmware bug.
748 	 */
749 	rv = acpiec_bus_read(sc->sc_ecdev, 0x84, &lo, 1);
750 	if (ACPI_FAILURE(rv)) {
751 		edata->state = ENVSYS_SINVALID;
752 		return;
753 	}
754 	rv = acpiec_bus_read(sc->sc_ecdev, 0x85, &hi, 1);
755 	if (ACPI_FAILURE(rv)) {
756 		edata->state = ENVSYS_SINVALID;
757 		return;
758 	}
759 
760 	/*
761 	 * Extract the low bytes from buffers
762 	 */
763 	lo = ((uint8_t *)&lo)[0];
764 	hi = ((uint8_t *)&hi)[0];
765 
766 	rpm = ((((int)hi) << 8) | ((int)lo));
767 	if (rpm < 0) {
768 		edata->state = ENVSYS_SINVALID;
769 		return;
770 	}
771 
772 	edata->value_cur = rpm;
773 	edata->state = ENVSYS_SVALID;
774 }
775 
776 static void
777 thinkpad_bluetooth_toggle(thinkpad_softc_t *sc)
778 {
779 	ACPI_BUFFER buf;
780 	ACPI_OBJECT retobj;
781 	ACPI_OBJECT param[1];
782 	ACPI_OBJECT_LIST params;
783 	ACPI_STATUS rv;
784 
785 	/* Ignore return value, as the hardware may not support bluetooth */
786 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "BTGL", NULL, NULL);
787 	if (!ACPI_FAILURE(rv))
788 		return;
789 
790 	buf.Pointer = &retobj;
791 	buf.Length = sizeof(retobj);
792 
793 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "GBDC", NULL, &buf);
794 	if (ACPI_FAILURE(rv))
795 		return;
796 
797 	params.Count = 1;
798 	params.Pointer = param;
799 	param[0].Type = ACPI_TYPE_INTEGER;
800 	param[0].Integer.Value =
801 		(retobj.Integer.Value & THINKPAD_BLUETOOTH_RADIOSSW) == 0
802 		? THINKPAD_BLUETOOTH_RADIOSSW | THINKPAD_BLUETOOTH_RESUMECTRL
803 		: 0;
804 
805 	(void)AcpiEvaluateObject(sc->sc_node->ad_handle, "SBDC", &params, NULL);
806 }
807 
808 static void
809 thinkpad_wwan_toggle(thinkpad_softc_t *sc)
810 {
811 	ACPI_BUFFER buf;
812 	ACPI_OBJECT retobj;
813 	ACPI_OBJECT param[1];
814 	ACPI_OBJECT_LIST params;
815 	ACPI_STATUS rv;
816 
817 	buf.Pointer = &retobj;
818 	buf.Length = sizeof(retobj);
819 
820 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "GWAN", NULL, &buf);
821 	if (ACPI_FAILURE(rv))
822 		return;
823 
824 	params.Count = 1;
825 	params.Pointer = param;
826 	param[0].Type = ACPI_TYPE_INTEGER;
827 	param[0].Integer.Value =
828 		(retobj.Integer.Value & THINKPAD_WWAN_RADIOSSW) == 0
829 		? THINKPAD_WWAN_RADIOSSW | THINKPAD_WWAN_RESUMECTRL
830 		: 0;
831 
832 	(void)AcpiEvaluateObject(sc->sc_node->ad_handle, "SWAN", &params, NULL);
833 }
834 
835 static void
836 thinkpad_uwb_toggle(thinkpad_softc_t *sc)
837 {
838 	ACPI_BUFFER buf;
839 	ACPI_OBJECT retobj;
840 	ACPI_OBJECT param[1];
841 	ACPI_OBJECT_LIST params;
842 	ACPI_STATUS rv;
843 
844 	buf.Pointer = &retobj;
845 	buf.Length = sizeof(retobj);
846 
847 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "GUWB", NULL, &buf);
848 	if (ACPI_FAILURE(rv))
849 		return;
850 
851 	params.Count = 1;
852 	params.Pointer = param;
853 	param[0].Type = ACPI_TYPE_INTEGER;
854 	param[0].Integer.Value =
855 		(retobj.Integer.Value & THINKPAD_UWB_RADIOSSW) == 0
856 		? THINKPAD_UWB_RADIOSSW
857 		: 0;
858 
859 	(void)AcpiEvaluateObject(sc->sc_node->ad_handle, "SUWB", &params, NULL);
860 }
861 
862 static uint8_t
863 thinkpad_brightness_read(thinkpad_softc_t *sc)
864 {
865 	uint32_t val = 0;
866 
867 	AcpiOsWritePort(IO_RTC, 0x6c, 8);
868 	AcpiOsReadPort(IO_RTC + 1, &val, 8);
869 
870 	return val & 7;
871 }
872 
873 static void
874 thinkpad_brightness_up(device_t self)
875 {
876 	thinkpad_softc_t *sc = device_private(self);
877 
878 	if (thinkpad_brightness_read(sc) == 7)
879 		return;
880 
881 	thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP);
882 }
883 
884 static void
885 thinkpad_brightness_down(device_t self)
886 {
887 	thinkpad_softc_t *sc = device_private(self);
888 
889 	if (thinkpad_brightness_read(sc) == 0)
890 		return;
891 
892 	thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN);
893 }
894 
895 static void
896 thinkpad_cmos(thinkpad_softc_t *sc, uint8_t cmd)
897 {
898 	ACPI_STATUS rv;
899 
900 	if (sc->sc_cmoshdl == NULL)
901 		return;
902 
903 	rv = acpi_eval_set_integer(sc->sc_cmoshdl, NULL, cmd);
904 
905 	if (ACPI_FAILURE(rv))
906 		aprint_error_dev(sc->sc_dev, "couldn't evaluate CMOS: %s\n",
907 		    AcpiFormatException(rv));
908 }
909 
910 static bool
911 thinkpad_resume(device_t dv, const pmf_qual_t *qual)
912 {
913 	thinkpad_softc_t *sc = device_private(dv);
914 
915 	if (sc->sc_powhdl == NULL)
916 		return true;
917 
918 	(void)acpi_power_res(sc->sc_powhdl, sc->sc_node->ad_handle, true);
919 
920 	return true;
921 }
922 
923 MODULE(MODULE_CLASS_DRIVER, thinkpad, "sysmon_envsys,sysmon_power");
924 
925 #ifdef _MODULE
926 #include "ioconf.c"
927 #endif
928 
929 static int
930 thinkpad_modcmd(modcmd_t cmd, void *aux)
931 {
932 	int rv = 0;
933 
934 	switch (cmd) {
935 
936 	case MODULE_CMD_INIT:
937 
938 #ifdef _MODULE
939 		rv = config_init_component(cfdriver_ioconf_thinkpad,
940 		    cfattach_ioconf_thinkpad, cfdata_ioconf_thinkpad);
941 #endif
942 		break;
943 
944 	case MODULE_CMD_FINI:
945 
946 #ifdef _MODULE
947 		rv = config_fini_component(cfdriver_ioconf_thinkpad,
948 		    cfattach_ioconf_thinkpad, cfdata_ioconf_thinkpad);
949 #endif
950 		break;
951 
952 	default:
953 		rv = ENOTTY;
954 	}
955 
956 	return rv;
957 }
958