1 /*- 2 * Copyright (c) 2003 OGAWA Takaya <t-ogawa@triaez.kaisei.org> 3 * Copyright (c) 2004 TAKAHASHI Yoshihiro <nyan@FreeBSD.org> 4 * All rights Reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include "opt_acpi.h" 33 #include <sys/param.h> 34 #include <sys/kernel.h> 35 #include <sys/malloc.h> 36 #include <sys/module.h> 37 #include <sys/bus.h> 38 #include <sys/power.h> 39 40 #include <contrib/dev/acpica/acpi.h> 41 #include <dev/acpica/acpivar.h> 42 43 #define _COMPONENT ACPI_OEM 44 ACPI_MODULE_NAME("Panasonic") 45 46 /* Debug */ 47 #undef ACPI_PANASONIC_DEBUG 48 49 /* Operations */ 50 #define HKEY_SET 0 51 #define HKEY_GET 1 52 53 /* Functions */ 54 #define HKEY_REG_LCD_BRIGHTNESS_MAX_AC 0x02 55 #define HKEY_REG_LCD_BRIGHTNESS_MIN_AC 0x03 56 #define HKEY_REG_LCD_BRIGHTNESS_AC 0x04 57 #define HKEY_REG_LCD_BRIGHTNESS_MAX_DC 0x05 58 #define HKEY_REG_LCD_BRIGHTNESS_MIN_DC 0x06 59 #define HKEY_REG_LCD_BRIGHTNESS_DC 0x07 60 #define HKEY_REG_SOUND_MUTE 0x08 61 62 /* Field definitions */ 63 #define HKEY_LCD_BRIGHTNESS_BITS 4 64 #define HKEY_LCD_BRIGHTNESS_DIV ((1 << HKEY_LCD_BRIGHTNESS_BITS) - 1) 65 66 struct acpi_panasonic_softc { 67 device_t dev; 68 ACPI_HANDLE handle; 69 70 struct sysctl_ctx_list sysctl_ctx; 71 struct sysctl_oid *sysctl_tree; 72 73 eventhandler_tag power_evh; 74 }; 75 76 /* Prototype for HKEY functions for getting/setting a value. */ 77 typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *); 78 79 static int acpi_panasonic_probe(device_t dev); 80 static int acpi_panasonic_attach(device_t dev); 81 static int acpi_panasonic_detach(device_t dev); 82 static int acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS); 83 static ACPI_INTEGER acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index); 84 static void acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, 85 ACPI_INTEGER val); 86 static int acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, 87 ACPI_HANDLE h, UINT32 *arg); 88 static void acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, 89 ACPI_HANDLE h, UINT32 key); 90 static void acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, 91 void *context); 92 static void acpi_panasonic_power_profile(void *arg); 93 94 static hkey_fn_t hkey_lcd_brightness_max; 95 static hkey_fn_t hkey_lcd_brightness_min; 96 static hkey_fn_t hkey_lcd_brightness; 97 static hkey_fn_t hkey_sound_mute; 98 ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras"); 99 100 /* Table of sysctl names and HKEY functions to call. */ 101 static struct { 102 char *name; 103 hkey_fn_t *handler; 104 } sysctl_table[] = { 105 /* name, handler */ 106 {"lcd_brightness_max", hkey_lcd_brightness_max}, 107 {"lcd_brightness_min", hkey_lcd_brightness_min}, 108 {"lcd_brightness", hkey_lcd_brightness}, 109 {"sound_mute", hkey_sound_mute}, 110 {NULL, NULL} 111 }; 112 113 static device_method_t acpi_panasonic_methods[] = { 114 DEVMETHOD(device_probe, acpi_panasonic_probe), 115 DEVMETHOD(device_attach, acpi_panasonic_attach), 116 DEVMETHOD(device_detach, acpi_panasonic_detach), 117 118 {0, 0} 119 }; 120 121 static driver_t acpi_panasonic_driver = { 122 "acpi_panasonic", 123 acpi_panasonic_methods, 124 sizeof(struct acpi_panasonic_softc), 125 }; 126 127 static devclass_t acpi_panasonic_devclass; 128 129 DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver, 130 acpi_panasonic_devclass, 0, 0); 131 MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1); 132 133 static int 134 acpi_panasonic_probe(device_t dev) 135 { 136 static char *mat_ids[] = { "MAT0019", NULL }; 137 138 if (acpi_disabled("panasonic") || 139 ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids) == NULL || 140 device_get_unit(dev) != 0) 141 return (ENXIO); 142 143 device_set_desc(dev, "Panasonic Notebook Hotkeys"); 144 return (0); 145 } 146 147 static int 148 acpi_panasonic_attach(device_t dev) 149 { 150 struct acpi_panasonic_softc *sc; 151 struct acpi_softc *acpi_sc; 152 ACPI_STATUS status; 153 int i; 154 155 sc = device_get_softc(dev); 156 sc->dev = dev; 157 sc->handle = acpi_get_handle(dev); 158 159 acpi_sc = acpi_device_get_parent_softc(dev); 160 161 /* Build sysctl tree */ 162 sysctl_ctx_init(&sc->sysctl_ctx); 163 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 164 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, 165 "panasonic", CTLFLAG_RD, 0, ""); 166 for (i = 0; sysctl_table[i].name != NULL; i++) { 167 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 168 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 169 sysctl_table[i].name, 170 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, 171 sc, i, acpi_panasonic_sysctl, "I", ""); 172 } 173 174 #if 0 175 /* Activate hotkeys */ 176 status = AcpiEvaluateObject(sc->handle, "", NULL, NULL); 177 if (ACPI_FAILURE(status)) { 178 device_printf(dev, "enable FN keys failed\n"); 179 sysctl_ctx_free(&sc->sysctl_ctx); 180 return (ENXIO); 181 } 182 #endif 183 184 /* Handle notifies */ 185 status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, 186 acpi_panasonic_notify, sc); 187 if (ACPI_FAILURE(status)) { 188 device_printf(dev, "couldn't install notify handler - %s\n", 189 AcpiFormatException(status)); 190 sysctl_ctx_free(&sc->sysctl_ctx); 191 return (ENXIO); 192 } 193 194 /* Install power profile event handler */ 195 sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change, 196 acpi_panasonic_power_profile, sc->handle, 0); 197 198 return (0); 199 } 200 201 static int 202 acpi_panasonic_detach(device_t dev) 203 { 204 struct acpi_panasonic_softc *sc; 205 206 sc = device_get_softc(dev); 207 208 /* Remove power profile event handler */ 209 EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh); 210 211 /* Remove notify handler */ 212 AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, 213 acpi_panasonic_notify); 214 215 /* Free sysctl tree */ 216 sysctl_ctx_free(&sc->sysctl_ctx); 217 218 return (0); 219 } 220 221 static int 222 acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS) 223 { 224 struct acpi_panasonic_softc *sc; 225 UINT32 arg; 226 int function, error; 227 hkey_fn_t *handler; 228 229 sc = (struct acpi_panasonic_softc *)oidp->oid_arg1; 230 function = oidp->oid_arg2; 231 handler = sysctl_table[function].handler; 232 233 /* Get the current value from the appropriate function. */ 234 ACPI_SERIAL_BEGIN(panasonic); 235 error = handler(sc->handle, HKEY_GET, &arg); 236 if (error != 0) 237 goto out; 238 239 /* Send the current value to the user and return if no new value. */ 240 error = sysctl_handle_int(oidp, &arg, 0, req); 241 if (error != 0 || req->newptr == NULL) 242 goto out; 243 244 /* Set the new value via the appropriate function. */ 245 error = handler(sc->handle, HKEY_SET, &arg); 246 247 out: 248 ACPI_SERIAL_END(panasonic); 249 return (error); 250 } 251 252 static ACPI_INTEGER 253 acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index) 254 { 255 ACPI_BUFFER buf; 256 ACPI_OBJECT *res; 257 ACPI_INTEGER ret; 258 259 ACPI_SERIAL_ASSERT(panasonic); 260 ret = -1; 261 buf.Length = ACPI_ALLOCATE_BUFFER; 262 buf.Pointer = NULL; 263 AcpiEvaluateObject(h, "SINF", NULL, &buf); 264 res = (ACPI_OBJECT *)buf.Pointer; 265 if (res->Type == ACPI_TYPE_PACKAGE) 266 ret = res->Package.Elements[index].Integer.Value; 267 AcpiOsFree(buf.Pointer); 268 269 return (ret); 270 } 271 272 static void 273 acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, ACPI_INTEGER val) 274 { 275 ACPI_OBJECT_LIST args; 276 ACPI_OBJECT obj[2]; 277 278 ACPI_SERIAL_ASSERT(panasonic); 279 obj[0].Type = ACPI_TYPE_INTEGER; 280 obj[0].Integer.Value = index; 281 obj[1].Type = ACPI_TYPE_INTEGER; 282 obj[1].Integer.Value = val; 283 args.Count = 2; 284 args.Pointer = obj; 285 AcpiEvaluateObject(h, "SSET", &args, NULL); 286 } 287 288 static int 289 hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val) 290 { 291 int reg; 292 293 ACPI_SERIAL_ASSERT(panasonic); 294 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 295 HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC; 296 297 switch (op) { 298 case HKEY_SET: 299 return (EPERM); 300 break; 301 case HKEY_GET: 302 *val = acpi_panasonic_sinf(h, reg); 303 break; 304 } 305 306 return (0); 307 } 308 309 static int 310 hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val) 311 { 312 int reg; 313 314 ACPI_SERIAL_ASSERT(panasonic); 315 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 316 HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC; 317 318 switch (op) { 319 case HKEY_SET: 320 return (EPERM); 321 break; 322 case HKEY_GET: 323 *val = acpi_panasonic_sinf(h, reg); 324 break; 325 } 326 327 return (0); 328 } 329 330 static int 331 hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val) 332 { 333 int reg; 334 UINT32 max, min; 335 336 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 337 HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC; 338 339 ACPI_SERIAL_ASSERT(panasonic); 340 switch (op) { 341 case HKEY_SET: 342 hkey_lcd_brightness_max(h, HKEY_GET, &max); 343 hkey_lcd_brightness_min(h, HKEY_GET, &min); 344 if (*val < min || *val > max) 345 return (EINVAL); 346 acpi_panasonic_sset(h, reg, *val); 347 break; 348 case HKEY_GET: 349 *val = acpi_panasonic_sinf(h, reg); 350 break; 351 } 352 353 return (0); 354 } 355 356 static int 357 hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val) 358 { 359 360 ACPI_SERIAL_ASSERT(panasonic); 361 switch (op) { 362 case HKEY_SET: 363 if (*val != 0 && *val != 1) 364 return (EINVAL); 365 acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val); 366 break; 367 case HKEY_GET: 368 *val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE); 369 break; 370 } 371 372 return (0); 373 } 374 375 static int 376 acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, 377 UINT32 *arg) 378 { 379 ACPI_BUFFER buf; 380 ACPI_OBJECT *res; 381 ACPI_INTEGER val; 382 int status; 383 384 ACPI_SERIAL_ASSERT(panasonic); 385 status = ENXIO; 386 387 buf.Length = ACPI_ALLOCATE_BUFFER; 388 buf.Pointer = NULL; 389 AcpiEvaluateObject(h, "HINF", NULL, &buf); 390 res = (ACPI_OBJECT *)buf.Pointer; 391 if (res->Type != ACPI_TYPE_INTEGER) { 392 device_printf(sc->dev, "HINF returned non-integer\n"); 393 goto end; 394 } 395 val = res->Integer.Value; 396 #ifdef ACPI_PANASONIC_DEBUG 397 device_printf(sc->dev, "%s button Fn+F%d\n", 398 (val & 0x80) ? "Pressed" : "Released", 399 (int)(val & 0x7f)); 400 #endif 401 if ((val & 0x7f) > 0 && (val & 0x7f) < 11) { 402 *arg = val; 403 status = 0; 404 } 405 end: 406 if (buf.Pointer) 407 AcpiOsFree(buf.Pointer); 408 409 return (status); 410 } 411 412 static void 413 acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, 414 UINT32 key) 415 { 416 struct acpi_softc *acpi_sc; 417 int arg, max, min; 418 419 acpi_sc = acpi_device_get_parent_softc(sc->dev); 420 421 ACPI_SERIAL_ASSERT(panasonic); 422 switch (key) { 423 case 1: 424 /* Decrease LCD brightness. */ 425 hkey_lcd_brightness_max(h, HKEY_GET, &max); 426 hkey_lcd_brightness_min(h, HKEY_GET, &min); 427 hkey_lcd_brightness(h, HKEY_GET, &arg); 428 arg -= max / HKEY_LCD_BRIGHTNESS_DIV; 429 if (arg < min) 430 arg = min; 431 else if (arg > max) 432 arg = max; 433 hkey_lcd_brightness(h, HKEY_SET, &arg); 434 break; 435 case 2: 436 /* Increase LCD brightness. */ 437 hkey_lcd_brightness_max(h, HKEY_GET, &max); 438 hkey_lcd_brightness_min(h, HKEY_GET, &min); 439 hkey_lcd_brightness(h, HKEY_GET, &arg); 440 arg += max / HKEY_LCD_BRIGHTNESS_DIV; 441 if (arg < min) 442 arg = min; 443 else if (arg > max) 444 arg = max; 445 hkey_lcd_brightness(h, HKEY_SET, &arg); 446 break; 447 case 4: 448 /* Toggle sound mute. */ 449 hkey_sound_mute(h, HKEY_GET, &arg); 450 if (arg) 451 arg = 0; 452 else 453 arg = 1; 454 hkey_sound_mute(h, HKEY_SET, &arg); 455 break; 456 case 7: 457 /* Suspend. */ 458 acpi_event_sleep_button_sleep(acpi_sc); 459 break; 460 } 461 } 462 463 static void 464 acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context) 465 { 466 struct acpi_panasonic_softc *sc; 467 UINT32 key; 468 469 sc = (struct acpi_panasonic_softc *)context; 470 471 switch (notify) { 472 case 0x80: 473 ACPI_SERIAL_BEGIN(panasonic); 474 if (acpi_panasonic_hkey_event(sc, h, &key) == 0) { 475 acpi_panasonic_hkey_action(sc, h, key); 476 acpi_UserNotify("Panasonic", h, (uint8_t)key); 477 } 478 ACPI_SERIAL_END(panasonic); 479 break; 480 default: 481 device_printf(sc->dev, "unknown notify: %#x\n", notify); 482 break; 483 } 484 } 485 486 static void 487 acpi_panasonic_power_profile(void *arg) 488 { 489 ACPI_HANDLE handle; 490 UINT32 brightness; 491 492 handle = (ACPI_HANDLE)arg; 493 494 /* Reset current brightness according to new power state. */ 495 ACPI_SERIAL_BEGIN(panasonic); 496 hkey_lcd_brightness(handle, HKEY_GET, &brightness); 497 hkey_lcd_brightness(handle, HKEY_SET, &brightness); 498 ACPI_SERIAL_END(panasonic); 499 } 500