1 | /* $NetBSD: wmi_eeepc.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2011 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Jukka Ruohonen. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * |
14 | * 1. Redistributions of source code must retain the above copyright |
15 | * notice, this list of conditions and the following disclaimer. |
16 | * 2. Redistributions in binary form must reproduce the above copyright |
17 | * notice, this list of conditions and the following disclaimer in the |
18 | * documentation and/or other materials provided with the distribution. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
30 | * SUCH DAMAGE. |
31 | */ |
32 | |
33 | #include <sys/cdefs.h> |
34 | __KERNEL_RCSID(0, "$NetBSD: wmi_eeepc.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $" ); |
35 | |
36 | #include <sys/param.h> |
37 | #include <sys/device.h> |
38 | #include <sys/module.h> |
39 | |
40 | #include <dev/acpi/acpireg.h> |
41 | #include <dev/acpi/acpivar.h> |
42 | #include <dev/acpi/wmi/wmi_acpivar.h> |
43 | |
44 | #include <dev/sysmon/sysmonvar.h> |
45 | |
46 | #define _COMPONENT ACPI_RESOURCE_COMPONENT |
47 | ACPI_MODULE_NAME ("wmi_eeepc" ) |
48 | |
49 | |
50 | /* |
51 | * Current brightness is reported by events in the range of 0x10 to 0x2f. |
52 | * The low nibble containing a value from 0x0 to 0xA proportionate to |
53 | * brightness, 0x0 indicates minimum illumination, not backlight off. |
54 | */ |
55 | #define WMI_EEEPC_HK_BACKLIGHT_STATUS_BRIGHTNESS_MASK 0x0f /* 0x0 to 0xA */ |
56 | #define WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_MASK (~WMI_EEEPC_HK_BACKLIGHT_STATUS_BRIGHTNESS_MASK) |
57 | #define WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_INC 0x10 |
58 | #define WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_DEC 0x20 |
59 | |
60 | #define WMI_EEEPC_HK_VOLUME_UP 0x30 |
61 | #define WMI_EEEPC_HK_VOLUME_DOWN 0x31 |
62 | #define WMI_EEEPC_HK_VOLUME_MUTE 0x32 |
63 | #define WMI_EEEPC_HK_EXPRESSGATE 0x5C /* Also, "SuperHybrid" */ |
64 | #define WMI_EEEPC_HK_TOUCHPAD 0x6B |
65 | #define WMI_EEEPC_HK_WIRELESS 0x88 |
66 | #define WMI_EEEPC_HK_WEBCAM 0xBD |
67 | #define WMI_EEEPC_HK_DISPLAY_CYCLE 0xCC |
68 | #define WMI_EEEPC_HK_DISPLAY_SAVER 0xE8 |
69 | #define WMI_EEEPC_HK_DISPLAY_OFF 0xE9 |
70 | /* Unlabeled keys on 1215T */ |
71 | #define WMI_EEEPC_HK_UNKNOWN_xEC 0xEC /* Fn+E */ |
72 | #define WMI_EEEPC_HK_UNKNOWN_xED 0xED /* Fn+D */ |
73 | #define WMI_EEEPC_HK_UNKNOWN_xEE 0xEE /* Fn+S */ |
74 | #define WMI_EEEPC_HK_UNKNOWN_xEF 0xEF /* Fn+F */ |
75 | |
76 | #define WMI_EEEPC_IS_BACKLIGHT_STATUS(x) (((x & WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_MASK) == WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_INC) || ((x & WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_MASK) == WMI_EEEPC_HK_BACKLIGHT_STATUS_DIRECTION_DEC)) |
77 | |
78 | enum eeepc_smpsw { |
79 | WMI_EEEPC_PSW_EXPRESSGATE = 0, |
80 | WMI_EEEPC_PSW_TOUCHPAD, |
81 | WMI_EEEPC_PSW_WIRELESS, |
82 | WMI_EEEPC_PSW_WEBCAM, |
83 | WMI_EEEPC_PSW_DISPLAY_CYCLE, |
84 | WMI_EEEPC_PSW_DISPLAY_SAVER, |
85 | WMI_EEEPC_PSW_DISPLAY_OFF, |
86 | WMI_EEEPC_PSW_UNKNOWN_xEC, |
87 | WMI_EEEPC_PSW_UNKNOWN_xED, |
88 | WMI_EEEPC_PSW_UNKNOWN_xEE, |
89 | WMI_EEEPC_PSW_UNKNOWN_xEF, |
90 | WMI_EEEPC_PSW_COUNT /* Must be last. */ |
91 | }; |
92 | |
93 | #define WMI_EEEPC_GUID_EVENT "ABBC0F72-8EA1-11D1-00A0-C90629100000" |
94 | |
95 | struct wmi_eeepc_softc { |
96 | device_t sc_dev; |
97 | device_t sc_parent; |
98 | struct sysmon_pswitch sc_psw[WMI_EEEPC_PSW_COUNT]; |
99 | bool sc_psw_valid[WMI_EEEPC_PSW_COUNT]; |
100 | }; |
101 | |
102 | static int wmi_eeepc_match(device_t, cfdata_t, void *); |
103 | static void wmi_eeepc_attach(device_t, device_t, void *); |
104 | static int wmi_eeepc_detach(device_t, int); |
105 | static void wmi_eeepc_notify_handler(ACPI_HANDLE, uint32_t, void *); |
106 | static bool wmi_eeepc_suspend(device_t, const pmf_qual_t *); |
107 | static bool wmi_eeepc_resume(device_t, const pmf_qual_t *); |
108 | |
109 | CFATTACH_DECL_NEW(wmieeepc, sizeof(struct wmi_eeepc_softc), |
110 | wmi_eeepc_match, wmi_eeepc_attach, wmi_eeepc_detach, NULL); |
111 | |
112 | static int |
113 | wmi_eeepc_match(device_t parent, cfdata_t match, void *aux) |
114 | { |
115 | return acpi_wmi_guid_match(parent, WMI_EEEPC_GUID_EVENT); |
116 | } |
117 | |
118 | static void |
119 | wmi_eeepc_attach(device_t parent, device_t self, void *aux) |
120 | { |
121 | struct wmi_eeepc_softc *sc = device_private(self); |
122 | ACPI_STATUS rv; |
123 | |
124 | sc->sc_dev = self; |
125 | sc->sc_parent = parent; |
126 | |
127 | rv = acpi_wmi_event_register(parent, wmi_eeepc_notify_handler); |
128 | |
129 | if (ACPI_FAILURE(rv)) { |
130 | aprint_error(": failed to install WMI notify handler\n" ); |
131 | return; |
132 | } |
133 | |
134 | aprint_naive("\n" ); |
135 | aprint_normal(": Asus Eee PC WMI mappings\n" ); |
136 | |
137 | memset(sc->sc_psw, 0, sizeof(sc->sc_psw)); |
138 | sc->sc_psw[WMI_EEEPC_PSW_EXPRESSGATE].smpsw_name = "expressgate" ; |
139 | sc->sc_psw[WMI_EEEPC_PSW_TOUCHPAD].smpsw_name = "touchpad-toggle" ; |
140 | sc->sc_psw[WMI_EEEPC_PSW_WIRELESS].smpsw_name = "wireless-toggle" ; |
141 | sc->sc_psw[WMI_EEEPC_PSW_WEBCAM].smpsw_name = "camera-button" ; |
142 | sc->sc_psw[WMI_EEEPC_PSW_DISPLAY_CYCLE].smpsw_name = PSWITCH_HK_DISPLAY_CYCLE; |
143 | sc->sc_psw[WMI_EEEPC_PSW_DISPLAY_SAVER].smpsw_name = PSWITCH_HK_LOCK_SCREEN; |
144 | sc->sc_psw[WMI_EEEPC_PSW_DISPLAY_OFF].smpsw_name = "display-off" ; |
145 | sc->sc_psw[WMI_EEEPC_PSW_UNKNOWN_xEC].smpsw_name = "unlabeled-xEC" ; |
146 | sc->sc_psw[WMI_EEEPC_PSW_UNKNOWN_xED].smpsw_name = "unlabeled-xED" ; |
147 | sc->sc_psw[WMI_EEEPC_PSW_UNKNOWN_xEE].smpsw_name = "unlabeled-xEE" ; |
148 | sc->sc_psw[WMI_EEEPC_PSW_UNKNOWN_xEF].smpsw_name = "unlabeled-xEF" ; |
149 | |
150 | for (int i = 0; i < WMI_EEEPC_PSW_COUNT; i++) { |
151 | KASSERT(sc->sc_psw[i].smpsw_name != NULL); |
152 | sc->sc_psw[i].smpsw_type = PSWITCH_TYPE_HOTKEY; |
153 | if (sysmon_pswitch_register(&sc->sc_psw[i]) == 0) { |
154 | sc->sc_psw_valid[i] = true; |
155 | } else { |
156 | sc->sc_psw_valid[i] = false; |
157 | aprint_error_dev(self, |
158 | "hotkey[%d] registration failed\n" , i); |
159 | } |
160 | } |
161 | |
162 | (void)pmf_device_register(self, wmi_eeepc_suspend, wmi_eeepc_resume); |
163 | } |
164 | |
165 | static int |
166 | wmi_eeepc_detach(device_t self, int flags) |
167 | { |
168 | struct wmi_eeepc_softc *sc = device_private(self); |
169 | device_t parent = sc->sc_parent; |
170 | |
171 | |
172 | for (int i = 0; i < WMI_EEEPC_PSW_COUNT; i++) { |
173 | if (sc->sc_psw_valid[i] == true) { |
174 | sysmon_pswitch_unregister(&sc->sc_psw[i]); |
175 | sc->sc_psw_valid[i] = false; |
176 | } |
177 | } |
178 | |
179 | (void)pmf_device_deregister(self); |
180 | (void)acpi_wmi_event_deregister(parent); |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static bool |
186 | wmi_eeepc_suspend(device_t self, const pmf_qual_t *qual) |
187 | { |
188 | struct wmi_eeepc_softc *sc = device_private(self); |
189 | device_t parent = sc->sc_parent; |
190 | |
191 | (void)acpi_wmi_event_deregister(parent); |
192 | |
193 | return true; |
194 | } |
195 | |
196 | static bool |
197 | wmi_eeepc_resume(device_t self, const pmf_qual_t *qual) |
198 | { |
199 | struct wmi_eeepc_softc *sc = device_private(self); |
200 | device_t parent = sc->sc_parent; |
201 | |
202 | (void)acpi_wmi_event_register(parent, wmi_eeepc_notify_handler); |
203 | |
204 | return true; |
205 | } |
206 | |
207 | static void |
208 | wmi_eeepc_pswitch_event(struct wmi_eeepc_softc *sc, enum eeepc_smpsw key) |
209 | { |
210 | if (sc->sc_psw_valid[key] != true) { |
211 | device_printf(sc->sc_dev, "hotkey[%d] not registered\n" , key); |
212 | return; |
213 | } |
214 | |
215 | /* |
216 | * This function is called upon key release, |
217 | * but the default powerd scripts expect presses. |
218 | * |
219 | * Anyway, we may as well send the make and the break event. |
220 | */ |
221 | |
222 | sysmon_pswitch_event(&sc->sc_psw[key], PSWITCH_EVENT_PRESSED); |
223 | sysmon_pswitch_event(&sc->sc_psw[key], PSWITCH_EVENT_RELEASED); |
224 | } |
225 | |
226 | static void |
227 | wmi_eeepc_notify_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux) |
228 | { |
229 | struct wmi_eeepc_softc *sc; |
230 | device_t self = aux; |
231 | ACPI_OBJECT *obj; |
232 | ACPI_BUFFER buf; |
233 | ACPI_STATUS rv; |
234 | uint32_t val = 0; /* XXX GCC */ |
235 | |
236 | buf.Pointer = NULL; |
237 | |
238 | sc = device_private(self); |
239 | rv = acpi_wmi_event_get(sc->sc_parent, evt, &buf); |
240 | |
241 | if (ACPI_FAILURE(rv)) |
242 | goto out; |
243 | |
244 | obj = buf.Pointer; |
245 | |
246 | if (obj->Type != ACPI_TYPE_INTEGER) { |
247 | rv = AE_TYPE; |
248 | goto out; |
249 | } |
250 | |
251 | if (obj->Integer.Value > UINT32_MAX) { |
252 | rv = AE_LIMIT; |
253 | goto out; |
254 | } |
255 | |
256 | val = obj->Integer.Value; |
257 | |
258 | if (WMI_EEEPC_IS_BACKLIGHT_STATUS(val)) { |
259 | /* We'll silently ignore these for now. */ |
260 | goto out; |
261 | } |
262 | |
263 | switch (val) { |
264 | |
265 | case WMI_EEEPC_HK_VOLUME_UP: |
266 | pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_UP); |
267 | break; |
268 | |
269 | case WMI_EEEPC_HK_VOLUME_DOWN: |
270 | pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_DOWN); |
271 | break; |
272 | |
273 | case WMI_EEEPC_HK_VOLUME_MUTE: |
274 | pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_TOGGLE); |
275 | break; |
276 | |
277 | case WMI_EEEPC_HK_EXPRESSGATE: |
278 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_EXPRESSGATE); |
279 | break; |
280 | |
281 | case WMI_EEEPC_HK_TOUCHPAD: |
282 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_TOUCHPAD); |
283 | break; |
284 | |
285 | case WMI_EEEPC_HK_WIRELESS: |
286 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_WIRELESS); |
287 | break; |
288 | |
289 | case WMI_EEEPC_HK_WEBCAM: |
290 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_WEBCAM); |
291 | break; |
292 | |
293 | case WMI_EEEPC_HK_DISPLAY_CYCLE: |
294 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_DISPLAY_CYCLE); |
295 | break; |
296 | |
297 | case WMI_EEEPC_HK_DISPLAY_SAVER: |
298 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_DISPLAY_SAVER); |
299 | break; |
300 | |
301 | case WMI_EEEPC_HK_DISPLAY_OFF: |
302 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_DISPLAY_OFF); |
303 | break; |
304 | |
305 | case WMI_EEEPC_HK_UNKNOWN_xEC: |
306 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_UNKNOWN_xEC); |
307 | break; |
308 | |
309 | case WMI_EEEPC_HK_UNKNOWN_xED: |
310 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_UNKNOWN_xED); |
311 | break; |
312 | |
313 | case WMI_EEEPC_HK_UNKNOWN_xEE: |
314 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_UNKNOWN_xEE); |
315 | break; |
316 | |
317 | case WMI_EEEPC_HK_UNKNOWN_xEF: |
318 | wmi_eeepc_pswitch_event(sc, WMI_EEEPC_PSW_UNKNOWN_xEF); |
319 | break; |
320 | |
321 | default: |
322 | device_printf(self, "unknown key 0x%02X for event 0x%02X\n" , |
323 | val, evt); |
324 | break; |
325 | } |
326 | |
327 | out: |
328 | if (buf.Pointer != NULL) |
329 | ACPI_FREE(buf.Pointer); |
330 | |
331 | if (ACPI_FAILURE(rv)) |
332 | device_printf(self, "failed to get data for " |
333 | "event 0x%02X: %s\n" , evt, AcpiFormatException(rv)); |
334 | } |
335 | |
336 | MODULE(MODULE_CLASS_DRIVER, wmieeepc, "acpiwmi,sysmon_power" ); |
337 | |
338 | #ifdef _MODULE |
339 | #include "ioconf.c" |
340 | #endif |
341 | |
342 | static int |
343 | wmieeepc_modcmd(modcmd_t cmd, void *aux) |
344 | { |
345 | int rv = 0; |
346 | |
347 | switch (cmd) { |
348 | |
349 | case MODULE_CMD_INIT: |
350 | |
351 | #ifdef _MODULE |
352 | rv = config_init_component(cfdriver_ioconf_wmieeepc, |
353 | cfattach_ioconf_wmieeepc, cfdata_ioconf_wmieeepc); |
354 | #endif |
355 | break; |
356 | |
357 | case MODULE_CMD_FINI: |
358 | |
359 | #ifdef _MODULE |
360 | rv = config_fini_component(cfdriver_ioconf_wmieeepc, |
361 | cfattach_ioconf_wmieeepc, cfdata_ioconf_wmieeepc); |
362 | #endif |
363 | break; |
364 | |
365 | default: |
366 | rv = ENOTTY; |
367 | } |
368 | |
369 | return rv; |
370 | } |
371 | |