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