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