loongson-laptop.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
  4. *
  5. * Jianmin Lv <[email protected]>
  6. * Huacai Chen <[email protected]>
  7. *
  8. * Copyright (C) 2022 Loongson Technology Corporation Limited
  9. */
  10. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11. #include <linux/init.h>
  12. #include <linux/kernel.h>
  13. #include <linux/module.h>
  14. #include <linux/acpi.h>
  15. #include <linux/backlight.h>
  16. #include <linux/device.h>
  17. #include <linux/input.h>
  18. #include <linux/input/sparse-keymap.h>
  19. #include <linux/platform_device.h>
  20. #include <linux/string.h>
  21. #include <linux/types.h>
  22. #include <acpi/video.h>
  23. /* 1. Driver-wide structs and misc. variables */
  24. /* ACPI HIDs */
  25. #define LOONGSON_ACPI_EC_HID "PNP0C09"
  26. #define LOONGSON_ACPI_HKEY_HID "LOON0000"
  27. #define ACPI_LAPTOP_NAME "loongson-laptop"
  28. #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
  29. #define MAX_ACPI_ARGS 3
  30. #define GENERIC_HOTKEY_MAP_MAX 64
  31. #define GENERIC_EVENT_TYPE_OFF 12
  32. #define GENERIC_EVENT_TYPE_MASK 0xF000
  33. #define GENERIC_EVENT_CODE_MASK 0x0FFF
  34. struct generic_sub_driver {
  35. u32 type;
  36. char *name;
  37. acpi_handle *handle;
  38. struct acpi_device *device;
  39. struct platform_driver *driver;
  40. int (*init)(struct generic_sub_driver *sub_driver);
  41. void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
  42. u8 acpi_notify_installed;
  43. };
  44. static u32 input_device_registered;
  45. static struct input_dev *generic_inputdev;
  46. static acpi_handle hotkey_handle;
  47. static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
  48. int loongson_laptop_turn_on_backlight(void);
  49. int loongson_laptop_turn_off_backlight(void);
  50. static int loongson_laptop_backlight_update(struct backlight_device *bd);
  51. /* 2. ACPI Helpers and device model */
  52. static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
  53. {
  54. char res_type;
  55. char *fmt0 = fmt;
  56. va_list ap;
  57. int success, quiet;
  58. acpi_status status;
  59. struct acpi_object_list params;
  60. struct acpi_buffer result, *resultp;
  61. union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
  62. if (!*fmt) {
  63. pr_err("acpi_evalf() called with empty format\n");
  64. return 0;
  65. }
  66. if (*fmt == 'q') {
  67. quiet = 1;
  68. fmt++;
  69. } else
  70. quiet = 0;
  71. res_type = *(fmt++);
  72. params.count = 0;
  73. params.pointer = &in_objs[0];
  74. va_start(ap, fmt);
  75. while (*fmt) {
  76. char c = *(fmt++);
  77. switch (c) {
  78. case 'd': /* int */
  79. in_objs[params.count].integer.value = va_arg(ap, int);
  80. in_objs[params.count++].type = ACPI_TYPE_INTEGER;
  81. break;
  82. /* add more types as needed */
  83. default:
  84. pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
  85. va_end(ap);
  86. return 0;
  87. }
  88. }
  89. va_end(ap);
  90. if (res_type != 'v') {
  91. result.length = sizeof(out_obj);
  92. result.pointer = &out_obj;
  93. resultp = &result;
  94. } else
  95. resultp = NULL;
  96. status = acpi_evaluate_object(handle, method, &params, resultp);
  97. switch (res_type) {
  98. case 'd': /* int */
  99. success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
  100. if (success && res)
  101. *res = out_obj.integer.value;
  102. break;
  103. case 'v': /* void */
  104. success = status == AE_OK;
  105. break;
  106. /* add more types as needed */
  107. default:
  108. pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
  109. return 0;
  110. }
  111. if (!success && !quiet)
  112. pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
  113. method, fmt0, acpi_format_exception(status));
  114. return success;
  115. }
  116. static int hotkey_status_get(int *status)
  117. {
  118. if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
  119. return -EIO;
  120. return 0;
  121. }
  122. static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
  123. {
  124. struct generic_sub_driver *sub_driver = data;
  125. if (!sub_driver || !sub_driver->notify)
  126. return;
  127. sub_driver->notify(sub_driver, event);
  128. }
  129. static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
  130. {
  131. acpi_status status;
  132. if (!*sub_driver->handle)
  133. return 0;
  134. sub_driver->device = acpi_fetch_acpi_dev(*sub_driver->handle);
  135. if (!sub_driver->device) {
  136. pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
  137. return -ENODEV;
  138. }
  139. sub_driver->device->driver_data = sub_driver;
  140. sprintf(acpi_device_class(sub_driver->device), "%s/%s",
  141. ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
  142. status = acpi_install_notify_handler(*sub_driver->handle,
  143. sub_driver->type, dispatch_acpi_notify, sub_driver);
  144. if (ACPI_FAILURE(status)) {
  145. if (status == AE_ALREADY_EXISTS) {
  146. pr_notice("Another device driver is already "
  147. "handling %s events\n", sub_driver->name);
  148. } else {
  149. pr_err("acpi_install_notify_handler(%s) failed: %s\n",
  150. sub_driver->name, acpi_format_exception(status));
  151. }
  152. return -ENODEV;
  153. }
  154. sub_driver->acpi_notify_installed = 1;
  155. return 0;
  156. }
  157. static int loongson_hotkey_suspend(struct device *dev)
  158. {
  159. return 0;
  160. }
  161. static int loongson_hotkey_resume(struct device *dev)
  162. {
  163. int status = 0;
  164. struct key_entry ke;
  165. struct backlight_device *bd;
  166. bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
  167. if (bd) {
  168. loongson_laptop_backlight_update(bd) ?
  169. pr_warn("Loongson_backlight: resume brightness failed") :
  170. pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
  171. }
  172. /*
  173. * Only if the firmware supports SW_LID event model, we can handle the
  174. * event. This is for the consideration of development board without EC.
  175. */
  176. if (test_bit(SW_LID, generic_inputdev->swbit)) {
  177. if (hotkey_status_get(&status) < 0)
  178. return -EIO;
  179. /*
  180. * The input device sw element records the last lid status.
  181. * When the system is awakened by other wake-up sources,
  182. * the lid event will also be reported. The judgment of
  183. * adding SW_LID bit which in sw element can avoid this
  184. * case.
  185. *
  186. * Input system will drop lid event when current lid event
  187. * value and last lid status in the same. So laptop driver
  188. * doesn't report repeated events.
  189. *
  190. * Lid status is generally 0, but hardware exception is
  191. * considered. So add lid status confirmation.
  192. */
  193. if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
  194. ke.type = KE_SW;
  195. ke.sw.value = (u8)status;
  196. ke.sw.code = SW_LID;
  197. sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
  198. }
  199. }
  200. return 0;
  201. }
  202. static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
  203. loongson_hotkey_suspend, loongson_hotkey_resume);
  204. static int loongson_hotkey_probe(struct platform_device *pdev)
  205. {
  206. hotkey_handle = ACPI_HANDLE(&pdev->dev);
  207. if (!hotkey_handle)
  208. return -ENODEV;
  209. return 0;
  210. }
  211. static const struct acpi_device_id loongson_device_ids[] = {
  212. {LOONGSON_ACPI_HKEY_HID, 0},
  213. {"", 0},
  214. };
  215. MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
  216. static struct platform_driver loongson_hotkey_driver = {
  217. .probe = loongson_hotkey_probe,
  218. .driver = {
  219. .name = "loongson-hotkey",
  220. .owner = THIS_MODULE,
  221. .pm = pm_ptr(&loongson_hotkey_pm),
  222. .acpi_match_table = loongson_device_ids,
  223. },
  224. };
  225. static int hotkey_map(void)
  226. {
  227. u32 index;
  228. acpi_status status;
  229. struct acpi_buffer buf;
  230. union acpi_object *pack;
  231. buf.length = ACPI_ALLOCATE_BUFFER;
  232. status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
  233. if (status != AE_OK) {
  234. pr_err("ACPI exception: %s\n", acpi_format_exception(status));
  235. return -1;
  236. }
  237. pack = buf.pointer;
  238. for (index = 0; index < pack->package.count; index++) {
  239. union acpi_object *element, *sub_pack;
  240. sub_pack = &pack->package.elements[index];
  241. element = &sub_pack->package.elements[0];
  242. hotkey_keycode_map[index].type = element->integer.value;
  243. element = &sub_pack->package.elements[1];
  244. hotkey_keycode_map[index].code = element->integer.value;
  245. element = &sub_pack->package.elements[2];
  246. hotkey_keycode_map[index].keycode = element->integer.value;
  247. }
  248. return 0;
  249. }
  250. static int hotkey_backlight_set(bool enable)
  251. {
  252. if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
  253. return -EIO;
  254. return 0;
  255. }
  256. static int ec_get_brightness(void)
  257. {
  258. int status = 0;
  259. if (!hotkey_handle)
  260. return -ENXIO;
  261. if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
  262. return -EIO;
  263. return status;
  264. }
  265. static int ec_set_brightness(int level)
  266. {
  267. int ret = 0;
  268. if (!hotkey_handle)
  269. return -ENXIO;
  270. if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
  271. ret = -EIO;
  272. return ret;
  273. }
  274. static int ec_backlight_level(u8 level)
  275. {
  276. int status = 0;
  277. if (!hotkey_handle)
  278. return -ENXIO;
  279. if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
  280. return -EIO;
  281. if ((status < 0) || (level > status))
  282. return status;
  283. if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
  284. return -EIO;
  285. if ((status < 0) || (level < status))
  286. return status;
  287. return level;
  288. }
  289. static int loongson_laptop_backlight_update(struct backlight_device *bd)
  290. {
  291. int lvl = ec_backlight_level(bd->props.brightness);
  292. if (lvl < 0)
  293. return -EIO;
  294. if (ec_set_brightness(lvl))
  295. return -EIO;
  296. return 0;
  297. }
  298. static int loongson_laptop_get_brightness(struct backlight_device *bd)
  299. {
  300. int level;
  301. level = ec_get_brightness();
  302. if (level < 0)
  303. return -EIO;
  304. return level;
  305. }
  306. static const struct backlight_ops backlight_laptop_ops = {
  307. .update_status = loongson_laptop_backlight_update,
  308. .get_brightness = loongson_laptop_get_brightness,
  309. };
  310. static int laptop_backlight_register(void)
  311. {
  312. int status = 0;
  313. struct backlight_properties props;
  314. memset(&props, 0, sizeof(props));
  315. if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
  316. return -EIO;
  317. props.brightness = 1;
  318. props.max_brightness = status;
  319. props.type = BACKLIGHT_PLATFORM;
  320. backlight_device_register("loongson_laptop",
  321. NULL, NULL, &backlight_laptop_ops, &props);
  322. return 0;
  323. }
  324. int loongson_laptop_turn_on_backlight(void)
  325. {
  326. int status;
  327. union acpi_object arg0 = { ACPI_TYPE_INTEGER };
  328. struct acpi_object_list args = { 1, &arg0 };
  329. arg0.integer.value = 1;
  330. status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
  331. if (ACPI_FAILURE(status)) {
  332. pr_info("Loongson lvds error: 0x%x\n", status);
  333. return -ENODEV;
  334. }
  335. return 0;
  336. }
  337. int loongson_laptop_turn_off_backlight(void)
  338. {
  339. int status;
  340. union acpi_object arg0 = { ACPI_TYPE_INTEGER };
  341. struct acpi_object_list args = { 1, &arg0 };
  342. arg0.integer.value = 0;
  343. status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
  344. if (ACPI_FAILURE(status)) {
  345. pr_info("Loongson lvds error: 0x%x\n", status);
  346. return -ENODEV;
  347. }
  348. return 0;
  349. }
  350. static int __init event_init(struct generic_sub_driver *sub_driver)
  351. {
  352. int ret;
  353. ret = hotkey_map();
  354. if (ret < 0) {
  355. pr_err("Failed to parse keymap from DSDT\n");
  356. return ret;
  357. }
  358. ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
  359. if (ret < 0) {
  360. pr_err("Failed to setup input device keymap\n");
  361. input_free_device(generic_inputdev);
  362. generic_inputdev = NULL;
  363. return ret;
  364. }
  365. /*
  366. * This hotkey driver handle backlight event when
  367. * acpi_video_get_backlight_type() gets acpi_backlight_vendor
  368. */
  369. if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
  370. hotkey_backlight_set(true);
  371. else
  372. hotkey_backlight_set(false);
  373. pr_info("ACPI: enabling firmware HKEY event interface...\n");
  374. return ret;
  375. }
  376. static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
  377. {
  378. int type, scan_code;
  379. struct key_entry *ke = NULL;
  380. scan_code = event & GENERIC_EVENT_CODE_MASK;
  381. type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
  382. ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
  383. if (ke) {
  384. if (type == KE_SW) {
  385. int status = 0;
  386. if (hotkey_status_get(&status) < 0)
  387. return;
  388. ke->sw.value = !!(status & (1 << ke->sw.code));
  389. }
  390. sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
  391. }
  392. }
  393. /* 3. Infrastructure */
  394. static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
  395. static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
  396. {
  397. int ret;
  398. if (!sub_driver || !sub_driver->driver)
  399. return -EINVAL;
  400. ret = platform_driver_register(sub_driver->driver);
  401. if (ret)
  402. return -EINVAL;
  403. if (sub_driver->init) {
  404. ret = sub_driver->init(sub_driver);
  405. if (ret)
  406. goto err_out;
  407. }
  408. if (sub_driver->notify) {
  409. ret = setup_acpi_notify(sub_driver);
  410. if (ret == -ENODEV) {
  411. ret = 0;
  412. goto err_out;
  413. }
  414. if (ret < 0)
  415. goto err_out;
  416. }
  417. return 0;
  418. err_out:
  419. generic_subdriver_exit(sub_driver);
  420. return ret;
  421. }
  422. static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
  423. {
  424. if (sub_driver->acpi_notify_installed) {
  425. acpi_remove_notify_handler(*sub_driver->handle,
  426. sub_driver->type, dispatch_acpi_notify);
  427. sub_driver->acpi_notify_installed = 0;
  428. }
  429. platform_driver_unregister(sub_driver->driver);
  430. }
  431. static struct generic_sub_driver generic_sub_drivers[] __refdata = {
  432. {
  433. .name = "hotkey",
  434. .init = event_init,
  435. .notify = event_notify,
  436. .handle = &hotkey_handle,
  437. .type = ACPI_DEVICE_NOTIFY,
  438. .driver = &loongson_hotkey_driver,
  439. },
  440. };
  441. static int __init generic_acpi_laptop_init(void)
  442. {
  443. bool ec_found;
  444. int i, ret, status;
  445. if (acpi_disabled)
  446. return -ENODEV;
  447. /* The EC device is required */
  448. ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
  449. if (!ec_found)
  450. return -ENODEV;
  451. /* Enable SCI for EC */
  452. acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
  453. generic_inputdev = input_allocate_device();
  454. if (!generic_inputdev) {
  455. pr_err("Unable to allocate input device\n");
  456. return -ENOMEM;
  457. }
  458. /* Prepare input device, but don't register */
  459. generic_inputdev->name =
  460. "Loongson Generic Laptop/All-in-One Extra Buttons";
  461. generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
  462. generic_inputdev->id.bustype = BUS_HOST;
  463. generic_inputdev->dev.parent = NULL;
  464. /* Init subdrivers */
  465. for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
  466. ret = generic_subdriver_init(&generic_sub_drivers[i]);
  467. if (ret < 0) {
  468. input_free_device(generic_inputdev);
  469. while (--i >= 0)
  470. generic_subdriver_exit(&generic_sub_drivers[i]);
  471. return ret;
  472. }
  473. }
  474. ret = input_register_device(generic_inputdev);
  475. if (ret < 0) {
  476. input_free_device(generic_inputdev);
  477. while (--i >= 0)
  478. generic_subdriver_exit(&generic_sub_drivers[i]);
  479. pr_err("Unable to register input device\n");
  480. return ret;
  481. }
  482. input_device_registered = 1;
  483. if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
  484. pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
  485. ret = laptop_backlight_register();
  486. if (ret < 0)
  487. pr_err("Loongson Laptop: laptop-backlight device register failed\n");
  488. }
  489. return 0;
  490. }
  491. static void __exit generic_acpi_laptop_exit(void)
  492. {
  493. if (generic_inputdev) {
  494. if (input_device_registered)
  495. input_unregister_device(generic_inputdev);
  496. else
  497. input_free_device(generic_inputdev);
  498. }
  499. }
  500. module_init(generic_acpi_laptop_init);
  501. module_exit(generic_acpi_laptop_exit);
  502. MODULE_AUTHOR("Jianmin Lv <[email protected]>");
  503. MODULE_AUTHOR("Huacai Chen <[email protected]>");
  504. MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
  505. MODULE_LICENSE("GPL");