system76_acpi.c 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * System76 ACPI Driver
  4. *
  5. * Copyright (C) 2019 System76
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License version 2 as
  9. * published by the Free Software Foundation.
  10. */
  11. #include <linux/acpi.h>
  12. #include <linux/init.h>
  13. #include <linux/kernel.h>
  14. #include <linux/leds.h>
  15. #include <linux/module.h>
  16. #include <linux/pci_ids.h>
  17. #include <linux/types.h>
  18. struct system76_data {
  19. struct acpi_device *acpi_dev;
  20. struct led_classdev ap_led;
  21. struct led_classdev kb_led;
  22. enum led_brightness kb_brightness;
  23. enum led_brightness kb_toggle_brightness;
  24. int kb_color;
  25. };
  26. static const struct acpi_device_id device_ids[] = {
  27. {"17761776", 0},
  28. {"", 0},
  29. };
  30. MODULE_DEVICE_TABLE(acpi, device_ids);
  31. // Array of keyboard LED brightness levels
  32. static const enum led_brightness kb_levels[] = {
  33. 48,
  34. 72,
  35. 96,
  36. 144,
  37. 192,
  38. 255
  39. };
  40. // Array of keyboard LED colors in 24-bit RGB format
  41. static const int kb_colors[] = {
  42. 0xFFFFFF,
  43. 0x0000FF,
  44. 0xFF0000,
  45. 0xFF00FF,
  46. 0x00FF00,
  47. 0x00FFFF,
  48. 0xFFFF00
  49. };
  50. // Get a System76 ACPI device value by name
  51. static int system76_get(struct system76_data *data, char *method)
  52. {
  53. acpi_handle handle;
  54. acpi_status status;
  55. unsigned long long ret = 0;
  56. handle = acpi_device_handle(data->acpi_dev);
  57. status = acpi_evaluate_integer(handle, method, NULL, &ret);
  58. if (ACPI_SUCCESS(status))
  59. return (int)ret;
  60. else
  61. return -1;
  62. }
  63. // Set a System76 ACPI device value by name
  64. static int system76_set(struct system76_data *data, char *method, int value)
  65. {
  66. union acpi_object obj;
  67. struct acpi_object_list obj_list;
  68. acpi_handle handle;
  69. acpi_status status;
  70. obj.type = ACPI_TYPE_INTEGER;
  71. obj.integer.value = value;
  72. obj_list.count = 1;
  73. obj_list.pointer = &obj;
  74. handle = acpi_device_handle(data->acpi_dev);
  75. status = acpi_evaluate_object(handle, method, &obj_list, NULL);
  76. if (ACPI_SUCCESS(status))
  77. return 0;
  78. else
  79. return -1;
  80. }
  81. // Get the airplane mode LED brightness
  82. static enum led_brightness ap_led_get(struct led_classdev *led)
  83. {
  84. struct system76_data *data;
  85. int value;
  86. data = container_of(led, struct system76_data, ap_led);
  87. value = system76_get(data, "GAPL");
  88. if (value > 0)
  89. return (enum led_brightness)value;
  90. else
  91. return LED_OFF;
  92. }
  93. // Set the airplane mode LED brightness
  94. static int ap_led_set(struct led_classdev *led, enum led_brightness value)
  95. {
  96. struct system76_data *data;
  97. data = container_of(led, struct system76_data, ap_led);
  98. return system76_set(data, "SAPL", value == LED_OFF ? 0 : 1);
  99. }
  100. // Get the last set keyboard LED brightness
  101. static enum led_brightness kb_led_get(struct led_classdev *led)
  102. {
  103. struct system76_data *data;
  104. data = container_of(led, struct system76_data, kb_led);
  105. return data->kb_brightness;
  106. }
  107. // Set the keyboard LED brightness
  108. static int kb_led_set(struct led_classdev *led, enum led_brightness value)
  109. {
  110. struct system76_data *data;
  111. data = container_of(led, struct system76_data, kb_led);
  112. data->kb_brightness = value;
  113. return system76_set(data, "SKBL", (int)data->kb_brightness);
  114. }
  115. // Get the last set keyboard LED color
  116. static ssize_t kb_led_color_show(
  117. struct device *dev,
  118. struct device_attribute *dev_attr,
  119. char *buf)
  120. {
  121. struct led_classdev *led;
  122. struct system76_data *data;
  123. led = (struct led_classdev *)dev->driver_data;
  124. data = container_of(led, struct system76_data, kb_led);
  125. return sprintf(buf, "%06X\n", data->kb_color);
  126. }
  127. // Set the keyboard LED color
  128. static ssize_t kb_led_color_store(
  129. struct device *dev,
  130. struct device_attribute *dev_attr,
  131. const char *buf,
  132. size_t size)
  133. {
  134. struct led_classdev *led;
  135. struct system76_data *data;
  136. unsigned int val;
  137. int ret;
  138. led = (struct led_classdev *)dev->driver_data;
  139. data = container_of(led, struct system76_data, kb_led);
  140. ret = kstrtouint(buf, 16, &val);
  141. if (ret)
  142. return ret;
  143. if (val > 0xFFFFFF)
  144. return -EINVAL;
  145. data->kb_color = (int)val;
  146. system76_set(data, "SKBC", data->kb_color);
  147. return size;
  148. }
  149. static const struct device_attribute kb_led_color_dev_attr = {
  150. .attr = {
  151. .name = "color",
  152. .mode = 0644,
  153. },
  154. .show = kb_led_color_show,
  155. .store = kb_led_color_store,
  156. };
  157. // Notify that the keyboard LED was changed by hardware
  158. static void kb_led_notify(struct system76_data *data)
  159. {
  160. led_classdev_notify_brightness_hw_changed(
  161. &data->kb_led,
  162. data->kb_brightness
  163. );
  164. }
  165. // Read keyboard LED brightness as set by hardware
  166. static void kb_led_hotkey_hardware(struct system76_data *data)
  167. {
  168. int value;
  169. value = system76_get(data, "GKBL");
  170. if (value < 0)
  171. return;
  172. data->kb_brightness = value;
  173. kb_led_notify(data);
  174. }
  175. // Toggle the keyboard LED
  176. static void kb_led_hotkey_toggle(struct system76_data *data)
  177. {
  178. if (data->kb_brightness > 0) {
  179. data->kb_toggle_brightness = data->kb_brightness;
  180. kb_led_set(&data->kb_led, 0);
  181. } else {
  182. kb_led_set(&data->kb_led, data->kb_toggle_brightness);
  183. }
  184. kb_led_notify(data);
  185. }
  186. // Decrease the keyboard LED brightness
  187. static void kb_led_hotkey_down(struct system76_data *data)
  188. {
  189. int i;
  190. if (data->kb_brightness > 0) {
  191. for (i = ARRAY_SIZE(kb_levels); i > 0; i--) {
  192. if (kb_levels[i - 1] < data->kb_brightness) {
  193. kb_led_set(&data->kb_led, kb_levels[i - 1]);
  194. break;
  195. }
  196. }
  197. } else {
  198. kb_led_set(&data->kb_led, data->kb_toggle_brightness);
  199. }
  200. kb_led_notify(data);
  201. }
  202. // Increase the keyboard LED brightness
  203. static void kb_led_hotkey_up(struct system76_data *data)
  204. {
  205. int i;
  206. if (data->kb_brightness > 0) {
  207. for (i = 0; i < ARRAY_SIZE(kb_levels); i++) {
  208. if (kb_levels[i] > data->kb_brightness) {
  209. kb_led_set(&data->kb_led, kb_levels[i]);
  210. break;
  211. }
  212. }
  213. } else {
  214. kb_led_set(&data->kb_led, data->kb_toggle_brightness);
  215. }
  216. kb_led_notify(data);
  217. }
  218. // Cycle the keyboard LED color
  219. static void kb_led_hotkey_color(struct system76_data *data)
  220. {
  221. int i;
  222. if (data->kb_color < 0)
  223. return;
  224. if (data->kb_brightness > 0) {
  225. for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
  226. if (kb_colors[i] == data->kb_color)
  227. break;
  228. }
  229. i += 1;
  230. if (i >= ARRAY_SIZE(kb_colors))
  231. i = 0;
  232. data->kb_color = kb_colors[i];
  233. system76_set(data, "SKBC", data->kb_color);
  234. } else {
  235. kb_led_set(&data->kb_led, data->kb_toggle_brightness);
  236. }
  237. kb_led_notify(data);
  238. }
  239. // Handle ACPI notification
  240. static void system76_notify(struct acpi_device *acpi_dev, u32 event)
  241. {
  242. struct system76_data *data;
  243. data = acpi_driver_data(acpi_dev);
  244. switch (event) {
  245. case 0x80:
  246. kb_led_hotkey_hardware(data);
  247. break;
  248. case 0x81:
  249. kb_led_hotkey_toggle(data);
  250. break;
  251. case 0x82:
  252. kb_led_hotkey_down(data);
  253. break;
  254. case 0x83:
  255. kb_led_hotkey_up(data);
  256. break;
  257. case 0x84:
  258. kb_led_hotkey_color(data);
  259. break;
  260. }
  261. }
  262. // Add a System76 ACPI device
  263. static int system76_add(struct acpi_device *acpi_dev)
  264. {
  265. struct system76_data *data;
  266. int err;
  267. data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL);
  268. if (!data)
  269. return -ENOMEM;
  270. acpi_dev->driver_data = data;
  271. data->acpi_dev = acpi_dev;
  272. err = system76_get(data, "INIT");
  273. if (err)
  274. return err;
  275. data->ap_led.name = "system76_acpi::airplane";
  276. data->ap_led.flags = LED_CORE_SUSPENDRESUME;
  277. data->ap_led.brightness_get = ap_led_get;
  278. data->ap_led.brightness_set_blocking = ap_led_set;
  279. data->ap_led.max_brightness = 1;
  280. data->ap_led.default_trigger = "rfkill-none";
  281. err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led);
  282. if (err)
  283. return err;
  284. data->kb_led.name = "system76_acpi::kbd_backlight";
  285. data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME;
  286. data->kb_led.brightness_get = kb_led_get;
  287. data->kb_led.brightness_set_blocking = kb_led_set;
  288. if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
  289. data->kb_led.max_brightness = 255;
  290. data->kb_toggle_brightness = 72;
  291. data->kb_color = 0xffffff;
  292. system76_set(data, "SKBC", data->kb_color);
  293. } else {
  294. data->kb_led.max_brightness = 5;
  295. data->kb_color = -1;
  296. }
  297. err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
  298. if (err)
  299. return err;
  300. if (data->kb_color >= 0) {
  301. err = device_create_file(
  302. data->kb_led.dev,
  303. &kb_led_color_dev_attr
  304. );
  305. if (err)
  306. return err;
  307. }
  308. return 0;
  309. }
  310. // Remove a System76 ACPI device
  311. static int system76_remove(struct acpi_device *acpi_dev)
  312. {
  313. struct system76_data *data;
  314. data = acpi_driver_data(acpi_dev);
  315. if (data->kb_color >= 0)
  316. device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr);
  317. devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led);
  318. devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led);
  319. system76_get(data, "FINI");
  320. return 0;
  321. }
  322. static struct acpi_driver system76_driver = {
  323. .name = "System76 ACPI Driver",
  324. .class = "hotkey",
  325. .ids = device_ids,
  326. .ops = {
  327. .add = system76_add,
  328. .remove = system76_remove,
  329. .notify = system76_notify,
  330. },
  331. };
  332. module_acpi_driver(system76_driver);
  333. MODULE_DESCRIPTION("System76 ACPI Driver");
  334. MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>");
  335. MODULE_LICENSE("GPL");