keyboard_leds.c 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Keyboard backlight LED driver for the Wilco Embedded Controller
  4. *
  5. * Copyright 2019 Google LLC
  6. *
  7. * Since the EC will never change the backlight level of its own accord,
  8. * we don't need to implement a brightness_get() method.
  9. */
  10. #include <linux/device.h>
  11. #include <linux/kernel.h>
  12. #include <linux/leds.h>
  13. #include <linux/platform_data/wilco-ec.h>
  14. #include <linux/slab.h>
  15. #define WILCO_EC_COMMAND_KBBL 0x75
  16. #define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */
  17. #define WILCO_KBBL_DEFAULT_BRIGHTNESS 0
  18. struct wilco_keyboard_leds {
  19. struct wilco_ec_device *ec;
  20. struct led_classdev keyboard;
  21. };
  22. enum wilco_kbbl_subcommand {
  23. WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00,
  24. WILCO_KBBL_SUBCMD_GET_STATE = 0x01,
  25. WILCO_KBBL_SUBCMD_SET_STATE = 0x02,
  26. };
  27. /**
  28. * struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control.
  29. * @command: Always WILCO_EC_COMMAND_KBBL.
  30. * @status: Set by EC to 0 on success, 0xFF on failure.
  31. * @subcmd: One of enum wilco_kbbl_subcommand.
  32. * @reserved3: Should be 0.
  33. * @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM.
  34. * @reserved5to8: Should be 0.
  35. * @percent: Brightness in 0-100. Only meaningful in PWM mode.
  36. * @reserved10to15: Should be 0.
  37. */
  38. struct wilco_keyboard_leds_msg {
  39. u8 command;
  40. u8 status;
  41. u8 subcmd;
  42. u8 reserved3;
  43. u8 mode;
  44. u8 reserved5to8[4];
  45. u8 percent;
  46. u8 reserved10to15[6];
  47. } __packed;
  48. /* Send a request, get a response, and check that the response is good. */
  49. static int send_kbbl_msg(struct wilco_ec_device *ec,
  50. struct wilco_keyboard_leds_msg *request,
  51. struct wilco_keyboard_leds_msg *response)
  52. {
  53. struct wilco_ec_message msg;
  54. int ret;
  55. memset(&msg, 0, sizeof(msg));
  56. msg.type = WILCO_EC_MSG_LEGACY;
  57. msg.request_data = request;
  58. msg.request_size = sizeof(*request);
  59. msg.response_data = response;
  60. msg.response_size = sizeof(*response);
  61. ret = wilco_ec_mailbox(ec, &msg);
  62. if (ret < 0) {
  63. dev_err(ec->dev,
  64. "Failed sending keyboard LEDs command: %d\n", ret);
  65. return ret;
  66. }
  67. return 0;
  68. }
  69. static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness)
  70. {
  71. struct wilco_keyboard_leds_msg request;
  72. struct wilco_keyboard_leds_msg response;
  73. int ret;
  74. memset(&request, 0, sizeof(request));
  75. request.command = WILCO_EC_COMMAND_KBBL;
  76. request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE;
  77. request.mode = WILCO_KBBL_MODE_FLAG_PWM;
  78. request.percent = brightness;
  79. ret = send_kbbl_msg(ec, &request, &response);
  80. if (ret < 0)
  81. return ret;
  82. if (response.status) {
  83. dev_err(ec->dev,
  84. "EC reported failure sending keyboard LEDs command: %d\n",
  85. response.status);
  86. return -EIO;
  87. }
  88. return 0;
  89. }
  90. static int kbbl_exist(struct wilco_ec_device *ec, bool *exists)
  91. {
  92. struct wilco_keyboard_leds_msg request;
  93. struct wilco_keyboard_leds_msg response;
  94. int ret;
  95. memset(&request, 0, sizeof(request));
  96. request.command = WILCO_EC_COMMAND_KBBL;
  97. request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES;
  98. ret = send_kbbl_msg(ec, &request, &response);
  99. if (ret < 0)
  100. return ret;
  101. *exists = response.status != 0xFF;
  102. return 0;
  103. }
  104. /**
  105. * kbbl_init() - Initialize the state of the keyboard backlight.
  106. * @ec: EC device to talk to.
  107. *
  108. * Gets the current brightness, ensuring that the BIOS already initialized the
  109. * backlight to PWM mode. If not in PWM mode, then the current brightness is
  110. * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS.
  111. *
  112. * Return: Final brightness of the keyboard, or negative error code on failure.
  113. */
  114. static int kbbl_init(struct wilco_ec_device *ec)
  115. {
  116. struct wilco_keyboard_leds_msg request;
  117. struct wilco_keyboard_leds_msg response;
  118. int ret;
  119. memset(&request, 0, sizeof(request));
  120. request.command = WILCO_EC_COMMAND_KBBL;
  121. request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE;
  122. ret = send_kbbl_msg(ec, &request, &response);
  123. if (ret < 0)
  124. return ret;
  125. if (response.status) {
  126. dev_err(ec->dev,
  127. "EC reported failure sending keyboard LEDs command: %d\n",
  128. response.status);
  129. return -EIO;
  130. }
  131. if (response.mode & WILCO_KBBL_MODE_FLAG_PWM)
  132. return response.percent;
  133. ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS);
  134. if (ret < 0)
  135. return ret;
  136. return WILCO_KBBL_DEFAULT_BRIGHTNESS;
  137. }
  138. static int wilco_keyboard_leds_set(struct led_classdev *cdev,
  139. enum led_brightness brightness)
  140. {
  141. struct wilco_keyboard_leds *wkl =
  142. container_of(cdev, struct wilco_keyboard_leds, keyboard);
  143. return set_kbbl(wkl->ec, brightness);
  144. }
  145. int wilco_keyboard_leds_init(struct wilco_ec_device *ec)
  146. {
  147. struct wilco_keyboard_leds *wkl;
  148. bool leds_exist;
  149. int ret;
  150. ret = kbbl_exist(ec, &leds_exist);
  151. if (ret < 0) {
  152. dev_err(ec->dev,
  153. "Failed checking keyboard LEDs support: %d\n", ret);
  154. return ret;
  155. }
  156. if (!leds_exist)
  157. return 0;
  158. wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL);
  159. if (!wkl)
  160. return -ENOMEM;
  161. wkl->ec = ec;
  162. wkl->keyboard.name = "platform::kbd_backlight";
  163. wkl->keyboard.max_brightness = 100;
  164. wkl->keyboard.flags = LED_CORE_SUSPENDRESUME;
  165. wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set;
  166. ret = kbbl_init(ec);
  167. if (ret < 0)
  168. return ret;
  169. wkl->keyboard.brightness = ret;
  170. return devm_led_classdev_register(ec->dev, &wkl->keyboard);
  171. }