1. /*
  2. * tuxedo-wmi.c
  3. *
  4. * Copyright (C) 2013-2014 Christoph Jaeger <[email protected]>
  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 (at
  9. * your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #define TUXEDO_DRIVER_NAME KBUILD_MODNAME
  20. #define pr_fmt(fmt) TUXEDO_DRIVER_NAME ": " fmt
  21. #include "/usr/src/sys/contrib/dev/acpica/include/acpi.h"
  22. #include <linux/acpi.h>
  23. #include <linux/delay.h>
  24. #include <linux/dmi.h>
  25. #include <linux/input.h>
  26. #include <linux/kernel.h>
  27. #include <linux/kthread.h>
  28. #include <linux/leds.h>
  29. #include <linux/module.h>
  30. #include <linux/mutex.h>
  31. #include <linux/platform_device.h>
  32. #include <linux/rfkill.h>
  33. #include <linux/stringify.h>
  34. #include <linux/version.h>
  35. #include <linux/workqueue.h>
  36. #define __TUXEDO_PR(lvl, fmt, ...) do { pr_##lvl(fmt, ##__VA_ARGS__); } while (0)
  37. #define TUXEDO_INFO(fmt, ...) __TUXEDO_PR(info, fmt, ##__VA_ARGS__)
  38. #define TUXEDO_ERROR(fmt, ...) __TUXEDO_PR(err, fmt, ##__VA_ARGS__)
  39. #define TUXEDO_DEBUG(fmt, ...) __TUXEDO_PR(debug, "[%s:%u] " fmt, __func__, __LINE__, ##__VA_ARGS__)
  40. #define CLEVO_EVENT_GUID "ABBC0F6B-8EA1-11D1-00A0-C90629100000"
  41. #define CLEVO_EMAIL_GUID "ABBC0F6C-8EA1-11D1-00A0-C90629100000"
  42. #define CLEVO_GET_GUID "ABBC0F6D-8EA1-11D1-00A0-C90629100000"
  43. /* method IDs for CLEVO_GET */
  44. #define GET_EVENT 0x01 /* 1 */
  45. #define GET_POWER_STATE_FOR_3G 0x0A /* 10 */
  46. #define GET_AP 0x46 /* 70 */
  47. #define SET_3G 0x4C /* 76 */
  48. #define SET_KB_LED 0x67 /* 103 */
  49. #define AIRPLANE_BUTTON 0x6D /* 109 */ /* or 0x6C (?) */
  50. #define TALK_BIOS_3G 0x78 /* 120 */
  51. #define COLORS { C(black, 0x000000), C(blue, 0x0000FF), \
  52. C(red, 0xFF0000), C(magenta, 0xFF00FF), \
  53. C(green, 0x00FF00), C(cyan, 0x00FFFF), \
  54. C(yellow, 0xFFFF00), C(white, 0xFFFFFF), }
  55. #undef C
  56. #define C(n, v) KB_COLOR_##n
  57. enum kb_color COLORS;
  58. #undef C
  59. union kb_rgb_color {
  60. u32 rgb;
  61. struct { u32 b:8, g:8, r:8, :8; };
  62. };
  63. #define C(n, v) { .name = #n, .value = { .rgb = v, }, }
  64. struct {
  65. const char *const name;
  66. union kb_rgb_color value;
  67. } kb_colors[] = COLORS;
  68. #undef C
  69. #define KB_COLOR_DEFAULT KB_COLOR_blue
  70. #define KB_BRIGHTNESS_MAX 10
  71. #define KB_BRIGHTNESS_DEFAULT KB_BRIGHTNESS_MAX
  72. static int param_set_kb_color(const char *val, const struct kernel_param *kp)
  73. {
  74. size_t i;
  75. if (!val)
  76. return -EINVAL;
  77. if (!val[0]) {
  78. *((enum kb_color *) kp->arg) = KB_COLOR_black;
  79. return 0;
  80. }
  81. for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
  82. if (!strcmp(val, kb_colors[i].name)) {
  83. *((enum kb_color *) kp->arg) = i;
  84. return 0;
  85. }
  86. }
  87. return -EINVAL;
  88. }
  89. static int param_get_kb_color(char *buffer, const struct kernel_param *kp)
  90. {
  91. return sprintf(buffer, "%s", kb_colors[*((enum kb_color *) kp->arg)].name);
  92. }
  93. static const struct kernel_param_ops param_ops_kb_color = {
  94. .set = param_set_kb_color,
  95. .get = param_get_kb_color,
  96. };
  97. static enum kb_color param_kb_color[] = { [0 ... 2] = KB_COLOR_DEFAULT };
  98. static int param_kb_color_num;
  99. #define param_check_kb_color(name, p) __param_check(name, p, enum kb_color)
  100. module_param_array_named(kb_color, param_kb_color, kb_color,
  101. &param_kb_color_num, S_IRUSR);
  102. MODULE_PARM_DESC(kb_color, "Set the color(s) of the keyboard (sections)");
  103. static int param_set_kb_brightness(const char *val, const struct kernel_param *kp)
  104. {
  105. int ret;
  106. ret = param_set_byte(val, kp);
  107. if (!ret && *((unsigned char *) kp->arg) > KB_BRIGHTNESS_MAX)
  108. return -EINVAL;
  109. return ret;
  110. }
  111. #if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
  112. static int param_get_kb_brightness(char *buffer, const struct kernel_param *kp)
  113. {
  114. /* due to a bug in the kernel, we do this ourselves */
  115. return sprintf(buffer, "%hhu", *((unsigned char *) kp->arg));
  116. }
  117. #endif
  118. static const struct kernel_param_ops param_ops_kb_brightness = {
  119. .set = param_set_kb_brightness,
  120. #if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
  121. .get = param_get_kb_brightness,
  122. #else
  123. .get = param_get_byte,
  124. #endif
  125. };
  126. static unsigned char param_kb_brightness = KB_BRIGHTNESS_DEFAULT;
  127. #define param_check_kb_brightness param_check_byte
  128. module_param_named(kb_brightness, param_kb_brightness, kb_brightness, S_IRUSR);
  129. MODULE_PARM_DESC(kb_brightness, "Set the brightness of the keyboard backlight");
  130. static bool param_kb_off = false;
  131. module_param_named(kb_off, param_kb_off, bool, S_IRUSR);
  132. MODULE_PARM_DESC(kb_off, "Switch keyboard backlight off");
  133. #define POLL_FREQ_MIN 1
  134. #define POLL_FREQ_MAX 20
  135. #define POLL_FREQ_DEFAULT 5
  136. static int param_set_poll_freq(const char *val, const struct kernel_param *kp)
  137. {
  138. int ret;
  139. ret = param_set_byte(val, kp);
  140. if (!ret)
  141. *((unsigned char *) kp->arg) = clamp_t(unsigned char, *((unsigned char *) kp->arg),
  142. POLL_FREQ_MIN, POLL_FREQ_MAX);
  143. return ret;
  144. }
  145. #if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
  146. static int param_get_poll_freq(char *buffer, const struct kernel_param *kp)
  147. {
  148. /* due to a bug in the kernel, we do this ourselves */
  149. return sprintf(buffer, "%hhu", *((unsigned char *) kp->arg));
  150. }
  151. #endif
  152. static const struct kernel_param_ops param_ops_poll_freq = {
  153. .set = param_set_poll_freq,
  154. #if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
  155. .get = param_get_poll_freq,
  156. #else
  157. .get = param_get_byte,
  158. #endif
  159. };
  160. static unsigned char param_poll_freq = POLL_FREQ_DEFAULT;
  161. #define param_check_poll_freq param_check_byte
  162. module_param_named(poll_freq, param_poll_freq, poll_freq, S_IRUSR);
  163. MODULE_PARM_DESC(poll_freq, "Set polling frequency");
  164. struct platform_device *tuxedo_platform_device;
  165. /* input sub-driver */
  166. static struct input_dev *tuxedo_input_device;
  167. static DEFINE_MUTEX(tuxedo_input_report_mutex);
  168. static unsigned int global_report_cnt = 0;
  169. /* call with tuxedo_input_report_mutex held */
  170. static void tuxedo_input_report_key(unsigned int code)
  171. {
  172. input_report_key(tuxedo_input_device, code, 1);
  173. input_report_key(tuxedo_input_device, code, 0);
  174. input_sync(tuxedo_input_device);
  175. global_report_cnt++;
  176. }
  177. static struct task_struct *tuxedo_input_polling_task;
  178. static int tuxedo_input_polling_thread(void *data)
  179. {
  180. unsigned int report_cnt = 0;
  181. TUXEDO_INFO("Polling thread started (PID: %i), polling at %i Hz\n",
  182. current->pid, param_poll_freq);
  183. while (!kthread_should_stop()) {
  184. u8 byte;
  185. ec_read(0xDB, &byte);
  186. if (byte & 0x40) {
  187. ec_write(0xDB, byte & ~0x40);
  188. TUXEDO_DEBUG("Airplane-Mode Hotkey pressed\n");
  189. mutex_lock(&tuxedo_input_report_mutex);
  190. if (global_report_cnt > report_cnt) {
  191. mutex_unlock(&tuxedo_input_report_mutex);
  192. break;
  193. }
  194. tuxedo_input_report_key(KEY_RFKILL);
  195. report_cnt++;
  196. mutex_unlock(&tuxedo_input_report_mutex);
  197. }
  198. msleep_interruptible(1000 / param_poll_freq);
  199. }
  200. TUXEDO_INFO("Polling thread exiting\n");
  201. return 0;
  202. }
  203. static int tuxedo_input_open(struct input_dev *dev)
  204. {
  205. tuxedo_input_polling_task = kthread_run(tuxedo_input_polling_thread,
  206. NULL, "tuxedo-polld");
  207. if (unlikely(IS_ERR(tuxedo_input_polling_task))) {
  208. tuxedo_input_polling_task = NULL;
  209. TUXEDO_ERROR("Could not create polling thread\n");
  210. return PTR_ERR(tuxedo_input_polling_task);
  211. }
  212. return 0;
  213. }
  214. static void tuxedo_input_close(struct input_dev *dev)
  215. {
  216. if (unlikely(IS_ERR_OR_NULL(tuxedo_input_polling_task)))
  217. return;
  218. kthread_stop(tuxedo_input_polling_task);
  219. tuxedo_input_polling_task = NULL;
  220. }
  221. static int __init tuxedo_input_init(void)
  222. {
  223. int err;
  224. u8 byte;
  225. tuxedo_input_device = input_allocate_device();
  226. if (unlikely(!tuxedo_input_device)) {
  227. TUXEDO_ERROR("Error allocating input device\n");
  228. return -ENOMEM;
  229. }
  230. tuxedo_input_device->name = "Clevo Airplane-Mode Hotkey";
  231. tuxedo_input_device->phys = TUXEDO_DRIVER_NAME "/input0";
  232. tuxedo_input_device->id.bustype = BUS_HOST;
  233. tuxedo_input_device->dev.parent = &tuxedo_platform_device->dev;
  234. tuxedo_input_device->open = tuxedo_input_open;
  235. tuxedo_input_device->close = tuxedo_input_close;
  236. set_bit(EV_KEY, tuxedo_input_device->evbit);
  237. set_bit(KEY_RFKILL, tuxedo_input_device->keybit);
  238. ec_read(0xDB, &byte);
  239. ec_write(0xDB, byte & ~0x40);
  240. err = input_register_device(tuxedo_input_device);
  241. if (unlikely(err)) {
  242. TUXEDO_ERROR("Error registering input device\n");
  243. goto err_free_input_device;
  244. }
  245. return 0;
  246. err_free_input_device:
  247. input_free_device(tuxedo_input_device);
  248. return err;
  249. }
  250. static void __exit tuxedo_input_exit(void)
  251. {
  252. if (unlikely(!tuxedo_input_device))
  253. return;
  254. input_unregister_device(tuxedo_input_device);
  255. tuxedo_input_device = NULL;
  256. }
  257. static int tuxedo_wmi_evaluate_wmbb_method(u32 method_id, u32 arg, u32 *retval)
  258. {
  259. struct acpi_buffer in = { (acpi_size) sizeof(arg), &arg };
  260. struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
  261. union acpi_object *obj;
  262. acpi_status status;
  263. u32 tmp;
  264. TUXEDO_DEBUG("%0#4x IN : %0#6x\n", method_id, arg);
  265. status = wmi_evaluate_method(CLEVO_GET_GUID, 0x01,
  266. method_id, &in, &out);
  267. if (unlikely(ACPI_FAILURE(status)))
  268. goto exit;
  269. obj = (union acpi_object *) out.pointer;
  270. if (obj && obj->type == ACPI_TYPE_INTEGER)
  271. tmp = (u32) obj->integer.value;
  272. else
  273. tmp = 0;
  274. TUXEDO_DEBUG("%0#4x OUT: %0#6x (IN: %0#6x)\n", method_id, tmp, arg);
  275. if (likely(retval))
  276. *retval = tmp;
  277. kfree(obj);
  278. exit:
  279. if (unlikely(ACPI_FAILURE(status)))
  280. return -EIO;
  281. return 0;
  282. }
  283. static struct {
  284. enum kb_state {
  285. KB_STATE_OFF,
  286. KB_STATE_ON,
  287. } state;
  288. struct {
  289. unsigned left;
  290. unsigned center;
  291. unsigned right;
  292. } color;
  293. unsigned brightness;
  294. enum kb_mode {
  295. KB_MODE_RANDOM_COLOR,
  296. KB_MODE_CUSTOM,
  297. KB_MODE_BREATHE,
  298. KB_MODE_CYCLE,
  299. KB_MODE_WAVE,
  300. KB_MODE_DANCE,
  301. KB_MODE_TEMPO,
  302. KB_MODE_FLASH,
  303. } mode;
  304. struct kb_backlight_ops {
  305. void (*set_state)(enum kb_state state);
  306. void (*set_color)(unsigned left, unsigned center, unsigned right);
  307. void (*set_brightness)(unsigned brightness);
  308. void (*set_mode)(enum kb_mode);
  309. void (*init)(void);
  310. } *ops;
  311. } kb_backlight = { .ops = NULL, };
  312. static void kb_dec_brightness(void)
  313. {
  314. if (kb_backlight.state == KB_STATE_OFF || kb_backlight.mode != KB_MODE_CUSTOM)
  315. return;
  316. if (kb_backlight.brightness == 0)
  317. return;
  318. TUXEDO_DEBUG();
  319. kb_backlight.ops->set_brightness(kb_backlight.brightness - 1);
  320. }
  321. static void kb_inc_brightness(void)
  322. {
  323. if (kb_backlight.state == KB_STATE_OFF || kb_backlight.mode != KB_MODE_CUSTOM)
  324. return;
  325. TUXEDO_DEBUG();
  326. kb_backlight.ops->set_brightness(kb_backlight.brightness + 1);
  327. }
  328. static void kb_toggle_state(void)
  329. {
  330. switch (kb_backlight.state) {
  331. case KB_STATE_OFF:
  332. kb_backlight.ops->set_state(KB_STATE_ON);
  333. break;
  334. case KB_STATE_ON:
  335. kb_backlight.ops->set_state(KB_STATE_OFF);
  336. break;
  337. default:
  338. BUG();
  339. }
  340. }
  341. static void kb_next_mode(void)
  342. {
  343. static enum kb_mode modes[] = {
  344. KB_MODE_RANDOM_COLOR,
  345. KB_MODE_DANCE,
  346. KB_MODE_TEMPO,
  347. KB_MODE_FLASH,
  348. KB_MODE_WAVE,
  349. KB_MODE_BREATHE,
  350. KB_MODE_CYCLE,
  351. KB_MODE_CUSTOM,
  352. };
  353. size_t i;
  354. if (kb_backlight.state == KB_STATE_OFF)
  355. return;
  356. for (i = 0; i < ARRAY_SIZE(modes); i++) {
  357. if (modes[i] == kb_backlight.mode)
  358. break;
  359. }
  360. BUG_ON(i == ARRAY_SIZE(modes));
  361. kb_backlight.ops->set_mode(modes[(i + 1) % ARRAY_SIZE(modes)]);
  362. }
  363. /* full color backlight keyboard */
  364. static void kb_full_color__set_color(unsigned left, unsigned center, unsigned right)
  365. {
  366. u32 cmd;
  367. cmd = 0xF0000000;
  368. cmd |= kb_colors[left].value.b << 16;
  369. cmd |= kb_colors[left].value.r << 8;
  370. cmd |= kb_colors[left].value.g << 0;
  371. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
  372. kb_backlight.color.left = left;
  373. cmd = 0xF1000000;
  374. cmd |= kb_colors[center].value.b << 16;
  375. cmd |= kb_colors[center].value.r << 8;
  376. cmd |= kb_colors[center].value.g << 0;
  377. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
  378. kb_backlight.color.center = center;
  379. cmd = 0xF2000000;
  380. cmd |= kb_colors[right].value.b << 16;
  381. cmd |= kb_colors[right].value.r << 8;
  382. cmd |= kb_colors[right].value.g << 0;
  383. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
  384. kb_backlight.color.right = right;
  385. kb_backlight.mode = KB_MODE_CUSTOM;
  386. }
  387. static void kb_full_color__set_brightness(unsigned i)
  388. {
  389. u8 lvl_to_raw[] = { 63, 126, 189, 252 };
  390. i = clamp_t(unsigned, i, 0, ARRAY_SIZE(lvl_to_raw) - 1);
  391. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, 0xF4000000 | lvl_to_raw[i], NULL))
  392. kb_backlight.brightness = i;
  393. }
  394. static void kb_full_color__set_mode(unsigned mode)
  395. {
  396. static u32 cmds[] = {
  397. [KB_MODE_BREATHE] = 0x1002a000,
  398. [KB_MODE_CUSTOM] = 0,
  399. [KB_MODE_CYCLE] = 0x33010000,
  400. [KB_MODE_DANCE] = 0x80000000,
  401. [KB_MODE_FLASH] = 0xA0000000,
  402. [KB_MODE_RANDOM_COLOR] = 0x70000000,
  403. [KB_MODE_TEMPO] = 0x90000000,
  404. [KB_MODE_WAVE] = 0xB0000000,
  405. };
  406. BUG_ON(mode >= ARRAY_SIZE(cmds));
  407. tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, 0x10000000, NULL);
  408. if (mode == KB_MODE_CUSTOM) {
  409. kb_full_color__set_color(kb_backlight.color.left,
  410. kb_backlight.color.center,
  411. kb_backlight.color.right);
  412. kb_full_color__set_brightness(kb_backlight.brightness);
  413. return;
  414. }
  415. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmds[mode], NULL))
  416. kb_backlight.mode = mode;
  417. }
  418. static void kb_full_color__set_state(enum kb_state state)
  419. {
  420. u32 cmd = 0xE0000000;
  421. TUXEDO_DEBUG("State: %d\n", state);
  422. switch (state) {
  423. case KB_STATE_OFF:
  424. cmd |= 0x003001;
  425. break;
  426. case KB_STATE_ON:
  427. cmd |= 0x07F001;
  428. break;
  429. default:
  430. BUG();
  431. }
  432. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
  433. kb_backlight.state = state;
  434. }
  435. static void kb_full_color__init(void)
  436. {
  437. TUXEDO_DEBUG();
  438. kb_full_color__set_state(param_kb_off ? KB_STATE_OFF : KB_STATE_ON);
  439. kb_full_color__set_color(param_kb_color[0], param_kb_color[1], param_kb_color[2]);
  440. kb_full_color__set_brightness(param_kb_brightness);
  441. }
  442. static struct kb_backlight_ops kb_full_color_ops = {
  443. .set_state = kb_full_color__set_state,
  444. .set_color = kb_full_color__set_color,
  445. .set_brightness = kb_full_color__set_brightness,
  446. .set_mode = kb_full_color__set_mode,
  447. .init = kb_full_color__init,
  448. };
  449. /* 8 color backlight keyboard */
  450. static void kb_8_color__set_color(unsigned left, unsigned center, unsigned right)
  451. {
  452. u32 cmd = 0x02010000;
  453. cmd |= kb_backlight.brightness << 12;
  454. cmd |= right << 8;
  455. cmd |= center << 4;
  456. cmd |= left;
  457. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL)) {
  458. kb_backlight.color.left = left;
  459. kb_backlight.color.center = center;
  460. kb_backlight.color.right = right;
  461. }
  462. kb_backlight.mode = KB_MODE_CUSTOM;
  463. }
  464. static void kb_8_color__set_brightness(unsigned i)
  465. {
  466. u32 cmd = 0xD2010000;
  467. i = clamp_t(unsigned, i, 0, KB_BRIGHTNESS_MAX);
  468. cmd |= i << 12;
  469. cmd |= kb_backlight.color.right << 8;
  470. cmd |= kb_backlight.color.center << 4;
  471. cmd |= kb_backlight.color.left;
  472. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
  473. kb_backlight.brightness = i;
  474. }
  475. static void kb_8_color__set_mode(unsigned mode)
  476. {
  477. static u32 cmds[] = {
  478. [KB_MODE_BREATHE] = 0x12010000,
  479. [KB_MODE_CUSTOM] = 0,
  480. [KB_MODE_CYCLE] = 0x32010000,
  481. [KB_MODE_DANCE] = 0x80000000,
  482. [KB_MODE_FLASH] = 0xA0000000,
  483. [KB_MODE_RANDOM_COLOR] = 0x70000000,
  484. [KB_MODE_TEMPO] = 0x90000000,
  485. [KB_MODE_WAVE] = 0xB0000000,
  486. };
  487. BUG_ON(mode >= ARRAY_SIZE(cmds));
  488. tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, 0x20000000, NULL);
  489. if (mode == KB_MODE_CUSTOM){
  490. kb_8_color__set_color(kb_backlight.color.left,
  491. kb_backlight.color.center,
  492. kb_backlight.color.right);
  493. kb_8_color__set_brightness(kb_backlight.brightness);
  494. return;
  495. }
  496. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, cmds[mode], NULL))
  497. kb_backlight.mode = mode;
  498. }
  499. static void kb_8_color__set_state(enum kb_state state)
  500. {
  501. TUXEDO_DEBUG("State: %d\n", state);
  502. switch (state) {
  503. case KB_STATE_OFF:
  504. if (!tuxedo_wmi_evaluate_wmbb_method(SET_KB_LED, 0x22010000, NULL))
  505. kb_backlight.state = state;
  506. break;
  507. case KB_STATE_ON:
  508. kb_8_color__set_mode(kb_backlight.mode);
  509. kb_backlight.state = state;
  510. break;
  511. default:
  512. BUG();
  513. }
  514. }
  515. static void kb_8_color__init(void)
  516. {
  517. TUXEDO_DEBUG();
  518. /* well, that's an uglymoron ... */
  519. kb_8_color__set_state(KB_STATE_OFF);
  520. kb_backlight.color.left = param_kb_color[0];
  521. kb_backlight.color.center = param_kb_color[1];
  522. kb_backlight.color.right = param_kb_color[2];
  523. kb_backlight.brightness = param_kb_brightness;
  524. kb_backlight.mode = KB_MODE_CUSTOM;
  525. if (!param_kb_off) {
  526. kb_8_color__set_color(kb_backlight.color.left,
  527. kb_backlight.color.center,
  528. kb_backlight.color.right);
  529. kb_8_color__set_brightness(kb_backlight.brightness);
  530. kb_8_color__set_state(KB_STATE_ON);
  531. }
  532. }
  533. static struct kb_backlight_ops kb_8_color_ops = {
  534. .set_state = kb_8_color__set_state,
  535. .set_color = kb_8_color__set_color,
  536. .set_brightness = kb_8_color__set_brightness,
  537. .set_mode = kb_8_color__set_mode,
  538. .init = kb_8_color__init,
  539. };
  540. static void tuxedo_wmi_notify(u32 value, void *context)
  541. {
  542. static unsigned int report_cnt = 0;
  543. u32 event;
  544. if (value != 0xD0) {
  545. TUXEDO_INFO("Unexpected WMI event (%0#6x)\n", value);
  546. return;
  547. }
  548. tuxedo_wmi_evaluate_wmbb_method(GET_EVENT, 0, &event);
  549. switch (event) {
  550. case 0xF4:
  551. TUXEDO_DEBUG("Airplane-Mode Hotkey pressed\n");
  552. if (tuxedo_input_polling_task) {
  553. TUXEDO_INFO("Stopping polling thread\n");
  554. kthread_stop(tuxedo_input_polling_task);
  555. tuxedo_input_polling_task = NULL;
  556. }
  557. mutex_lock(&tuxedo_input_report_mutex);
  558. if (global_report_cnt > report_cnt) {
  559. mutex_unlock(&tuxedo_input_report_mutex);
  560. break;
  561. }
  562. tuxedo_input_report_key(KEY_RFKILL);
  563. report_cnt++;
  564. mutex_unlock(&tuxedo_input_report_mutex);
  565. break;
  566. default:
  567. if (!kb_backlight.ops)
  568. break;
  569. switch (event) {
  570. case 0x81:
  571. kb_dec_brightness();
  572. break;
  573. case 0x82:
  574. kb_inc_brightness();
  575. break;
  576. case 0x83:
  577. kb_next_mode();
  578. break;
  579. case 0x9F:
  580. kb_toggle_state();
  581. break;
  582. }
  583. break;
  584. }
  585. }
  586. static int tuxedo_wmi_probe(struct platform_device *dev)
  587. {
  588. int status;
  589. status = wmi_install_notify_handler(CLEVO_EVENT_GUID,
  590. tuxedo_wmi_notify, NULL);
  591. if (unlikely(ACPI_FAILURE(status))) {
  592. TUXEDO_ERROR("Could not register WMI notify handler (%0#6x)\n",
  593. status);
  594. return -EIO;
  595. }
  596. tuxedo_wmi_evaluate_wmbb_method(GET_AP, 0, NULL);
  597. if (kb_backlight.ops)
  598. kb_backlight.ops->init();
  599. return 0;
  600. }
  601. static int tuxedo_wmi_remove(struct platform_device *dev)
  602. {
  603. wmi_remove_notify_handler(CLEVO_EVENT_GUID);
  604. return 0;
  605. }
  606. static int tuxedo_wmi_resume(struct platform_device *dev)
  607. {
  608. tuxedo_wmi_evaluate_wmbb_method(GET_AP, 0, NULL);
  609. if (kb_backlight.ops && kb_backlight.state == KB_STATE_ON)
  610. kb_backlight.ops->set_mode(kb_backlight.mode);
  611. return 0;
  612. }
  613. static struct platform_driver tuxedo_platform_driver = {
  614. .remove = tuxedo_wmi_remove,
  615. .resume = tuxedo_wmi_resume,
  616. .driver = {
  617. .name = TUXEDO_DRIVER_NAME,
  618. .owner = THIS_MODULE,
  619. },
  620. };
  621. /* LED sub-driver */
  622. static bool param_led_invert = false;
  623. module_param_named(led_invert, param_led_invert, bool, 0);
  624. MODULE_PARM_DESC(led_invert, "Invert airplane mode LED state.");
  625. static struct workqueue_struct *led_workqueue;
  626. static struct _led_work {
  627. struct work_struct work;
  628. int wk;
  629. } led_work;
  630. static void airplane_led_update(struct work_struct *work)
  631. {
  632. u8 byte;
  633. struct _led_work *w;
  634. w = container_of(work, struct _led_work, work);
  635. ec_read(0xD9, &byte);
  636. if (param_led_invert)
  637. ec_write(0xD9, w->wk ? byte & ~0x40 : byte | 0x40);
  638. else
  639. ec_write(0xD9, w->wk ? byte | 0x40 : byte & ~0x40);
  640. /* wmbb 0x6C 1 (?) */
  641. }
  642. static enum led_brightness airplane_led_get(struct led_classdev *led_cdev)
  643. {
  644. u8 byte;
  645. ec_read(0xD9, &byte);
  646. if (param_led_invert)
  647. return byte & 0x40 ? LED_OFF : LED_FULL;
  648. else
  649. return byte & 0x40 ? LED_FULL : LED_OFF;
  650. }
  651. /* must not sleep */
  652. static void airplane_led_set(struct led_classdev *led_cdev,
  653. enum led_brightness value)
  654. {
  655. led_work.wk = value;
  656. queue_work(led_workqueue, &led_work.work);
  657. }
  658. static struct led_classdev airplane_led = {
  659. .name = "tuxedo::airplane",
  660. .brightness_get = airplane_led_get,
  661. .brightness_set = airplane_led_set,
  662. .max_brightness = 1,
  663. };
  664. static int __init tuxedo_led_init(void)
  665. {
  666. int err;
  667. led_workqueue = create_singlethread_workqueue("led_workqueue");
  668. if (unlikely(!led_workqueue))
  669. return -ENOMEM;
  670. INIT_WORK(&led_work.work, airplane_led_update);
  671. err = led_classdev_register(&tuxedo_platform_device->dev, &airplane_led);
  672. if (unlikely(err))
  673. goto err_destroy_workqueue;
  674. return 0;
  675. err_destroy_workqueue:
  676. destroy_workqueue(led_workqueue);
  677. led_workqueue = NULL;
  678. return err;
  679. }
  680. static void __exit tuxedo_led_exit(void)
  681. {
  682. if (!IS_ERR_OR_NULL(airplane_led.dev))
  683. led_classdev_unregister(&airplane_led);
  684. if (led_workqueue)
  685. destroy_workqueue(led_workqueue);
  686. }
  687. /* RFKILL sub-driver */
  688. static bool param_rfkill = false;
  689. module_param_named(rfkill, param_rfkill, bool, 0);
  690. MODULE_PARM_DESC(rfkill, "Enable WWAN-RFKILL capability.");
  691. static struct rfkill *tuxedo_wwan_rfkill_device;
  692. static int tuxedo_wwan_rfkill_set_block(void *data, bool blocked)
  693. {
  694. TUXEDO_DEBUG("blocked=%i\n", blocked);
  695. if (tuxedo_wmi_evaluate_wmbb_method(SET_3G, !blocked, NULL))
  696. TUXEDO_ERROR("Setting 3G power state failed!\n");
  697. return 0;
  698. }
  699. static const struct rfkill_ops tuxedo_wwan_rfkill_ops = {
  700. .set_block = tuxedo_wwan_rfkill_set_block,
  701. };
  702. static int __init tuxedo_rfkill_init(void)
  703. {
  704. int err;
  705. u32 unblocked = 0;
  706. if (!param_rfkill)
  707. return 0;
  708. tuxedo_wmi_evaluate_wmbb_method(TALK_BIOS_3G, 1, NULL);
  709. tuxedo_wwan_rfkill_device = rfkill_alloc("tuxedo-wwan",
  710. &tuxedo_platform_device->dev,
  711. RFKILL_TYPE_WWAN,
  712. &tuxedo_wwan_rfkill_ops, NULL);
  713. if (unlikely(!tuxedo_wwan_rfkill_device))
  714. return -ENOMEM;
  715. err = rfkill_register(tuxedo_wwan_rfkill_device);
  716. if (unlikely(err))
  717. goto err_destroy_wwan;
  718. if (tuxedo_wmi_evaluate_wmbb_method(GET_POWER_STATE_FOR_3G, 0, &unblocked))
  719. TUXEDO_ERROR("Could not get 3G power state!\n");
  720. else
  721. rfkill_set_sw_state(tuxedo_wwan_rfkill_device, !unblocked);
  722. return 0;
  723. err_destroy_wwan:
  724. rfkill_destroy(tuxedo_wwan_rfkill_device);
  725. tuxedo_wmi_evaluate_wmbb_method(TALK_BIOS_3G, 0, NULL);
  726. return err;
  727. }
  728. static void __exit tuxedo_rfkill_exit(void)
  729. {
  730. if (!tuxedo_wwan_rfkill_device)
  731. return;
  732. tuxedo_wmi_evaluate_wmbb_method(TALK_BIOS_3G, 0, NULL);
  733. rfkill_unregister(tuxedo_wwan_rfkill_device);
  734. rfkill_destroy(tuxedo_wwan_rfkill_device);
  735. }
  736. static int __init tuxedo_dmi_matched(const struct dmi_system_id *id)
  737. {
  738. TUXEDO_INFO("Model %s found\n", id->ident);
  739. kb_backlight.ops = id->driver_data;
  740. return 1;
  741. }
  742. static struct dmi_system_id __initdata tuxedo_dmi_table[] = {
  743. {
  744. .ident = "Clevo P370SM-A",
  745. .matches = {
  746. DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
  747. DMI_MATCH(DMI_PRODUCT_NAME, "P370SM-A"),
  748. },
  749. .callback = tuxedo_dmi_matched,
  750. .driver_data = &kb_full_color_ops,
  751. },
  752. {
  753. .ident = "Clevo P17xSM-A",
  754. .matches = {
  755. DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
  756. DMI_MATCH(DMI_PRODUCT_NAME, "P17SM-A"),
  757. },
  758. .callback = tuxedo_dmi_matched,
  759. .driver_data = &kb_full_color_ops,
  760. },
  761. {
  762. .ident = "Clevo P15xSM-A/P15xSM1-A",
  763. .matches = {
  764. DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
  765. DMI_MATCH(DMI_PRODUCT_NAME, "P15SM-A/SM1-A"),
  766. },
  767. .callback = tuxedo_dmi_matched,
  768. .driver_data = &kb_full_color_ops,
  769. },
  770. {
  771. .ident = "Clevo P17xSM",
  772. .matches = {
  773. DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
  774. DMI_MATCH(DMI_PRODUCT_NAME, "P17SM"),
  775. },
  776. .callback = tuxedo_dmi_matched,
  777. .driver_data = &kb_8_color_ops,
  778. },
  779. {
  780. .ident = "Clevo P15xSM",
  781. .matches = {
  782. DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
  783. DMI_MATCH(DMI_PRODUCT_NAME, "P15SM"),
  784. },
  785. .callback = tuxedo_dmi_matched,
  786. .driver_data = &kb_8_color_ops,
  787. },
  788. {
  789. /* terminating NULL entry */
  790. },
  791. };
  792. MODULE_DEVICE_TABLE(dmi, tuxedo_dmi_table);
  793. static int __init tuxedo_init(void)
  794. {
  795. int err;
  796. switch (param_kb_color_num) {
  797. case 1:
  798. param_kb_color[1] = param_kb_color[2] = param_kb_color[0];
  799. break;
  800. case 2:
  801. return -EINVAL;
  802. }
  803. dmi_check_system(tuxedo_dmi_table);
  804. if (!wmi_has_guid(CLEVO_EVENT_GUID)) {
  805. TUXEDO_INFO("No known WMI event notification GUID found\n");
  806. return -ENODEV;
  807. }
  808. if (!wmi_has_guid(CLEVO_GET_GUID)) {
  809. TUXEDO_INFO("No known WMI control method GUID found\n");
  810. return -ENODEV;
  811. }
  812. tuxedo_platform_device =
  813. platform_create_bundle(&tuxedo_platform_driver,
  814. tuxedo_wmi_probe, NULL, 0, NULL, 0);
  815. if (unlikely(IS_ERR(tuxedo_platform_device)))
  816. return PTR_ERR(tuxedo_platform_device);
  817. err = tuxedo_rfkill_init();
  818. if (unlikely(err))
  819. TUXEDO_ERROR("Could not register rfkill device\n");
  820. err = tuxedo_input_init();
  821. if (unlikely(err))
  822. TUXEDO_ERROR("Could not register input device\n");
  823. err = tuxedo_led_init();
  824. if (unlikely(err))
  825. TUXEDO_ERROR("Could not register LED device\n");
  826. return 0;
  827. }
  828. static void __exit tuxedo_exit(void)
  829. {
  830. tuxedo_led_exit();
  831. tuxedo_input_exit();
  832. tuxedo_rfkill_exit();
  833. platform_device_unregister(tuxedo_platform_device);
  834. platform_driver_unregister(&tuxedo_platform_driver);
  835. }
  836. module_init(tuxedo_init);
  837. module_exit(tuxedo_exit);
  838. MODULE_AUTHOR("Christoph Jaeger <[email protected]>");
  839. MODULE_DESCRIPTION("Tuxedo/Clevo laptop driver.");
  840. MODULE_LICENSE("GPL");
  841. MODULE_VERSION("1.5.1");