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/include/acpi.h> 41 42 #include <dev/acpica/acpivar.h> 43 44 #define _COMPONENT ACPI_OEM 45 ACPI_MODULE_NAME("Panasonic") 46 47 /* Debug */ 48 #undef ACPI_PANASONIC_DEBUG 49 50 /* Operations */ 51 #define HKEY_SET 0 52 #define HKEY_GET 1 53 54 /* Functions */ 55 #define HKEY_REG_LCD_BRIGHTNESS_MAX_AC 0x02 56 #define HKEY_REG_LCD_BRIGHTNESS_MIN_AC 0x03 57 #define HKEY_REG_LCD_BRIGHTNESS_AC 0x04 58 #define HKEY_REG_LCD_BRIGHTNESS_MAX_DC 0x05 59 #define HKEY_REG_LCD_BRIGHTNESS_MIN_DC 0x06 60 #define HKEY_REG_LCD_BRIGHTNESS_DC 0x07 61 #define HKEY_REG_SOUND_MUTE 0x08 62 63 /* Field definitions */ 64 #define HKEY_LCD_BRIGHTNESS_BITS 4 65 #define HKEY_LCD_BRIGHTNESS_DIV ((1 << HKEY_LCD_BRIGHTNESS_BITS) - 1) 66 67 struct acpi_panasonic_softc { 68 device_t dev; 69 ACPI_HANDLE handle; 70 71 struct sysctl_ctx_list sysctl_ctx; 72 struct sysctl_oid *sysctl_tree; 73 74 eventhandler_tag power_evh; 75 }; 76 77 /* Prototype for HKEY functions for getting/setting a value. */ 78 typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *); 79 80 static int acpi_panasonic_probe(device_t dev); 81 static int acpi_panasonic_attach(device_t dev); 82 static int acpi_panasonic_detach(device_t dev); 83 static int acpi_panasonic_shutdown(device_t dev); 84 static int acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS); 85 static UINT64 acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index); 86 static void acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index, 87 UINT64 val); 88 static int acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, 89 ACPI_HANDLE h, UINT32 *arg); 90 static void acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, 91 ACPI_HANDLE h, UINT32 key); 92 static void acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, 93 void *context); 94 static void acpi_panasonic_power_profile(void *arg); 95 96 static hkey_fn_t hkey_lcd_brightness_max; 97 static hkey_fn_t hkey_lcd_brightness_min; 98 static hkey_fn_t hkey_lcd_brightness; 99 static hkey_fn_t hkey_sound_mute; 100 ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras"); 101 102 /* Table of sysctl names and HKEY functions to call. */ 103 static struct { 104 char *name; 105 hkey_fn_t *handler; 106 } sysctl_table[] = { 107 /* name, handler */ 108 {"lcd_brightness_max", hkey_lcd_brightness_max}, 109 {"lcd_brightness_min", hkey_lcd_brightness_min}, 110 {"lcd_brightness", hkey_lcd_brightness}, 111 {"sound_mute", hkey_sound_mute}, 112 {NULL, NULL} 113 }; 114 115 static device_method_t acpi_panasonic_methods[] = { 116 DEVMETHOD(device_probe, acpi_panasonic_probe), 117 DEVMETHOD(device_attach, acpi_panasonic_attach), 118 DEVMETHOD(device_detach, acpi_panasonic_detach), 119 DEVMETHOD(device_shutdown, acpi_panasonic_shutdown), 120 121 DEVMETHOD_END 122 }; 123 124 static driver_t acpi_panasonic_driver = { 125 "acpi_panasonic", 126 acpi_panasonic_methods, 127 sizeof(struct acpi_panasonic_softc), 128 }; 129 130 static devclass_t acpi_panasonic_devclass; 131 132 DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver, 133 acpi_panasonic_devclass, 0, 0); 134 MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1); 135 136 static int 137 acpi_panasonic_probe(device_t dev) 138 { 139 static char *mat_ids[] = { "MAT0019", NULL }; 140 int rv; 141 142 if (acpi_disabled("panasonic") || 143 device_get_unit(dev) != 0) 144 return (ENXIO); 145 rv = ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids, NULL); 146 147 if (rv <= 0) 148 device_set_desc(dev, "Panasonic Notebook Hotkeys"); 149 return (rv); 150 } 151 152 static int 153 acpi_panasonic_attach(device_t dev) 154 { 155 struct acpi_panasonic_softc *sc; 156 struct acpi_softc *acpi_sc; 157 ACPI_STATUS status; 158 int i; 159 160 sc = device_get_softc(dev); 161 sc->dev = dev; 162 sc->handle = acpi_get_handle(dev); 163 164 acpi_sc = acpi_device_get_parent_softc(dev); 165 166 /* Build sysctl tree */ 167 sysctl_ctx_init(&sc->sysctl_ctx); 168 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 169 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, 170 "panasonic", CTLFLAG_RD, 0, ""); 171 for (i = 0; sysctl_table[i].name != NULL; i++) { 172 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 173 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, 174 sysctl_table[i].name, 175 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, 176 sc, i, acpi_panasonic_sysctl, "I", ""); 177 } 178 179 #if 0 180 /* Activate hotkeys */ 181 status = AcpiEvaluateObject(sc->handle, "", NULL, NULL); 182 if (ACPI_FAILURE(status)) { 183 device_printf(dev, "enable FN keys failed\n"); 184 sysctl_ctx_free(&sc->sysctl_ctx); 185 return (ENXIO); 186 } 187 #endif 188 189 /* Handle notifies */ 190 status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, 191 acpi_panasonic_notify, sc); 192 if (ACPI_FAILURE(status)) { 193 device_printf(dev, "couldn't install notify handler - %s\n", 194 AcpiFormatException(status)); 195 sysctl_ctx_free(&sc->sysctl_ctx); 196 return (ENXIO); 197 } 198 199 /* Install power profile event handler */ 200 sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change, 201 acpi_panasonic_power_profile, sc->handle, 0); 202 203 return (0); 204 } 205 206 static int 207 acpi_panasonic_detach(device_t dev) 208 { 209 struct acpi_panasonic_softc *sc; 210 211 sc = device_get_softc(dev); 212 213 /* Remove power profile event handler */ 214 EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh); 215 216 /* Remove notify handler */ 217 AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, 218 acpi_panasonic_notify); 219 220 /* Free sysctl tree */ 221 sysctl_ctx_free(&sc->sysctl_ctx); 222 223 return (0); 224 } 225 226 static int 227 acpi_panasonic_shutdown(device_t dev) 228 { 229 struct acpi_panasonic_softc *sc; 230 int mute; 231 232 /* Mute the main audio during reboot to prevent static burst to speaker. */ 233 sc = device_get_softc(dev); 234 mute = 1; 235 hkey_sound_mute(sc->handle, HKEY_SET, &mute); 236 return (0); 237 } 238 239 static int 240 acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS) 241 { 242 struct acpi_panasonic_softc *sc; 243 UINT32 arg; 244 int function, error; 245 hkey_fn_t *handler; 246 247 sc = (struct acpi_panasonic_softc *)oidp->oid_arg1; 248 function = oidp->oid_arg2; 249 handler = sysctl_table[function].handler; 250 251 /* Get the current value from the appropriate function. */ 252 ACPI_SERIAL_BEGIN(panasonic); 253 error = handler(sc->handle, HKEY_GET, &arg); 254 if (error != 0) 255 goto out; 256 257 /* Send the current value to the user and return if no new value. */ 258 error = sysctl_handle_int(oidp, &arg, 0, req); 259 if (error != 0 || req->newptr == NULL) 260 goto out; 261 262 /* Set the new value via the appropriate function. */ 263 error = handler(sc->handle, HKEY_SET, &arg); 264 265 out: 266 ACPI_SERIAL_END(panasonic); 267 return (error); 268 } 269 270 static UINT64 271 acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index) 272 { 273 ACPI_BUFFER buf; 274 ACPI_OBJECT *res; 275 UINT64 ret; 276 277 ACPI_SERIAL_ASSERT(panasonic); 278 ret = -1; 279 buf.Length = ACPI_ALLOCATE_BUFFER; 280 buf.Pointer = NULL; 281 AcpiEvaluateObject(h, "SINF", NULL, &buf); 282 res = (ACPI_OBJECT *)buf.Pointer; 283 if (res->Type == ACPI_TYPE_PACKAGE) 284 ret = res->Package.Elements[index].Integer.Value; 285 AcpiOsFree(buf.Pointer); 286 287 return (ret); 288 } 289 290 static void 291 acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index, UINT64 val) 292 { 293 ACPI_OBJECT_LIST args; 294 ACPI_OBJECT obj[2]; 295 296 ACPI_SERIAL_ASSERT(panasonic); 297 obj[0].Type = ACPI_TYPE_INTEGER; 298 obj[0].Integer.Value = index; 299 obj[1].Type = ACPI_TYPE_INTEGER; 300 obj[1].Integer.Value = val; 301 args.Count = 2; 302 args.Pointer = obj; 303 AcpiEvaluateObject(h, "SSET", &args, NULL); 304 } 305 306 static int 307 hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val) 308 { 309 int reg; 310 311 ACPI_SERIAL_ASSERT(panasonic); 312 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 313 HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC; 314 315 switch (op) { 316 case HKEY_SET: 317 return (EPERM); 318 break; 319 case HKEY_GET: 320 *val = acpi_panasonic_sinf(h, reg); 321 break; 322 } 323 324 return (0); 325 } 326 327 static int 328 hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val) 329 { 330 int reg; 331 332 ACPI_SERIAL_ASSERT(panasonic); 333 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 334 HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC; 335 336 switch (op) { 337 case HKEY_SET: 338 return (EPERM); 339 break; 340 case HKEY_GET: 341 *val = acpi_panasonic_sinf(h, reg); 342 break; 343 } 344 345 return (0); 346 } 347 348 static int 349 hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val) 350 { 351 int reg; 352 UINT32 max, min; 353 354 reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? 355 HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC; 356 357 ACPI_SERIAL_ASSERT(panasonic); 358 switch (op) { 359 case HKEY_SET: 360 hkey_lcd_brightness_max(h, HKEY_GET, &max); 361 hkey_lcd_brightness_min(h, HKEY_GET, &min); 362 if (*val < min || *val > max) 363 return (EINVAL); 364 acpi_panasonic_sset(h, reg, *val); 365 break; 366 case HKEY_GET: 367 *val = acpi_panasonic_sinf(h, reg); 368 break; 369 } 370 371 return (0); 372 } 373 374 static int 375 hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val) 376 { 377 378 ACPI_SERIAL_ASSERT(panasonic); 379 switch (op) { 380 case HKEY_SET: 381 if (*val != 0 && *val != 1) 382 return (EINVAL); 383 acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val); 384 break; 385 case HKEY_GET: 386 *val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE); 387 break; 388 } 389 390 return (0); 391 } 392 393 static int 394 acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, 395 UINT32 *arg) 396 { 397 ACPI_BUFFER buf; 398 ACPI_OBJECT *res; 399 UINT64 val; 400 int status; 401 402 ACPI_SERIAL_ASSERT(panasonic); 403 status = ENXIO; 404 405 buf.Length = ACPI_ALLOCATE_BUFFER; 406 buf.Pointer = NULL; 407 AcpiEvaluateObject(h, "HINF", NULL, &buf); 408 res = (ACPI_OBJECT *)buf.Pointer; 409 if (res->Type != ACPI_TYPE_INTEGER) { 410 device_printf(sc->dev, "HINF returned non-integer\n"); 411 goto end; 412 } 413 val = res->Integer.Value; 414 #ifdef ACPI_PANASONIC_DEBUG 415 device_printf(sc->dev, "%s button Fn+F%d\n", 416 (val & 0x80) ? "Pressed" : "Released", 417 (int)(val & 0x7f)); 418 #endif 419 if ((val & 0x7f) > 0 && (val & 0x7f) < 11) { 420 *arg = val; 421 status = 0; 422 } 423 end: 424 if (buf.Pointer) 425 AcpiOsFree(buf.Pointer); 426 427 return (status); 428 } 429 430 static void 431 acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, 432 UINT32 key) 433 { 434 struct acpi_softc *acpi_sc; 435 int arg, max, min; 436 437 acpi_sc = acpi_device_get_parent_softc(sc->dev); 438 439 ACPI_SERIAL_ASSERT(panasonic); 440 switch (key) { 441 case 1: 442 /* Decrease LCD brightness. */ 443 hkey_lcd_brightness_max(h, HKEY_GET, &max); 444 hkey_lcd_brightness_min(h, HKEY_GET, &min); 445 hkey_lcd_brightness(h, HKEY_GET, &arg); 446 arg -= max / HKEY_LCD_BRIGHTNESS_DIV; 447 if (arg < min) 448 arg = min; 449 else if (arg > max) 450 arg = max; 451 hkey_lcd_brightness(h, HKEY_SET, &arg); 452 break; 453 case 2: 454 /* Increase LCD brightness. */ 455 hkey_lcd_brightness_max(h, HKEY_GET, &max); 456 hkey_lcd_brightness_min(h, HKEY_GET, &min); 457 hkey_lcd_brightness(h, HKEY_GET, &arg); 458 arg += max / HKEY_LCD_BRIGHTNESS_DIV; 459 if (arg < min) 460 arg = min; 461 else if (arg > max) 462 arg = max; 463 hkey_lcd_brightness(h, HKEY_SET, &arg); 464 break; 465 case 4: 466 /* Toggle sound mute. */ 467 hkey_sound_mute(h, HKEY_GET, &arg); 468 if (arg) 469 arg = 0; 470 else 471 arg = 1; 472 hkey_sound_mute(h, HKEY_SET, &arg); 473 break; 474 case 7: 475 /* Suspend. */ 476 acpi_event_sleep_button_sleep(acpi_sc); 477 break; 478 } 479 } 480 481 static void 482 acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context) 483 { 484 struct acpi_panasonic_softc *sc; 485 UINT32 key = 0; 486 487 sc = (struct acpi_panasonic_softc *)context; 488 489 switch (notify) { 490 case 0x80: 491 ACPI_SERIAL_BEGIN(panasonic); 492 if (acpi_panasonic_hkey_event(sc, h, &key) == 0) { 493 acpi_panasonic_hkey_action(sc, h, key); 494 acpi_UserNotify("Panasonic", h, (uint8_t)key); 495 } 496 ACPI_SERIAL_END(panasonic); 497 break; 498 case 0x81: 499 if (!bootverbose) 500 break; 501 /* FALLTHROUGH */ 502 default: 503 device_printf(sc->dev, "unknown notify: %#x\n", notify); 504 break; 505 } 506 } 507 508 static void 509 acpi_panasonic_power_profile(void *arg) 510 { 511 ACPI_HANDLE handle; 512 UINT32 brightness; 513 514 handle = (ACPI_HANDLE)arg; 515 516 /* Reset current brightness according to new power state. */ 517 ACPI_SERIAL_BEGIN(panasonic); 518 hkey_lcd_brightness(handle, HKEY_GET, &brightness); 519 hkey_lcd_brightness(handle, HKEY_SET, &brightness); 520 ACPI_SERIAL_END(panasonic); 521 } 522