1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Alienware AlienFX control 4 * 5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> 6 */ 7 8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 10 #include <linux/acpi.h> 11 #include <linux/bitfield.h> 12 #include <linux/bits.h> 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/platform_profile.h> 16 #include <linux/dmi.h> 17 #include <linux/leds.h> 18 19 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" 20 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" 21 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" 22 23 #define WMAX_METHOD_HDMI_SOURCE 0x1 24 #define WMAX_METHOD_HDMI_STATUS 0x2 25 #define WMAX_METHOD_BRIGHTNESS 0x3 26 #define WMAX_METHOD_ZONE_CONTROL 0x4 27 #define WMAX_METHOD_HDMI_CABLE 0x5 28 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6 29 #define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B 30 #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C 31 #define WMAX_METHOD_THERMAL_INFORMATION 0x14 32 #define WMAX_METHOD_THERMAL_CONTROL 0x15 33 #define WMAX_METHOD_GAME_SHIFT_STATUS 0x25 34 35 #define WMAX_THERMAL_MODE_GMODE 0xAB 36 37 #define WMAX_FAILURE_CODE 0xFFFFFFFF 38 39 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 40 MODULE_DESCRIPTION("Alienware special feature control"); 41 MODULE_LICENSE("GPL"); 42 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); 43 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); 44 45 static bool force_platform_profile; 46 module_param_unsafe(force_platform_profile, bool, 0); 47 MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available"); 48 49 static bool force_gmode; 50 module_param_unsafe(force_gmode, bool, 0); 51 MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); 52 53 enum INTERFACE_FLAGS { 54 LEGACY, 55 WMAX, 56 }; 57 58 enum LEGACY_CONTROL_STATES { 59 LEGACY_RUNNING = 1, 60 LEGACY_BOOTING = 0, 61 LEGACY_SUSPEND = 3, 62 }; 63 64 enum WMAX_CONTROL_STATES { 65 WMAX_RUNNING = 0xFF, 66 WMAX_BOOTING = 0, 67 WMAX_SUSPEND = 3, 68 }; 69 70 enum WMAX_THERMAL_INFORMATION_OPERATIONS { 71 WMAX_OPERATION_SYS_DESCRIPTION = 0x02, 72 WMAX_OPERATION_LIST_IDS = 0x03, 73 WMAX_OPERATION_CURRENT_PROFILE = 0x0B, 74 }; 75 76 enum WMAX_THERMAL_CONTROL_OPERATIONS { 77 WMAX_OPERATION_ACTIVATE_PROFILE = 0x01, 78 }; 79 80 enum WMAX_GAME_SHIFT_STATUS_OPERATIONS { 81 WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01, 82 WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02, 83 }; 84 85 enum WMAX_THERMAL_TABLES { 86 WMAX_THERMAL_TABLE_BASIC = 0x90, 87 WMAX_THERMAL_TABLE_USTT = 0xA0, 88 }; 89 90 enum wmax_thermal_mode { 91 THERMAL_MODE_USTT_BALANCED, 92 THERMAL_MODE_USTT_BALANCED_PERFORMANCE, 93 THERMAL_MODE_USTT_COOL, 94 THERMAL_MODE_USTT_QUIET, 95 THERMAL_MODE_USTT_PERFORMANCE, 96 THERMAL_MODE_USTT_LOW_POWER, 97 THERMAL_MODE_BASIC_QUIET, 98 THERMAL_MODE_BASIC_BALANCED, 99 THERMAL_MODE_BASIC_BALANCED_PERFORMANCE, 100 THERMAL_MODE_BASIC_PERFORMANCE, 101 THERMAL_MODE_LAST, 102 }; 103 104 static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = { 105 [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, 106 [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, 107 [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL, 108 [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, 109 [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, 110 [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, 111 [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET, 112 [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED, 113 [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, 114 [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, 115 }; 116 117 struct quirk_entry { 118 u8 num_zones; 119 u8 hdmi_mux; 120 u8 amplifier; 121 u8 deepslp; 122 bool thermal; 123 bool gmode; 124 }; 125 126 static struct quirk_entry *quirks; 127 128 129 static struct quirk_entry quirk_inspiron5675 = { 130 .num_zones = 2, 131 .hdmi_mux = 0, 132 .amplifier = 0, 133 .deepslp = 0, 134 .thermal = false, 135 .gmode = false, 136 }; 137 138 static struct quirk_entry quirk_unknown = { 139 .num_zones = 2, 140 .hdmi_mux = 0, 141 .amplifier = 0, 142 .deepslp = 0, 143 .thermal = false, 144 .gmode = false, 145 }; 146 147 static struct quirk_entry quirk_x51_r1_r2 = { 148 .num_zones = 3, 149 .hdmi_mux = 0, 150 .amplifier = 0, 151 .deepslp = 0, 152 .thermal = false, 153 .gmode = false, 154 }; 155 156 static struct quirk_entry quirk_x51_r3 = { 157 .num_zones = 4, 158 .hdmi_mux = 0, 159 .amplifier = 1, 160 .deepslp = 0, 161 .thermal = false, 162 .gmode = false, 163 }; 164 165 static struct quirk_entry quirk_asm100 = { 166 .num_zones = 2, 167 .hdmi_mux = 1, 168 .amplifier = 0, 169 .deepslp = 0, 170 .thermal = false, 171 .gmode = false, 172 }; 173 174 static struct quirk_entry quirk_asm200 = { 175 .num_zones = 2, 176 .hdmi_mux = 1, 177 .amplifier = 0, 178 .deepslp = 1, 179 .thermal = false, 180 .gmode = false, 181 }; 182 183 static struct quirk_entry quirk_asm201 = { 184 .num_zones = 2, 185 .hdmi_mux = 1, 186 .amplifier = 1, 187 .deepslp = 1, 188 .thermal = false, 189 .gmode = false, 190 }; 191 192 static struct quirk_entry quirk_g_series = { 193 .num_zones = 0, 194 .hdmi_mux = 0, 195 .amplifier = 0, 196 .deepslp = 0, 197 .thermal = true, 198 .gmode = true, 199 }; 200 201 static struct quirk_entry quirk_x_series = { 202 .num_zones = 0, 203 .hdmi_mux = 0, 204 .amplifier = 0, 205 .deepslp = 0, 206 .thermal = true, 207 .gmode = false, 208 }; 209 210 static int __init dmi_matched(const struct dmi_system_id *dmi) 211 { 212 quirks = dmi->driver_data; 213 return 1; 214 } 215 216 static const struct dmi_system_id alienware_quirks[] __initconst = { 217 { 218 .callback = dmi_matched, 219 .ident = "Alienware ASM100", 220 .matches = { 221 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 222 DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), 223 }, 224 .driver_data = &quirk_asm100, 225 }, 226 { 227 .callback = dmi_matched, 228 .ident = "Alienware ASM200", 229 .matches = { 230 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 231 DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), 232 }, 233 .driver_data = &quirk_asm200, 234 }, 235 { 236 .callback = dmi_matched, 237 .ident = "Alienware ASM201", 238 .matches = { 239 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 240 DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), 241 }, 242 .driver_data = &quirk_asm201, 243 }, 244 { 245 .callback = dmi_matched, 246 .ident = "Alienware m16 R1 AMD", 247 .matches = { 248 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 249 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"), 250 }, 251 .driver_data = &quirk_x_series, 252 }, 253 { 254 .callback = dmi_matched, 255 .ident = "Alienware m17 R5", 256 .matches = { 257 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 258 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), 259 }, 260 .driver_data = &quirk_x_series, 261 }, 262 { 263 .callback = dmi_matched, 264 .ident = "Alienware m18 R2", 265 .matches = { 266 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 267 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"), 268 }, 269 .driver_data = &quirk_x_series, 270 }, 271 { 272 .callback = dmi_matched, 273 .ident = "Alienware x15 R1", 274 .matches = { 275 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 276 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"), 277 }, 278 .driver_data = &quirk_x_series, 279 }, 280 { 281 .callback = dmi_matched, 282 .ident = "Alienware x17 R2", 283 .matches = { 284 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 285 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"), 286 }, 287 .driver_data = &quirk_x_series, 288 }, 289 { 290 .callback = dmi_matched, 291 .ident = "Alienware X51 R1", 292 .matches = { 293 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 294 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), 295 }, 296 .driver_data = &quirk_x51_r1_r2, 297 }, 298 { 299 .callback = dmi_matched, 300 .ident = "Alienware X51 R2", 301 .matches = { 302 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 303 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), 304 }, 305 .driver_data = &quirk_x51_r1_r2, 306 }, 307 { 308 .callback = dmi_matched, 309 .ident = "Alienware X51 R3", 310 .matches = { 311 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 312 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), 313 }, 314 .driver_data = &quirk_x51_r3, 315 }, 316 { 317 .callback = dmi_matched, 318 .ident = "Dell Inc. G15 5510", 319 .matches = { 320 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 321 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), 322 }, 323 .driver_data = &quirk_g_series, 324 }, 325 { 326 .callback = dmi_matched, 327 .ident = "Dell Inc. G15 5511", 328 .matches = { 329 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 330 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"), 331 }, 332 .driver_data = &quirk_g_series, 333 }, 334 { 335 .callback = dmi_matched, 336 .ident = "Dell Inc. G15 5515", 337 .matches = { 338 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 339 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"), 340 }, 341 .driver_data = &quirk_g_series, 342 }, 343 { 344 .callback = dmi_matched, 345 .ident = "Dell Inc. G3 3500", 346 .matches = { 347 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 348 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"), 349 }, 350 .driver_data = &quirk_g_series, 351 }, 352 { 353 .callback = dmi_matched, 354 .ident = "Dell Inc. G3 3590", 355 .matches = { 356 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 357 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"), 358 }, 359 .driver_data = &quirk_g_series, 360 }, 361 { 362 .callback = dmi_matched, 363 .ident = "Dell Inc. G5 5500", 364 .matches = { 365 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 366 DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"), 367 }, 368 .driver_data = &quirk_g_series, 369 }, 370 { 371 .callback = dmi_matched, 372 .ident = "Dell Inc. Inspiron 5675", 373 .matches = { 374 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 375 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), 376 }, 377 .driver_data = &quirk_inspiron5675, 378 }, 379 {} 380 }; 381 382 struct color_platform { 383 u8 blue; 384 u8 green; 385 u8 red; 386 } __packed; 387 388 struct platform_zone { 389 u8 location; 390 struct device_attribute *attr; 391 struct color_platform colors; 392 }; 393 394 struct wmax_brightness_args { 395 u32 led_mask; 396 u32 percentage; 397 }; 398 399 struct wmax_basic_args { 400 u8 arg; 401 }; 402 403 struct legacy_led_args { 404 struct color_platform colors; 405 u8 brightness; 406 u8 state; 407 } __packed; 408 409 struct wmax_led_args { 410 u32 led_mask; 411 struct color_platform colors; 412 u8 state; 413 } __packed; 414 415 struct wmax_u32_args { 416 u8 operation; 417 u8 arg1; 418 u8 arg2; 419 u8 arg3; 420 }; 421 422 static struct platform_device *platform_device; 423 static struct device_attribute *zone_dev_attrs; 424 static struct attribute **zone_attrs; 425 static struct platform_zone *zone_data; 426 static struct platform_profile_handler pp_handler; 427 static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST]; 428 429 static struct platform_driver platform_driver = { 430 .driver = { 431 .name = "alienware-wmi", 432 } 433 }; 434 435 static struct attribute_group zone_attribute_group = { 436 .name = "rgb_zones", 437 }; 438 439 static u8 interface; 440 static u8 lighting_control_state; 441 static u8 global_brightness; 442 443 /* 444 * Helpers used for zone control 445 */ 446 static int parse_rgb(const char *buf, struct platform_zone *zone) 447 { 448 long unsigned int rgb; 449 int ret; 450 union color_union { 451 struct color_platform cp; 452 int package; 453 } repackager; 454 455 ret = kstrtoul(buf, 16, &rgb); 456 if (ret) 457 return ret; 458 459 /* RGB triplet notation is 24-bit hexadecimal */ 460 if (rgb > 0xFFFFFF) 461 return -EINVAL; 462 463 repackager.package = rgb & 0x0f0f0f0f; 464 pr_debug("alienware-wmi: r: %d g:%d b: %d\n", 465 repackager.cp.red, repackager.cp.green, repackager.cp.blue); 466 zone->colors = repackager.cp; 467 return 0; 468 } 469 470 static struct platform_zone *match_zone(struct device_attribute *attr) 471 { 472 u8 zone; 473 474 for (zone = 0; zone < quirks->num_zones; zone++) { 475 if ((struct device_attribute *)zone_data[zone].attr == attr) { 476 pr_debug("alienware-wmi: matched zone location: %d\n", 477 zone_data[zone].location); 478 return &zone_data[zone]; 479 } 480 } 481 return NULL; 482 } 483 484 /* 485 * Individual RGB zone control 486 */ 487 static int alienware_update_led(struct platform_zone *zone) 488 { 489 int method_id; 490 acpi_status status; 491 char *guid; 492 struct acpi_buffer input; 493 struct legacy_led_args legacy_args; 494 struct wmax_led_args wmax_basic_args; 495 if (interface == WMAX) { 496 wmax_basic_args.led_mask = 1 << zone->location; 497 wmax_basic_args.colors = zone->colors; 498 wmax_basic_args.state = lighting_control_state; 499 guid = WMAX_CONTROL_GUID; 500 method_id = WMAX_METHOD_ZONE_CONTROL; 501 502 input.length = sizeof(wmax_basic_args); 503 input.pointer = &wmax_basic_args; 504 } else { 505 legacy_args.colors = zone->colors; 506 legacy_args.brightness = global_brightness; 507 legacy_args.state = 0; 508 if (lighting_control_state == LEGACY_BOOTING || 509 lighting_control_state == LEGACY_SUSPEND) { 510 guid = LEGACY_POWER_CONTROL_GUID; 511 legacy_args.state = lighting_control_state; 512 } else 513 guid = LEGACY_CONTROL_GUID; 514 method_id = zone->location + 1; 515 516 input.length = sizeof(legacy_args); 517 input.pointer = &legacy_args; 518 } 519 pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); 520 521 status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); 522 if (ACPI_FAILURE(status)) 523 pr_err("alienware-wmi: zone set failure: %u\n", status); 524 return ACPI_FAILURE(status); 525 } 526 527 static ssize_t zone_show(struct device *dev, struct device_attribute *attr, 528 char *buf) 529 { 530 struct platform_zone *target_zone; 531 target_zone = match_zone(attr); 532 if (target_zone == NULL) 533 return sprintf(buf, "red: -1, green: -1, blue: -1\n"); 534 return sprintf(buf, "red: %d, green: %d, blue: %d\n", 535 target_zone->colors.red, 536 target_zone->colors.green, target_zone->colors.blue); 537 538 } 539 540 static ssize_t zone_set(struct device *dev, struct device_attribute *attr, 541 const char *buf, size_t count) 542 { 543 struct platform_zone *target_zone; 544 int ret; 545 target_zone = match_zone(attr); 546 if (target_zone == NULL) { 547 pr_err("alienware-wmi: invalid target zone\n"); 548 return 1; 549 } 550 ret = parse_rgb(buf, target_zone); 551 if (ret) 552 return ret; 553 ret = alienware_update_led(target_zone); 554 return ret ? ret : count; 555 } 556 557 /* 558 * LED Brightness (Global) 559 */ 560 static int wmax_brightness(int brightness) 561 { 562 acpi_status status; 563 struct acpi_buffer input; 564 struct wmax_brightness_args args = { 565 .led_mask = 0xFF, 566 .percentage = brightness, 567 }; 568 input.length = sizeof(args); 569 input.pointer = &args; 570 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 571 WMAX_METHOD_BRIGHTNESS, &input, NULL); 572 if (ACPI_FAILURE(status)) 573 pr_err("alienware-wmi: brightness set failure: %u\n", status); 574 return ACPI_FAILURE(status); 575 } 576 577 static void global_led_set(struct led_classdev *led_cdev, 578 enum led_brightness brightness) 579 { 580 int ret; 581 global_brightness = brightness; 582 if (interface == WMAX) 583 ret = wmax_brightness(brightness); 584 else 585 ret = alienware_update_led(&zone_data[0]); 586 if (ret) 587 pr_err("LED brightness update failed\n"); 588 } 589 590 static enum led_brightness global_led_get(struct led_classdev *led_cdev) 591 { 592 return global_brightness; 593 } 594 595 static struct led_classdev global_led = { 596 .brightness_set = global_led_set, 597 .brightness_get = global_led_get, 598 .name = "alienware::global_brightness", 599 }; 600 601 /* 602 * Lighting control state device attribute (Global) 603 */ 604 static ssize_t show_control_state(struct device *dev, 605 struct device_attribute *attr, char *buf) 606 { 607 if (lighting_control_state == LEGACY_BOOTING) 608 return sysfs_emit(buf, "[booting] running suspend\n"); 609 else if (lighting_control_state == LEGACY_SUSPEND) 610 return sysfs_emit(buf, "booting running [suspend]\n"); 611 return sysfs_emit(buf, "booting [running] suspend\n"); 612 } 613 614 static ssize_t store_control_state(struct device *dev, 615 struct device_attribute *attr, 616 const char *buf, size_t count) 617 { 618 long unsigned int val; 619 if (strcmp(buf, "booting\n") == 0) 620 val = LEGACY_BOOTING; 621 else if (strcmp(buf, "suspend\n") == 0) 622 val = LEGACY_SUSPEND; 623 else if (interface == LEGACY) 624 val = LEGACY_RUNNING; 625 else 626 val = WMAX_RUNNING; 627 lighting_control_state = val; 628 pr_debug("alienware-wmi: updated control state to %d\n", 629 lighting_control_state); 630 return count; 631 } 632 633 static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, 634 store_control_state); 635 636 static int alienware_zone_init(struct platform_device *dev) 637 { 638 u8 zone; 639 char *name; 640 641 if (interface == WMAX) { 642 lighting_control_state = WMAX_RUNNING; 643 } else if (interface == LEGACY) { 644 lighting_control_state = LEGACY_RUNNING; 645 } 646 global_led.max_brightness = 0x0F; 647 global_brightness = global_led.max_brightness; 648 649 /* 650 * - zone_dev_attrs num_zones + 1 is for individual zones and then 651 * null terminated 652 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + 653 * the lighting control + null terminated 654 * - zone_data num_zones is for the distinct zones 655 */ 656 zone_dev_attrs = 657 kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), 658 GFP_KERNEL); 659 if (!zone_dev_attrs) 660 return -ENOMEM; 661 662 zone_attrs = 663 kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), 664 GFP_KERNEL); 665 if (!zone_attrs) 666 return -ENOMEM; 667 668 zone_data = 669 kcalloc(quirks->num_zones, sizeof(struct platform_zone), 670 GFP_KERNEL); 671 if (!zone_data) 672 return -ENOMEM; 673 674 for (zone = 0; zone < quirks->num_zones; zone++) { 675 name = kasprintf(GFP_KERNEL, "zone%02hhX", zone); 676 if (name == NULL) 677 return 1; 678 sysfs_attr_init(&zone_dev_attrs[zone].attr); 679 zone_dev_attrs[zone].attr.name = name; 680 zone_dev_attrs[zone].attr.mode = 0644; 681 zone_dev_attrs[zone].show = zone_show; 682 zone_dev_attrs[zone].store = zone_set; 683 zone_data[zone].location = zone; 684 zone_attrs[zone] = &zone_dev_attrs[zone].attr; 685 zone_data[zone].attr = &zone_dev_attrs[zone]; 686 } 687 zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; 688 zone_attribute_group.attrs = zone_attrs; 689 690 led_classdev_register(&dev->dev, &global_led); 691 692 return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); 693 } 694 695 static void alienware_zone_exit(struct platform_device *dev) 696 { 697 u8 zone; 698 699 if (!quirks->num_zones) 700 return; 701 702 sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); 703 led_classdev_unregister(&global_led); 704 if (zone_dev_attrs) { 705 for (zone = 0; zone < quirks->num_zones; zone++) 706 kfree(zone_dev_attrs[zone].attr.name); 707 } 708 kfree(zone_dev_attrs); 709 kfree(zone_data); 710 kfree(zone_attrs); 711 } 712 713 static acpi_status alienware_wmax_command(void *in_args, size_t in_size, 714 u32 command, u32 *out_data) 715 { 716 acpi_status status; 717 union acpi_object *obj; 718 struct acpi_buffer input; 719 struct acpi_buffer output; 720 721 input.length = in_size; 722 input.pointer = in_args; 723 if (out_data) { 724 output.length = ACPI_ALLOCATE_BUFFER; 725 output.pointer = NULL; 726 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 727 command, &input, &output); 728 if (ACPI_SUCCESS(status)) { 729 obj = (union acpi_object *)output.pointer; 730 if (obj && obj->type == ACPI_TYPE_INTEGER) 731 *out_data = (u32)obj->integer.value; 732 } 733 kfree(output.pointer); 734 } else { 735 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 736 command, &input, NULL); 737 } 738 return status; 739 } 740 741 /* 742 * The HDMI mux sysfs node indicates the status of the HDMI input mux. 743 * It can toggle between standard system GPU output and HDMI input. 744 */ 745 static ssize_t show_hdmi_cable(struct device *dev, 746 struct device_attribute *attr, char *buf) 747 { 748 acpi_status status; 749 u32 out_data; 750 struct wmax_basic_args in_args = { 751 .arg = 0, 752 }; 753 status = 754 alienware_wmax_command(&in_args, sizeof(in_args), 755 WMAX_METHOD_HDMI_CABLE, &out_data); 756 if (ACPI_SUCCESS(status)) { 757 if (out_data == 0) 758 return sysfs_emit(buf, "[unconnected] connected unknown\n"); 759 else if (out_data == 1) 760 return sysfs_emit(buf, "unconnected [connected] unknown\n"); 761 } 762 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); 763 return sysfs_emit(buf, "unconnected connected [unknown]\n"); 764 } 765 766 static ssize_t show_hdmi_source(struct device *dev, 767 struct device_attribute *attr, char *buf) 768 { 769 acpi_status status; 770 u32 out_data; 771 struct wmax_basic_args in_args = { 772 .arg = 0, 773 }; 774 status = 775 alienware_wmax_command(&in_args, sizeof(in_args), 776 WMAX_METHOD_HDMI_STATUS, &out_data); 777 778 if (ACPI_SUCCESS(status)) { 779 if (out_data == 1) 780 return sysfs_emit(buf, "[input] gpu unknown\n"); 781 else if (out_data == 2) 782 return sysfs_emit(buf, "input [gpu] unknown\n"); 783 } 784 pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); 785 return sysfs_emit(buf, "input gpu [unknown]\n"); 786 } 787 788 static ssize_t toggle_hdmi_source(struct device *dev, 789 struct device_attribute *attr, 790 const char *buf, size_t count) 791 { 792 acpi_status status; 793 struct wmax_basic_args args; 794 if (strcmp(buf, "gpu\n") == 0) 795 args.arg = 1; 796 else if (strcmp(buf, "input\n") == 0) 797 args.arg = 2; 798 else 799 args.arg = 3; 800 pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); 801 802 status = alienware_wmax_command(&args, sizeof(args), 803 WMAX_METHOD_HDMI_SOURCE, NULL); 804 805 if (ACPI_FAILURE(status)) 806 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", 807 status); 808 return count; 809 } 810 811 static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); 812 static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, 813 toggle_hdmi_source); 814 815 static struct attribute *hdmi_attrs[] = { 816 &dev_attr_cable.attr, 817 &dev_attr_source.attr, 818 NULL, 819 }; 820 821 static const struct attribute_group hdmi_attribute_group = { 822 .name = "hdmi", 823 .attrs = hdmi_attrs, 824 }; 825 826 static void remove_hdmi(struct platform_device *dev) 827 { 828 if (quirks->hdmi_mux > 0) 829 sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); 830 } 831 832 static int create_hdmi(struct platform_device *dev) 833 { 834 int ret; 835 836 ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); 837 if (ret) 838 remove_hdmi(dev); 839 return ret; 840 } 841 842 /* 843 * Alienware GFX amplifier support 844 * - Currently supports reading cable status 845 * - Leaving expansion room to possibly support dock/undock events later 846 */ 847 static ssize_t show_amplifier_status(struct device *dev, 848 struct device_attribute *attr, char *buf) 849 { 850 acpi_status status; 851 u32 out_data; 852 struct wmax_basic_args in_args = { 853 .arg = 0, 854 }; 855 status = 856 alienware_wmax_command(&in_args, sizeof(in_args), 857 WMAX_METHOD_AMPLIFIER_CABLE, &out_data); 858 if (ACPI_SUCCESS(status)) { 859 if (out_data == 0) 860 return sysfs_emit(buf, "[unconnected] connected unknown\n"); 861 else if (out_data == 1) 862 return sysfs_emit(buf, "unconnected [connected] unknown\n"); 863 } 864 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); 865 return sysfs_emit(buf, "unconnected connected [unknown]\n"); 866 } 867 868 static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); 869 870 static struct attribute *amplifier_attrs[] = { 871 &dev_attr_status.attr, 872 NULL, 873 }; 874 875 static const struct attribute_group amplifier_attribute_group = { 876 .name = "amplifier", 877 .attrs = amplifier_attrs, 878 }; 879 880 static void remove_amplifier(struct platform_device *dev) 881 { 882 if (quirks->amplifier > 0) 883 sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); 884 } 885 886 static int create_amplifier(struct platform_device *dev) 887 { 888 int ret; 889 890 ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); 891 if (ret) 892 remove_amplifier(dev); 893 return ret; 894 } 895 896 /* 897 * Deep Sleep Control support 898 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events 899 */ 900 static ssize_t show_deepsleep_status(struct device *dev, 901 struct device_attribute *attr, char *buf) 902 { 903 acpi_status status; 904 u32 out_data; 905 struct wmax_basic_args in_args = { 906 .arg = 0, 907 }; 908 status = alienware_wmax_command(&in_args, sizeof(in_args), 909 WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data); 910 if (ACPI_SUCCESS(status)) { 911 if (out_data == 0) 912 return sysfs_emit(buf, "[disabled] s5 s5_s4\n"); 913 else if (out_data == 1) 914 return sysfs_emit(buf, "disabled [s5] s5_s4\n"); 915 else if (out_data == 2) 916 return sysfs_emit(buf, "disabled s5 [s5_s4]\n"); 917 } 918 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); 919 return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n"); 920 } 921 922 static ssize_t toggle_deepsleep(struct device *dev, 923 struct device_attribute *attr, 924 const char *buf, size_t count) 925 { 926 acpi_status status; 927 struct wmax_basic_args args; 928 929 if (strcmp(buf, "disabled\n") == 0) 930 args.arg = 0; 931 else if (strcmp(buf, "s5\n") == 0) 932 args.arg = 1; 933 else 934 args.arg = 2; 935 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); 936 937 status = alienware_wmax_command(&args, sizeof(args), 938 WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL); 939 940 if (ACPI_FAILURE(status)) 941 pr_err("alienware-wmi: deep sleep control failed: results: %u\n", 942 status); 943 return count; 944 } 945 946 static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); 947 948 static struct attribute *deepsleep_attrs[] = { 949 &dev_attr_deepsleep.attr, 950 NULL, 951 }; 952 953 static const struct attribute_group deepsleep_attribute_group = { 954 .name = "deepsleep", 955 .attrs = deepsleep_attrs, 956 }; 957 958 static void remove_deepsleep(struct platform_device *dev) 959 { 960 if (quirks->deepslp > 0) 961 sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); 962 } 963 964 static int create_deepsleep(struct platform_device *dev) 965 { 966 int ret; 967 968 ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); 969 if (ret) 970 remove_deepsleep(dev); 971 return ret; 972 } 973 974 /* 975 * Thermal Profile control 976 * - Provides thermal profile control through the Platform Profile API 977 */ 978 #define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4) 979 #define WMAX_THERMAL_MODE_MASK GENMASK(3, 0) 980 #define WMAX_SENSOR_ID_MASK BIT(8) 981 982 static bool is_wmax_thermal_code(u32 code) 983 { 984 if (code & WMAX_SENSOR_ID_MASK) 985 return false; 986 987 if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST) 988 return false; 989 990 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC && 991 (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET) 992 return true; 993 994 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT && 995 (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER) 996 return true; 997 998 return false; 999 } 1000 1001 static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data) 1002 { 1003 acpi_status status; 1004 struct wmax_u32_args in_args = { 1005 .operation = operation, 1006 .arg1 = arg, 1007 .arg2 = 0, 1008 .arg3 = 0, 1009 }; 1010 1011 status = alienware_wmax_command(&in_args, sizeof(in_args), 1012 WMAX_METHOD_THERMAL_INFORMATION, 1013 out_data); 1014 1015 if (ACPI_FAILURE(status)) 1016 return -EIO; 1017 1018 if (*out_data == WMAX_FAILURE_CODE) 1019 return -EBADRQC; 1020 1021 return 0; 1022 } 1023 1024 static int wmax_thermal_control(u8 profile) 1025 { 1026 acpi_status status; 1027 struct wmax_u32_args in_args = { 1028 .operation = WMAX_OPERATION_ACTIVATE_PROFILE, 1029 .arg1 = profile, 1030 .arg2 = 0, 1031 .arg3 = 0, 1032 }; 1033 u32 out_data; 1034 1035 status = alienware_wmax_command(&in_args, sizeof(in_args), 1036 WMAX_METHOD_THERMAL_CONTROL, 1037 &out_data); 1038 1039 if (ACPI_FAILURE(status)) 1040 return -EIO; 1041 1042 if (out_data == WMAX_FAILURE_CODE) 1043 return -EBADRQC; 1044 1045 return 0; 1046 } 1047 1048 static int wmax_game_shift_status(u8 operation, u32 *out_data) 1049 { 1050 acpi_status status; 1051 struct wmax_u32_args in_args = { 1052 .operation = operation, 1053 .arg1 = 0, 1054 .arg2 = 0, 1055 .arg3 = 0, 1056 }; 1057 1058 status = alienware_wmax_command(&in_args, sizeof(in_args), 1059 WMAX_METHOD_GAME_SHIFT_STATUS, 1060 out_data); 1061 1062 if (ACPI_FAILURE(status)) 1063 return -EIO; 1064 1065 if (*out_data == WMAX_FAILURE_CODE) 1066 return -EOPNOTSUPP; 1067 1068 return 0; 1069 } 1070 1071 static int thermal_profile_get(struct platform_profile_handler *pprof, 1072 enum platform_profile_option *profile) 1073 { 1074 u32 out_data; 1075 int ret; 1076 1077 ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE, 1078 0, &out_data); 1079 1080 if (ret < 0) 1081 return ret; 1082 1083 if (out_data == WMAX_THERMAL_MODE_GMODE) { 1084 *profile = PLATFORM_PROFILE_PERFORMANCE; 1085 return 0; 1086 } 1087 1088 if (!is_wmax_thermal_code(out_data)) 1089 return -ENODATA; 1090 1091 out_data &= WMAX_THERMAL_MODE_MASK; 1092 *profile = wmax_mode_to_platform_profile[out_data]; 1093 1094 return 0; 1095 } 1096 1097 static int thermal_profile_set(struct platform_profile_handler *pprof, 1098 enum platform_profile_option profile) 1099 { 1100 if (quirks->gmode) { 1101 u32 gmode_status; 1102 int ret; 1103 1104 ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS, 1105 &gmode_status); 1106 1107 if (ret < 0) 1108 return ret; 1109 1110 if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || 1111 (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { 1112 ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT, 1113 &gmode_status); 1114 1115 if (ret < 0) 1116 return ret; 1117 } 1118 } 1119 1120 return wmax_thermal_control(supported_thermal_profiles[profile]); 1121 } 1122 1123 static int create_thermal_profile(void) 1124 { 1125 u32 out_data; 1126 u8 sys_desc[4]; 1127 u32 first_mode; 1128 enum wmax_thermal_mode mode; 1129 enum platform_profile_option profile; 1130 int ret; 1131 1132 ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION, 1133 0, (u32 *) &sys_desc); 1134 if (ret < 0) 1135 return ret; 1136 1137 first_mode = sys_desc[0] + sys_desc[1]; 1138 1139 for (u32 i = 0; i < sys_desc[3]; i++) { 1140 ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS, 1141 i + first_mode, &out_data); 1142 1143 if (ret == -EIO) 1144 return ret; 1145 1146 if (ret == -EBADRQC) 1147 break; 1148 1149 if (!is_wmax_thermal_code(out_data)) 1150 continue; 1151 1152 mode = out_data & WMAX_THERMAL_MODE_MASK; 1153 profile = wmax_mode_to_platform_profile[mode]; 1154 supported_thermal_profiles[profile] = out_data; 1155 1156 set_bit(profile, pp_handler.choices); 1157 } 1158 1159 if (bitmap_empty(pp_handler.choices, PLATFORM_PROFILE_LAST)) 1160 return -ENODEV; 1161 1162 if (quirks->gmode) { 1163 supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] = 1164 WMAX_THERMAL_MODE_GMODE; 1165 1166 set_bit(PLATFORM_PROFILE_PERFORMANCE, pp_handler.choices); 1167 } 1168 1169 pp_handler.profile_get = thermal_profile_get; 1170 pp_handler.profile_set = thermal_profile_set; 1171 1172 return platform_profile_register(&pp_handler); 1173 } 1174 1175 static void remove_thermal_profile(void) 1176 { 1177 if (quirks->thermal) 1178 platform_profile_remove(); 1179 } 1180 1181 static int __init alienware_wmi_init(void) 1182 { 1183 int ret; 1184 1185 if (wmi_has_guid(LEGACY_CONTROL_GUID)) 1186 interface = LEGACY; 1187 else if (wmi_has_guid(WMAX_CONTROL_GUID)) 1188 interface = WMAX; 1189 else { 1190 pr_warn("alienware-wmi: No known WMI GUID found\n"); 1191 return -ENODEV; 1192 } 1193 1194 dmi_check_system(alienware_quirks); 1195 if (quirks == NULL) 1196 quirks = &quirk_unknown; 1197 1198 if (force_platform_profile) 1199 quirks->thermal = true; 1200 1201 if (force_gmode) { 1202 if (quirks->thermal) 1203 quirks->gmode = true; 1204 else 1205 pr_warn("force_gmode requires platform profile support\n"); 1206 } 1207 1208 ret = platform_driver_register(&platform_driver); 1209 if (ret) 1210 goto fail_platform_driver; 1211 platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE); 1212 if (!platform_device) { 1213 ret = -ENOMEM; 1214 goto fail_platform_device1; 1215 } 1216 ret = platform_device_add(platform_device); 1217 if (ret) 1218 goto fail_platform_device2; 1219 1220 if (quirks->hdmi_mux > 0) { 1221 ret = create_hdmi(platform_device); 1222 if (ret) 1223 goto fail_prep_hdmi; 1224 } 1225 1226 if (quirks->amplifier > 0) { 1227 ret = create_amplifier(platform_device); 1228 if (ret) 1229 goto fail_prep_amplifier; 1230 } 1231 1232 if (quirks->deepslp > 0) { 1233 ret = create_deepsleep(platform_device); 1234 if (ret) 1235 goto fail_prep_deepsleep; 1236 } 1237 1238 if (quirks->thermal) { 1239 ret = create_thermal_profile(); 1240 if (ret) 1241 goto fail_prep_thermal_profile; 1242 } 1243 1244 if (quirks->num_zones > 0) { 1245 ret = alienware_zone_init(platform_device); 1246 if (ret) 1247 goto fail_prep_zones; 1248 } 1249 1250 return 0; 1251 1252 fail_prep_zones: 1253 alienware_zone_exit(platform_device); 1254 remove_thermal_profile(); 1255 fail_prep_thermal_profile: 1256 fail_prep_deepsleep: 1257 fail_prep_amplifier: 1258 fail_prep_hdmi: 1259 platform_device_del(platform_device); 1260 fail_platform_device2: 1261 platform_device_put(platform_device); 1262 fail_platform_device1: 1263 platform_driver_unregister(&platform_driver); 1264 fail_platform_driver: 1265 return ret; 1266 } 1267 1268 module_init(alienware_wmi_init); 1269 1270 static void __exit alienware_wmi_exit(void) 1271 { 1272 if (platform_device) { 1273 alienware_zone_exit(platform_device); 1274 remove_hdmi(platform_device); 1275 remove_thermal_profile(); 1276 platform_device_unregister(platform_device); 1277 platform_driver_unregister(&platform_driver); 1278 } 1279 } 1280 1281 module_exit(alienware_wmi_exit); 1282