alienware-wmi.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. /*
  2. * Alienware AlienFX control
  3. *
  4. * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. */
  17. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  18. #include <linux/acpi.h>
  19. #include <linux/module.h>
  20. #include <linux/platform_device.h>
  21. #include <linux/dmi.h>
  22. #include <linux/acpi.h>
  23. #include <linux/leds.h>
  24. #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
  25. #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
  26. #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
  27. #define WMAX_METHOD_HDMI_SOURCE 0x1
  28. #define WMAX_METHOD_HDMI_STATUS 0x2
  29. #define WMAX_METHOD_BRIGHTNESS 0x3
  30. #define WMAX_METHOD_ZONE_CONTROL 0x4
  31. #define WMAX_METHOD_HDMI_CABLE 0x5
  32. MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
  33. MODULE_DESCRIPTION("Alienware special feature control");
  34. MODULE_LICENSE("GPL");
  35. MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
  36. MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
  37. enum INTERFACE_FLAGS {
  38. LEGACY,
  39. WMAX,
  40. };
  41. enum LEGACY_CONTROL_STATES {
  42. LEGACY_RUNNING = 1,
  43. LEGACY_BOOTING = 0,
  44. LEGACY_SUSPEND = 3,
  45. };
  46. enum WMAX_CONTROL_STATES {
  47. WMAX_RUNNING = 0xFF,
  48. WMAX_BOOTING = 0,
  49. WMAX_SUSPEND = 3,
  50. };
  51. struct quirk_entry {
  52. u8 num_zones;
  53. };
  54. static struct quirk_entry *quirks;
  55. static struct quirk_entry quirk_unknown = {
  56. .num_zones = 2,
  57. };
  58. static struct quirk_entry quirk_x51_family = {
  59. .num_zones = 3,
  60. };
  61. static int dmi_matched(const struct dmi_system_id *dmi)
  62. {
  63. quirks = dmi->driver_data;
  64. return 1;
  65. }
  66. static struct dmi_system_id alienware_quirks[] = {
  67. {
  68. .callback = dmi_matched,
  69. .ident = "Alienware X51 R1",
  70. .matches = {
  71. DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
  72. DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
  73. },
  74. .driver_data = &quirk_x51_family,
  75. },
  76. {
  77. .callback = dmi_matched,
  78. .ident = "Alienware X51 R2",
  79. .matches = {
  80. DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
  81. DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
  82. },
  83. .driver_data = &quirk_x51_family,
  84. },
  85. {}
  86. };
  87. struct color_platform {
  88. u8 blue;
  89. u8 green;
  90. u8 red;
  91. } __packed;
  92. struct platform_zone {
  93. u8 location;
  94. struct device_attribute *attr;
  95. struct color_platform colors;
  96. };
  97. struct wmax_brightness_args {
  98. u32 led_mask;
  99. u32 percentage;
  100. };
  101. struct hdmi_args {
  102. u8 arg;
  103. };
  104. struct legacy_led_args {
  105. struct color_platform colors;
  106. u8 brightness;
  107. u8 state;
  108. } __packed;
  109. struct wmax_led_args {
  110. u32 led_mask;
  111. struct color_platform colors;
  112. u8 state;
  113. } __packed;
  114. static struct platform_device *platform_device;
  115. static struct device_attribute *zone_dev_attrs;
  116. static struct attribute **zone_attrs;
  117. static struct platform_zone *zone_data;
  118. static struct platform_driver platform_driver = {
  119. .driver = {
  120. .name = "alienware-wmi",
  121. .owner = THIS_MODULE,
  122. }
  123. };
  124. static struct attribute_group zone_attribute_group = {
  125. .name = "rgb_zones",
  126. };
  127. static u8 interface;
  128. static u8 lighting_control_state;
  129. static u8 global_brightness;
  130. /*
  131. * Helpers used for zone control
  132. */
  133. static int parse_rgb(const char *buf, struct platform_zone *zone)
  134. {
  135. long unsigned int rgb;
  136. int ret;
  137. union color_union {
  138. struct color_platform cp;
  139. int package;
  140. } repackager;
  141. ret = kstrtoul(buf, 16, &rgb);
  142. if (ret)
  143. return ret;
  144. /* RGB triplet notation is 24-bit hexadecimal */
  145. if (rgb > 0xFFFFFF)
  146. return -EINVAL;
  147. repackager.package = rgb & 0x0f0f0f0f;
  148. pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
  149. repackager.cp.red, repackager.cp.green, repackager.cp.blue);
  150. zone->colors = repackager.cp;
  151. return 0;
  152. }
  153. static struct platform_zone *match_zone(struct device_attribute *attr)
  154. {
  155. int i;
  156. for (i = 0; i < quirks->num_zones; i++) {
  157. if ((struct device_attribute *)zone_data[i].attr == attr) {
  158. pr_debug("alienware-wmi: matched zone location: %d\n",
  159. zone_data[i].location);
  160. return &zone_data[i];
  161. }
  162. }
  163. return NULL;
  164. }
  165. /*
  166. * Individual RGB zone control
  167. */
  168. static int alienware_update_led(struct platform_zone *zone)
  169. {
  170. int method_id;
  171. acpi_status status;
  172. char *guid;
  173. struct acpi_buffer input;
  174. struct legacy_led_args legacy_args;
  175. struct wmax_led_args wmax_args;
  176. if (interface == WMAX) {
  177. wmax_args.led_mask = 1 << zone->location;
  178. wmax_args.colors = zone->colors;
  179. wmax_args.state = lighting_control_state;
  180. guid = WMAX_CONTROL_GUID;
  181. method_id = WMAX_METHOD_ZONE_CONTROL;
  182. input.length = (acpi_size) sizeof(wmax_args);
  183. input.pointer = &wmax_args;
  184. } else {
  185. legacy_args.colors = zone->colors;
  186. legacy_args.brightness = global_brightness;
  187. legacy_args.state = 0;
  188. if (lighting_control_state == LEGACY_BOOTING ||
  189. lighting_control_state == LEGACY_SUSPEND) {
  190. guid = LEGACY_POWER_CONTROL_GUID;
  191. legacy_args.state = lighting_control_state;
  192. } else
  193. guid = LEGACY_CONTROL_GUID;
  194. method_id = zone->location + 1;
  195. input.length = (acpi_size) sizeof(legacy_args);
  196. input.pointer = &legacy_args;
  197. }
  198. pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
  199. status = wmi_evaluate_method(guid, 1, method_id, &input, NULL);
  200. if (ACPI_FAILURE(status))
  201. pr_err("alienware-wmi: zone set failure: %u\n", status);
  202. return ACPI_FAILURE(status);
  203. }
  204. static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
  205. char *buf)
  206. {
  207. struct platform_zone *target_zone;
  208. target_zone = match_zone(attr);
  209. if (target_zone == NULL)
  210. return sprintf(buf, "red: -1, green: -1, blue: -1\n");
  211. return sprintf(buf, "red: %d, green: %d, blue: %d\n",
  212. target_zone->colors.red,
  213. target_zone->colors.green, target_zone->colors.blue);
  214. }
  215. static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
  216. const char *buf, size_t count)
  217. {
  218. struct platform_zone *target_zone;
  219. int ret;
  220. target_zone = match_zone(attr);
  221. if (target_zone == NULL) {
  222. pr_err("alienware-wmi: invalid target zone\n");
  223. return 1;
  224. }
  225. ret = parse_rgb(buf, target_zone);
  226. if (ret)
  227. return ret;
  228. ret = alienware_update_led(target_zone);
  229. return ret ? ret : count;
  230. }
  231. /*
  232. * LED Brightness (Global)
  233. */
  234. static int wmax_brightness(int brightness)
  235. {
  236. acpi_status status;
  237. struct acpi_buffer input;
  238. struct wmax_brightness_args args = {
  239. .led_mask = 0xFF,
  240. .percentage = brightness,
  241. };
  242. input.length = (acpi_size) sizeof(args);
  243. input.pointer = &args;
  244. status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
  245. WMAX_METHOD_BRIGHTNESS, &input, NULL);
  246. if (ACPI_FAILURE(status))
  247. pr_err("alienware-wmi: brightness set failure: %u\n", status);
  248. return ACPI_FAILURE(status);
  249. }
  250. static void global_led_set(struct led_classdev *led_cdev,
  251. enum led_brightness brightness)
  252. {
  253. int ret;
  254. global_brightness = brightness;
  255. if (interface == WMAX)
  256. ret = wmax_brightness(brightness);
  257. else
  258. ret = alienware_update_led(&zone_data[0]);
  259. if (ret)
  260. pr_err("LED brightness update failed\n");
  261. }
  262. static enum led_brightness global_led_get(struct led_classdev *led_cdev)
  263. {
  264. return global_brightness;
  265. }
  266. static struct led_classdev global_led = {
  267. .brightness_set = global_led_set,
  268. .brightness_get = global_led_get,
  269. .name = "alienware::global_brightness",
  270. };
  271. /*
  272. * Lighting control state device attribute (Global)
  273. */
  274. static ssize_t show_control_state(struct device *dev,
  275. struct device_attribute *attr, char *buf)
  276. {
  277. if (lighting_control_state == LEGACY_BOOTING)
  278. return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n");
  279. else if (lighting_control_state == LEGACY_SUSPEND)
  280. return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n");
  281. return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n");
  282. }
  283. static ssize_t store_control_state(struct device *dev,
  284. struct device_attribute *attr,
  285. const char *buf, size_t count)
  286. {
  287. long unsigned int val;
  288. if (strcmp(buf, "booting\n") == 0)
  289. val = LEGACY_BOOTING;
  290. else if (strcmp(buf, "suspend\n") == 0)
  291. val = LEGACY_SUSPEND;
  292. else if (interface == LEGACY)
  293. val = LEGACY_RUNNING;
  294. else
  295. val = WMAX_RUNNING;
  296. lighting_control_state = val;
  297. pr_debug("alienware-wmi: updated control state to %d\n",
  298. lighting_control_state);
  299. return count;
  300. }
  301. static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,
  302. store_control_state);
  303. static int alienware_zone_init(struct platform_device *dev)
  304. {
  305. int i;
  306. char buffer[10];
  307. char *name;
  308. if (interface == WMAX) {
  309. lighting_control_state = WMAX_RUNNING;
  310. } else if (interface == LEGACY) {
  311. lighting_control_state = LEGACY_RUNNING;
  312. }
  313. global_led.max_brightness = 0x0F;
  314. global_brightness = global_led.max_brightness;
  315. /*
  316. * - zone_dev_attrs num_zones + 1 is for individual zones and then
  317. * null terminated
  318. * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
  319. * the lighting control + null terminated
  320. * - zone_data num_zones is for the distinct zones
  321. */
  322. zone_dev_attrs =
  323. kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1),
  324. GFP_KERNEL);
  325. if (!zone_dev_attrs)
  326. return -ENOMEM;
  327. zone_attrs =
  328. kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2),
  329. GFP_KERNEL);
  330. if (!zone_attrs)
  331. return -ENOMEM;
  332. zone_data =
  333. kzalloc(sizeof(struct platform_zone) * (quirks->num_zones),
  334. GFP_KERNEL);
  335. if (!zone_data)
  336. return -ENOMEM;
  337. for (i = 0; i < quirks->num_zones; i++) {
  338. sprintf(buffer, "zone%02X", i);
  339. name = kstrdup(buffer, GFP_KERNEL);
  340. if (name == NULL)
  341. return 1;
  342. sysfs_attr_init(&zone_dev_attrs[i].attr);
  343. zone_dev_attrs[i].attr.name = name;
  344. zone_dev_attrs[i].attr.mode = 0644;
  345. zone_dev_attrs[i].show = zone_show;
  346. zone_dev_attrs[i].store = zone_set;
  347. zone_data[i].location = i;
  348. zone_attrs[i] = &zone_dev_attrs[i].attr;
  349. zone_data[i].attr = &zone_dev_attrs[i];
  350. }
  351. zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;
  352. zone_attribute_group.attrs = zone_attrs;
  353. led_classdev_register(&dev->dev, &global_led);
  354. return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
  355. }
  356. static void alienware_zone_exit(struct platform_device *dev)
  357. {
  358. sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
  359. led_classdev_unregister(&global_led);
  360. if (zone_dev_attrs) {
  361. int i;
  362. for (i = 0; i < quirks->num_zones; i++)
  363. kfree(zone_dev_attrs[i].attr.name);
  364. }
  365. kfree(zone_dev_attrs);
  366. kfree(zone_data);
  367. kfree(zone_attrs);
  368. }
  369. /*
  370. The HDMI mux sysfs node indicates the status of the HDMI input mux.
  371. It can toggle between standard system GPU output and HDMI input.
  372. */
  373. static acpi_status alienware_hdmi_command(struct hdmi_args *in_args,
  374. u32 command, int *out_data)
  375. {
  376. acpi_status status;
  377. union acpi_object *obj;
  378. struct acpi_buffer input;
  379. struct acpi_buffer output;
  380. input.length = (acpi_size) sizeof(*in_args);
  381. input.pointer = in_args;
  382. if (out_data != NULL) {
  383. output.length = ACPI_ALLOCATE_BUFFER;
  384. output.pointer = NULL;
  385. status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
  386. command, &input, &output);
  387. } else
  388. status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
  389. command, &input, NULL);
  390. if (ACPI_SUCCESS(status) && out_data != NULL) {
  391. obj = (union acpi_object *)output.pointer;
  392. if (obj && obj->type == ACPI_TYPE_INTEGER)
  393. *out_data = (u32) obj->integer.value;
  394. }
  395. return status;
  396. }
  397. static ssize_t show_hdmi_cable(struct device *dev,
  398. struct device_attribute *attr, char *buf)
  399. {
  400. acpi_status status;
  401. u32 out_data;
  402. struct hdmi_args in_args = {
  403. .arg = 0,
  404. };
  405. status =
  406. alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_CABLE,
  407. (u32 *) &out_data);
  408. if (ACPI_SUCCESS(status)) {
  409. if (out_data == 0)
  410. return scnprintf(buf, PAGE_SIZE,
  411. "[unconnected] connected unknown\n");
  412. else if (out_data == 1)
  413. return scnprintf(buf, PAGE_SIZE,
  414. "unconnected [connected] unknown\n");
  415. }
  416. pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
  417. return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
  418. }
  419. static ssize_t show_hdmi_source(struct device *dev,
  420. struct device_attribute *attr, char *buf)
  421. {
  422. acpi_status status;
  423. u32 out_data;
  424. struct hdmi_args in_args = {
  425. .arg = 0,
  426. };
  427. status =
  428. alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_STATUS,
  429. (u32 *) &out_data);
  430. if (ACPI_SUCCESS(status)) {
  431. if (out_data == 1)
  432. return scnprintf(buf, PAGE_SIZE,
  433. "[input] gpu unknown\n");
  434. else if (out_data == 2)
  435. return scnprintf(buf, PAGE_SIZE,
  436. "input [gpu] unknown\n");
  437. }
  438. pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data);
  439. return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
  440. }
  441. static ssize_t toggle_hdmi_source(struct device *dev,
  442. struct device_attribute *attr,
  443. const char *buf, size_t count)
  444. {
  445. acpi_status status;
  446. struct hdmi_args args;
  447. if (strcmp(buf, "gpu\n") == 0)
  448. args.arg = 1;
  449. else if (strcmp(buf, "input\n") == 0)
  450. args.arg = 2;
  451. else
  452. args.arg = 3;
  453. pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
  454. status = alienware_hdmi_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
  455. if (ACPI_FAILURE(status))
  456. pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
  457. status);
  458. return count;
  459. }
  460. static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
  461. static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
  462. toggle_hdmi_source);
  463. static struct attribute *hdmi_attrs[] = {
  464. &dev_attr_cable.attr,
  465. &dev_attr_source.attr,
  466. NULL,
  467. };
  468. static struct attribute_group hdmi_attribute_group = {
  469. .name = "hdmi",
  470. .attrs = hdmi_attrs,
  471. };
  472. static void remove_hdmi(struct platform_device *dev)
  473. {
  474. sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
  475. }
  476. static int create_hdmi(struct platform_device *dev)
  477. {
  478. int ret;
  479. ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
  480. if (ret)
  481. goto error_create_hdmi;
  482. return 0;
  483. error_create_hdmi:
  484. remove_hdmi(dev);
  485. return ret;
  486. }
  487. static int __init alienware_wmi_init(void)
  488. {
  489. int ret;
  490. if (wmi_has_guid(LEGACY_CONTROL_GUID))
  491. interface = LEGACY;
  492. else if (wmi_has_guid(WMAX_CONTROL_GUID))
  493. interface = WMAX;
  494. else {
  495. pr_warn("alienware-wmi: No known WMI GUID found\n");
  496. return -ENODEV;
  497. }
  498. dmi_check_system(alienware_quirks);
  499. if (quirks == NULL)
  500. quirks = &quirk_unknown;
  501. ret = platform_driver_register(&platform_driver);
  502. if (ret)
  503. goto fail_platform_driver;
  504. platform_device = platform_device_alloc("alienware-wmi", -1);
  505. if (!platform_device) {
  506. ret = -ENOMEM;
  507. goto fail_platform_device1;
  508. }
  509. ret = platform_device_add(platform_device);
  510. if (ret)
  511. goto fail_platform_device2;
  512. if (interface == WMAX) {
  513. ret = create_hdmi(platform_device);
  514. if (ret)
  515. goto fail_prep_hdmi;
  516. }
  517. ret = alienware_zone_init(platform_device);
  518. if (ret)
  519. goto fail_prep_zones;
  520. return 0;
  521. fail_prep_zones:
  522. alienware_zone_exit(platform_device);
  523. fail_prep_hdmi:
  524. platform_device_del(platform_device);
  525. fail_platform_device2:
  526. platform_device_put(platform_device);
  527. fail_platform_device1:
  528. platform_driver_unregister(&platform_driver);
  529. fail_platform_driver:
  530. return ret;
  531. }
  532. module_init(alienware_wmi_init);
  533. static void __exit alienware_wmi_exit(void)
  534. {
  535. if (platform_device) {
  536. alienware_zone_exit(platform_device);
  537. remove_hdmi(platform_device);
  538. platform_device_unregister(platform_device);
  539. platform_driver_unregister(&platform_driver);
  540. }
  541. }
  542. module_exit(alienware_wmi_exit);