cros_ec_lightbar.c 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace
  3. *
  4. * Copyright (C) 2014 Google, Inc.
  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. * 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 pr_fmt(fmt) "cros_ec_lightbar: " fmt
  20. #include <linux/ctype.h>
  21. #include <linux/delay.h>
  22. #include <linux/device.h>
  23. #include <linux/fs.h>
  24. #include <linux/kobject.h>
  25. #include <linux/mfd/cros_ec.h>
  26. #include <linux/mfd/cros_ec_commands.h>
  27. #include <linux/module.h>
  28. #include <linux/platform_device.h>
  29. #include <linux/sched.h>
  30. #include <linux/types.h>
  31. #include <linux/uaccess.h>
  32. #include "cros_ec_dev.h"
  33. /* Rate-limit the lightbar interface to prevent DoS. */
  34. static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
  35. static ssize_t interval_msec_show(struct device *dev,
  36. struct device_attribute *attr, char *buf)
  37. {
  38. unsigned long msec = lb_interval_jiffies * 1000 / HZ;
  39. return scnprintf(buf, PAGE_SIZE, "%lu\n", msec);
  40. }
  41. static ssize_t interval_msec_store(struct device *dev,
  42. struct device_attribute *attr,
  43. const char *buf, size_t count)
  44. {
  45. unsigned long msec;
  46. if (kstrtoul(buf, 0, &msec))
  47. return -EINVAL;
  48. lb_interval_jiffies = msec * HZ / 1000;
  49. return count;
  50. }
  51. static DEFINE_MUTEX(lb_mutex);
  52. /* Return 0 if able to throttle correctly, error otherwise */
  53. static int lb_throttle(void)
  54. {
  55. static unsigned long last_access;
  56. unsigned long now, next_timeslot;
  57. long delay;
  58. int ret = 0;
  59. mutex_lock(&lb_mutex);
  60. now = jiffies;
  61. next_timeslot = last_access + lb_interval_jiffies;
  62. if (time_before(now, next_timeslot)) {
  63. delay = (long)(next_timeslot) - (long)now;
  64. set_current_state(TASK_INTERRUPTIBLE);
  65. if (schedule_timeout(delay) > 0) {
  66. /* interrupted - just abort */
  67. ret = -EINTR;
  68. goto out;
  69. }
  70. now = jiffies;
  71. }
  72. last_access = now;
  73. out:
  74. mutex_unlock(&lb_mutex);
  75. return ret;
  76. }
  77. #define INIT_MSG(P, R) { \
  78. .command = EC_CMD_LIGHTBAR_CMD, \
  79. .outsize = sizeof(*P), \
  80. .insize = sizeof(*R), \
  81. }
  82. static int get_lightbar_version(struct cros_ec_device *ec,
  83. uint32_t *ver_ptr, uint32_t *flg_ptr)
  84. {
  85. struct ec_params_lightbar *param;
  86. struct ec_response_lightbar *resp;
  87. struct cros_ec_command msg = INIT_MSG(param, resp);
  88. int ret;
  89. param = (struct ec_params_lightbar *)msg.outdata;
  90. param->cmd = LIGHTBAR_CMD_VERSION;
  91. ret = cros_ec_cmd_xfer(ec, &msg);
  92. if (ret < 0)
  93. return 0;
  94. switch (msg.result) {
  95. case EC_RES_INVALID_PARAM:
  96. /* Pixel had no version command. */
  97. if (ver_ptr)
  98. *ver_ptr = 0;
  99. if (flg_ptr)
  100. *flg_ptr = 0;
  101. return 1;
  102. case EC_RES_SUCCESS:
  103. resp = (struct ec_response_lightbar *)msg.indata;
  104. /* Future devices w/lightbars should implement this command */
  105. if (ver_ptr)
  106. *ver_ptr = resp->version.num;
  107. if (flg_ptr)
  108. *flg_ptr = resp->version.flags;
  109. return 1;
  110. }
  111. /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
  112. return 0;
  113. }
  114. static ssize_t version_show(struct device *dev,
  115. struct device_attribute *attr, char *buf)
  116. {
  117. uint32_t version, flags;
  118. struct cros_ec_device *ec = dev_get_drvdata(dev);
  119. int ret;
  120. ret = lb_throttle();
  121. if (ret)
  122. return ret;
  123. /* This should always succeed, because we check during init. */
  124. if (!get_lightbar_version(ec, &version, &flags))
  125. return -EIO;
  126. return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags);
  127. }
  128. static ssize_t brightness_store(struct device *dev,
  129. struct device_attribute *attr,
  130. const char *buf, size_t count)
  131. {
  132. struct ec_params_lightbar *param;
  133. struct ec_response_lightbar *resp;
  134. struct cros_ec_command msg = INIT_MSG(param, resp);
  135. int ret;
  136. unsigned int val;
  137. struct cros_ec_device *ec = dev_get_drvdata(dev);
  138. if (kstrtouint(buf, 0, &val))
  139. return -EINVAL;
  140. param = (struct ec_params_lightbar *)msg.outdata;
  141. param->cmd = LIGHTBAR_CMD_BRIGHTNESS;
  142. param->brightness.num = val;
  143. ret = lb_throttle();
  144. if (ret)
  145. return ret;
  146. ret = cros_ec_cmd_xfer(ec, &msg);
  147. if (ret < 0)
  148. return ret;
  149. if (msg.result != EC_RES_SUCCESS)
  150. return -EINVAL;
  151. return count;
  152. }
  153. /*
  154. * We expect numbers, and we'll keep reading until we find them, skipping over
  155. * any whitespace (sysfs guarantees that the input is null-terminated). Every
  156. * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first
  157. * parsing error, if we don't parse any numbers, or if we have numbers left
  158. * over.
  159. */
  160. static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr,
  161. const char *buf, size_t count)
  162. {
  163. struct ec_params_lightbar *param;
  164. struct ec_response_lightbar *resp;
  165. struct cros_ec_command msg = INIT_MSG(param, resp);
  166. struct cros_ec_device *ec = dev_get_drvdata(dev);
  167. unsigned int val[4];
  168. int ret, i = 0, j = 0, ok = 0;
  169. do {
  170. /* Skip any whitespace */
  171. while (*buf && isspace(*buf))
  172. buf++;
  173. if (!*buf)
  174. break;
  175. ret = sscanf(buf, "%i", &val[i++]);
  176. if (ret == 0)
  177. return -EINVAL;
  178. if (i == 4) {
  179. param = (struct ec_params_lightbar *)msg.outdata;
  180. param->cmd = LIGHTBAR_CMD_RGB;
  181. param->rgb.led = val[0];
  182. param->rgb.red = val[1];
  183. param->rgb.green = val[2];
  184. param->rgb.blue = val[3];
  185. /*
  186. * Throttle only the first of every four transactions,
  187. * so that the user can update all four LEDs at once.
  188. */
  189. if ((j++ % 4) == 0) {
  190. ret = lb_throttle();
  191. if (ret)
  192. return ret;
  193. }
  194. ret = cros_ec_cmd_xfer(ec, &msg);
  195. if (ret < 0)
  196. return ret;
  197. if (msg.result != EC_RES_SUCCESS)
  198. return -EINVAL;
  199. i = 0;
  200. ok = 1;
  201. }
  202. /* Skip over the number we just read */
  203. while (*buf && !isspace(*buf))
  204. buf++;
  205. } while (*buf);
  206. return (ok && i == 0) ? count : -EINVAL;
  207. }
  208. static char const *seqname[] = {
  209. "ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
  210. "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI",
  211. };
  212. static ssize_t sequence_show(struct device *dev,
  213. struct device_attribute *attr, char *buf)
  214. {
  215. struct ec_params_lightbar *param;
  216. struct ec_response_lightbar *resp;
  217. struct cros_ec_command msg = INIT_MSG(param, resp);
  218. int ret;
  219. struct cros_ec_device *ec = dev_get_drvdata(dev);
  220. param = (struct ec_params_lightbar *)msg.outdata;
  221. param->cmd = LIGHTBAR_CMD_GET_SEQ;
  222. ret = lb_throttle();
  223. if (ret)
  224. return ret;
  225. ret = cros_ec_cmd_xfer(ec, &msg);
  226. if (ret < 0)
  227. return ret;
  228. if (msg.result != EC_RES_SUCCESS)
  229. return scnprintf(buf, PAGE_SIZE,
  230. "ERROR: EC returned %d\n", msg.result);
  231. resp = (struct ec_response_lightbar *)msg.indata;
  232. if (resp->get_seq.num >= ARRAY_SIZE(seqname))
  233. return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
  234. else
  235. return scnprintf(buf, PAGE_SIZE, "%s\n",
  236. seqname[resp->get_seq.num]);
  237. }
  238. static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
  239. const char *buf, size_t count)
  240. {
  241. struct ec_params_lightbar *param;
  242. struct ec_response_lightbar *resp;
  243. struct cros_ec_command msg = INIT_MSG(param, resp);
  244. unsigned int num;
  245. int ret, len;
  246. struct cros_ec_device *ec = dev_get_drvdata(dev);
  247. for (len = 0; len < count; len++)
  248. if (!isalnum(buf[len]))
  249. break;
  250. for (num = 0; num < ARRAY_SIZE(seqname); num++)
  251. if (!strncasecmp(seqname[num], buf, len))
  252. break;
  253. if (num >= ARRAY_SIZE(seqname)) {
  254. ret = kstrtouint(buf, 0, &num);
  255. if (ret)
  256. return ret;
  257. }
  258. param = (struct ec_params_lightbar *)msg.outdata;
  259. param->cmd = LIGHTBAR_CMD_SEQ;
  260. param->seq.num = num;
  261. ret = lb_throttle();
  262. if (ret)
  263. return ret;
  264. ret = cros_ec_cmd_xfer(ec, &msg);
  265. if (ret < 0)
  266. return ret;
  267. if (msg.result != EC_RES_SUCCESS)
  268. return -EINVAL;
  269. return count;
  270. }
  271. /* Module initialization */
  272. static DEVICE_ATTR_RW(interval_msec);
  273. static DEVICE_ATTR_RO(version);
  274. static DEVICE_ATTR_WO(brightness);
  275. static DEVICE_ATTR_WO(led_rgb);
  276. static DEVICE_ATTR_RW(sequence);
  277. static struct attribute *__lb_cmds_attrs[] = {
  278. &dev_attr_interval_msec.attr,
  279. &dev_attr_version.attr,
  280. &dev_attr_brightness.attr,
  281. &dev_attr_led_rgb.attr,
  282. &dev_attr_sequence.attr,
  283. NULL,
  284. };
  285. static struct attribute_group lb_cmds_attr_group = {
  286. .name = "lightbar",
  287. .attrs = __lb_cmds_attrs,
  288. };
  289. void ec_dev_lightbar_init(struct cros_ec_device *ec)
  290. {
  291. int ret = 0;
  292. /* Only instantiate this stuff if the EC has a lightbar */
  293. if (!get_lightbar_version(ec, NULL, NULL))
  294. return;
  295. ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group);
  296. if (ret)
  297. pr_warn("sysfs_create_group() failed: %d\n", ret);
  298. }
  299. void ec_dev_lightbar_remove(struct cros_ec_device *ec)
  300. {
  301. sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group);
  302. }