1 /* $NetBSD: wmi_dell.c,v 1.11 2017/12/03 23:43:00 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2009, 2010 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_dell.c,v 1.11 2017/12/03 23:43:00 christos 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 #ifdef WMI_DEBUG 47 #define DPRINTF(x) printf x 48 #else 49 #define DPRINTF(x) 50 #endif 51 52 #define _COMPONENT ACPI_RESOURCE_COMPONENT 53 ACPI_MODULE_NAME ("wmi_dell") 54 55 #define WMI_DELL_PSW_DISPLAY_CYCLE 0 56 #define WMI_DELL_PSW_COUNT 1 57 58 #define WMI_DELL_GUID_EVENT "9DBB5994-A997-11DA-B012-B622A1EF5492" 59 #define WMI_DELL_GUID_DESC "8D9DDCBC-A997-11DA-B012-B622A1EF5492" 60 61 struct wmi_dell_softc { 62 device_t sc_dev; 63 device_t sc_parent; 64 int sc_version; 65 struct sysmon_pswitch sc_smpsw[WMI_DELL_PSW_COUNT]; 66 bool sc_smpsw_valid; 67 }; 68 69 #define WMI_DELLA_PMF 0x0 70 #define WMI_DELLA_PSW 0x1 71 #define WMI_DELLA_IGN 0x2 72 73 const struct wmi_dell_actions { 74 u_int wda_action; 75 u_int wda_type; 76 u_int wda_subtype; 77 u_int wda_data; 78 } wmi_dell_actions[] = { 79 /* type 0 */ 80 /* brightness control */ 81 {WMI_DELLA_PMF, 0x0000, 0xe005, PMFE_DISPLAY_BRIGHTNESS_DOWN}, 82 {WMI_DELLA_PMF, 0x0000, 0xe006, PMFE_DISPLAY_BRIGHTNESS_UP}, 83 {WMI_DELLA_PSW, 0x0000, 0xe00b, WMI_DELL_PSW_DISPLAY_CYCLE}, 84 85 {WMI_DELLA_PMF, 0x0000, 0xe008, PMFE_RADIO_TOGGLE}, 86 {WMI_DELLA_IGN, 0x0000, 0xe00c, 0}, /* keyboard illumination */ 87 88 /* volume control */ 89 {WMI_DELLA_PMF, 0x0000, 0xe020, PMFE_AUDIO_VOLUME_TOGGLE}, 90 {WMI_DELLA_PMF, 0x0000, 0xe02e, PMFE_AUDIO_VOLUME_DOWN}, 91 {WMI_DELLA_PMF, 0x0000, 0xe030, PMFE_AUDIO_VOLUME_UP}, 92 {WMI_DELLA_PMF, 0x0000, 0xe0f8, PMFE_AUDIO_VOLUME_DOWN}, 93 {WMI_DELLA_PMF, 0x0000, 0xe0f9, PMFE_AUDIO_VOLUME_UP}, 94 95 96 /* type 0x10 */ 97 {WMI_DELLA_PMF, 0x0010, 0x0057, PMFE_DISPLAY_BRIGHTNESS_DOWN}, 98 {WMI_DELLA_PMF, 0x0010, 0x0058, PMFE_DISPLAY_BRIGHTNESS_UP}, 99 {WMI_DELLA_IGN, 0x0010, 0x0151, 0}, /* Fn-lock */ 100 {WMI_DELLA_IGN, 0x0010, 0x0152, 0}, /* keyboard illumination */ 101 {WMI_DELLA_PMF, 0x0010, 0x0153, PMFE_RADIO_TOGGLE}, 102 {WMI_DELLA_IGN, 0x0010, 0x0155, 0}, /* Stealth mode toggle */ 103 {WMI_DELLA_IGN, 0x0010, 0xE035, 0}, /* Fn-lock */ 104 105 /* type 0x11 */ 106 {WMI_DELLA_IGN, 0x0011, 0x02eb5, 0}, /* keyboard illumination */ 107 }; 108 109 static int wmi_dell_match(device_t, cfdata_t, void *); 110 static void wmi_dell_attach(device_t, device_t, void *); 111 static int wmi_dell_detach(device_t, int); 112 static void wmi_dell_notify_handler(ACPI_HANDLE, uint32_t, void *); 113 static bool wmi_dell_suspend(device_t, const pmf_qual_t *); 114 static bool wmi_dell_resume(device_t, const pmf_qual_t *); 115 116 CFATTACH_DECL_NEW(wmidell, sizeof(struct wmi_dell_softc), 117 wmi_dell_match, wmi_dell_attach, wmi_dell_detach, NULL); 118 119 static int 120 wmi_dell_match(device_t parent, cfdata_t match, void *aux) 121 { 122 return acpi_wmi_guid_match(parent, WMI_DELL_GUID_EVENT); 123 } 124 125 static void 126 wmi_dell_attach(device_t parent, device_t self, void *aux) 127 { 128 struct wmi_dell_softc *sc = device_private(self); 129 ACPI_STATUS rv; 130 ACPI_BUFFER obuf; 131 ACPI_OBJECT *obj; 132 uint32_t *data; 133 int e; 134 135 sc->sc_dev = self; 136 sc->sc_parent = parent; 137 sc->sc_smpsw_valid = true; 138 139 rv = acpi_wmi_event_register(parent, wmi_dell_notify_handler); 140 141 if (ACPI_FAILURE(rv)) { 142 aprint_error(": failed to install WMI notify handler\n"); 143 return; 144 } 145 146 memset(&obuf, 0, sizeof(obuf)); 147 rv = acpi_wmi_data_query(parent, WMI_DELL_GUID_DESC, 0, &obuf); 148 if (ACPI_FAILURE(rv)) { 149 aprint_error(": failed to query WMI descriptor: %s\n", 150 AcpiFormatException(rv)); 151 return; 152 } 153 obj = obuf.Pointer; 154 if (obj->Type != ACPI_TYPE_BUFFER) { 155 aprint_error(": wrong type %d for WMI descriptor\n", obj->Type); 156 return; 157 } 158 if (obj->Buffer.Length != 128) { 159 aprint_error(": wrong len %d for WMI descriptor", 160 obj->Buffer.Length); 161 if (obj->Buffer.Length < 16) { 162 aprint_error("\n"); 163 return; 164 } 165 } 166 data = (uint32_t *)obj->Buffer.Pointer; 167 #define WMI_LLED 0x4C4C4544 168 #define WMI_IMWsp 0x494D5720 169 if (data[0] != WMI_LLED || data[1] != WMI_IMWsp) { 170 aprint_error(": wrong WMI descriptor signature %#x %#x", 171 data[0], data[1]); 172 } 173 sc->sc_version = data[2]; 174 aprint_naive("\n"); 175 aprint_normal(": Dell WMI mappings version %d\n", sc->sc_version); 176 177 sc->sc_smpsw[WMI_DELL_PSW_DISPLAY_CYCLE].smpsw_name = 178 PSWITCH_HK_DISPLAY_CYCLE; 179 180 sc->sc_smpsw[WMI_DELL_PSW_DISPLAY_CYCLE].smpsw_type = 181 PSWITCH_TYPE_HOTKEY; 182 183 e = sysmon_pswitch_register(&sc->sc_smpsw[WMI_DELL_PSW_DISPLAY_CYCLE]); 184 185 if (e != 0) 186 sc->sc_smpsw_valid = false; 187 188 (void)pmf_device_register(self, wmi_dell_suspend, wmi_dell_resume); 189 } 190 191 static int 192 wmi_dell_detach(device_t self, int flags) 193 { 194 struct wmi_dell_softc *sc = device_private(self); 195 device_t parent = sc->sc_parent; 196 size_t i; 197 198 (void)pmf_device_deregister(self); 199 (void)acpi_wmi_event_deregister(parent); 200 201 if (sc->sc_smpsw_valid != true) 202 return 0; 203 204 for (i = 0; i < __arraycount(sc->sc_smpsw); i++) 205 sysmon_pswitch_unregister(&sc->sc_smpsw[i]); 206 207 return 0; 208 } 209 210 static bool 211 wmi_dell_suspend(device_t self, const pmf_qual_t *qual) 212 { 213 struct wmi_dell_softc *sc = device_private(self); 214 device_t parent = sc->sc_parent; 215 216 (void)acpi_wmi_event_deregister(parent); 217 218 return true; 219 } 220 221 static bool 222 wmi_dell_resume(device_t self, const pmf_qual_t *qual) 223 { 224 struct wmi_dell_softc *sc = device_private(self); 225 device_t parent = sc->sc_parent; 226 227 (void)acpi_wmi_event_register(parent, wmi_dell_notify_handler); 228 229 return true; 230 } 231 232 static void 233 wmi_dell_action(struct wmi_dell_softc *sc, uint16_t *data, int len) 234 { 235 size_t i; 236 for (i = 0; i < __arraycount(wmi_dell_actions); i++) { 237 const struct wmi_dell_actions *wda = &wmi_dell_actions[i]; 238 if (wda->wda_type == data[0] && 239 wda->wda_subtype == data[1]) { 240 switch(wda->wda_action) { 241 case WMI_DELLA_IGN: 242 DPRINTF((" ignored")); 243 return; 244 case WMI_DELLA_PMF: 245 DPRINTF((" pmf %d", wda->wda_data)); 246 pmf_event_inject(NULL, wda->wda_data); 247 return; 248 case WMI_DELLA_PSW: 249 DPRINTF((" psw %d", wda->wda_data)); 250 sysmon_pswitch_event( 251 &sc->sc_smpsw[wda->wda_data], 252 PSWITCH_EVENT_PRESSED); 253 return; 254 default: 255 aprint_debug_dev(sc->sc_dev, 256 "unknown dell wmi action %d\n", 257 wda->wda_action); 258 return; 259 } 260 261 } 262 } 263 aprint_debug_dev(sc->sc_dev, "unknown event %#4X %#4X\n", 264 data[0], data[1]); 265 } 266 267 static void 268 wmi_dell_notify_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux) 269 { 270 struct wmi_dell_softc *sc; 271 device_t self = aux; 272 ACPI_OBJECT *obj; 273 ACPI_BUFFER buf; 274 ACPI_STATUS rv; 275 uint16_t *data, *end; 276 int i, len; 277 278 buf.Pointer = NULL; 279 280 sc = device_private(self); 281 rv = acpi_wmi_event_get(sc->sc_parent, evt, &buf); 282 283 if (ACPI_FAILURE(rv)) 284 goto out; 285 286 obj = buf.Pointer; 287 288 if (obj->Type != ACPI_TYPE_BUFFER) { 289 rv = AE_TYPE; 290 goto out; 291 } 292 293 data = (void *)(&obj->Buffer.Pointer[0]); 294 end = (void *)(&obj->Buffer.Pointer[obj->Buffer.Length]); 295 296 DPRINTF(("wmi_dell_notify_handler buffer len %d\n", 297 obj->Buffer.Length)); 298 while (data < end) { 299 DPRINTF(("wmi_dell_notify_handler len %d", data[0])); 300 if (data[0] == 0) { 301 DPRINTF(("\n")); 302 break; 303 } 304 len = data[0] + 1; 305 306 if (&data[len] >= end) { 307 DPRINTF(("\n")); 308 break; 309 } 310 if (len < 2) { 311 DPRINTF(("\n")); 312 continue; 313 } 314 for (i = 1; i < len; i++) 315 DPRINTF((" %#04X", data[i])); 316 wmi_dell_action(sc, &data[1], len - 1); 317 DPRINTF(("\n")); 318 data = &data[len]; 319 /* 320 * WMI interface version 0 don't clear the buffer from previous 321 * event, so if the current event is smaller than the previous 322 * one there will be garbage after the current event. 323 * workaround by processing only the first event 324 */ 325 if (sc->sc_version == 0) 326 break; 327 } 328 329 out: 330 if (buf.Pointer != NULL) 331 ACPI_FREE(buf.Pointer); 332 333 if (ACPI_FAILURE(rv)) 334 aprint_error_dev(sc->sc_dev, "failed to get data for " 335 "event %#02X: %s\n", evt, AcpiFormatException(rv)); 336 } 337 338 MODULE(MODULE_CLASS_DRIVER, wmidell, "acpiwmi,sysmon_power"); 339 340 #ifdef _MODULE 341 #include "ioconf.c" 342 #endif 343 344 static int 345 wmidell_modcmd(modcmd_t cmd, void *aux) 346 { 347 int rv = 0; 348 349 switch (cmd) { 350 case MODULE_CMD_INIT: 351 #ifdef _MODULE 352 rv = config_init_component(cfdriver_ioconf_wmidell, 353 cfattach_ioconf_wmidell, cfdata_ioconf_wmidell); 354 #endif 355 break; 356 357 case MODULE_CMD_FINI: 358 #ifdef _MODULE 359 rv = config_fini_component(cfdriver_ioconf_wmidell, 360 cfattach_ioconf_wmidell, cfdata_ioconf_wmidell); 361 #endif 362 break; 363 364 default: 365 rv = ENOTTY; 366 } 367 368 return rv; 369 } 370