]>
Commit | Line | Data |
---|---|---|
f3f837e5 BR |
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 | ||
20 | #define pr_fmt(fmt) "cros_ec_lightbar: " fmt | |
21 | ||
22 | #include <linux/ctype.h> | |
23 | #include <linux/delay.h> | |
24 | #include <linux/device.h> | |
25 | #include <linux/fs.h> | |
26 | #include <linux/kobject.h> | |
27 | #include <linux/mfd/cros_ec.h> | |
28 | #include <linux/mfd/cros_ec_commands.h> | |
29 | #include <linux/module.h> | |
30 | #include <linux/platform_device.h> | |
31 | #include <linux/sched.h> | |
32 | #include <linux/types.h> | |
33 | #include <linux/uaccess.h> | |
a8411784 | 34 | #include <linux/slab.h> |
f3f837e5 BR |
35 | |
36 | #include "cros_ec_dev.h" | |
37 | ||
38 | /* Rate-limit the lightbar interface to prevent DoS. */ | |
39 | static unsigned long lb_interval_jiffies = 50 * HZ / 1000; | |
40 | ||
41 | static ssize_t interval_msec_show(struct device *dev, | |
42 | struct device_attribute *attr, char *buf) | |
43 | { | |
44 | unsigned long msec = lb_interval_jiffies * 1000 / HZ; | |
45 | ||
46 | return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); | |
47 | } | |
48 | ||
49 | static ssize_t interval_msec_store(struct device *dev, | |
50 | struct device_attribute *attr, | |
51 | const char *buf, size_t count) | |
52 | { | |
53 | unsigned long msec; | |
54 | ||
55 | if (kstrtoul(buf, 0, &msec)) | |
56 | return -EINVAL; | |
57 | ||
58 | lb_interval_jiffies = msec * HZ / 1000; | |
59 | ||
60 | return count; | |
61 | } | |
62 | ||
63 | static DEFINE_MUTEX(lb_mutex); | |
64 | /* Return 0 if able to throttle correctly, error otherwise */ | |
65 | static int lb_throttle(void) | |
66 | { | |
67 | static unsigned long last_access; | |
68 | unsigned long now, next_timeslot; | |
69 | long delay; | |
70 | int ret = 0; | |
71 | ||
72 | mutex_lock(&lb_mutex); | |
73 | ||
74 | now = jiffies; | |
75 | next_timeslot = last_access + lb_interval_jiffies; | |
76 | ||
77 | if (time_before(now, next_timeslot)) { | |
78 | delay = (long)(next_timeslot) - (long)now; | |
79 | set_current_state(TASK_INTERRUPTIBLE); | |
80 | if (schedule_timeout(delay) > 0) { | |
81 | /* interrupted - just abort */ | |
82 | ret = -EINTR; | |
83 | goto out; | |
84 | } | |
85 | now = jiffies; | |
86 | } | |
87 | ||
88 | last_access = now; | |
89 | out: | |
90 | mutex_unlock(&lb_mutex); | |
91 | ||
92 | return ret; | |
93 | } | |
94 | ||
57b33ff0 | 95 | static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec) |
a8411784 JMC |
96 | { |
97 | struct cros_ec_command *msg; | |
98 | int len; | |
99 | ||
100 | len = max(sizeof(struct ec_params_lightbar), | |
101 | sizeof(struct ec_response_lightbar)); | |
102 | ||
103 | msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); | |
104 | if (!msg) | |
105 | return NULL; | |
106 | ||
107 | msg->version = 0; | |
57b33ff0 | 108 | msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset; |
a8411784 JMC |
109 | msg->outsize = sizeof(struct ec_params_lightbar); |
110 | msg->insize = sizeof(struct ec_response_lightbar); | |
111 | ||
112 | return msg; | |
113 | } | |
f3f837e5 | 114 | |
57b33ff0 | 115 | static int get_lightbar_version(struct cros_ec_dev *ec, |
f3f837e5 BR |
116 | uint32_t *ver_ptr, uint32_t *flg_ptr) |
117 | { | |
118 | struct ec_params_lightbar *param; | |
119 | struct ec_response_lightbar *resp; | |
a8411784 | 120 | struct cros_ec_command *msg; |
f3f837e5 BR |
121 | int ret; |
122 | ||
57b33ff0 | 123 | msg = alloc_lightbar_cmd_msg(ec); |
a8411784 | 124 | if (!msg) |
f3f837e5 BR |
125 | return 0; |
126 | ||
a8411784 JMC |
127 | param = (struct ec_params_lightbar *)msg->data; |
128 | param->cmd = LIGHTBAR_CMD_VERSION; | |
57b33ff0 | 129 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
a8411784 JMC |
130 | if (ret < 0) { |
131 | ret = 0; | |
132 | goto exit; | |
133 | } | |
134 | ||
135 | switch (msg->result) { | |
f3f837e5 BR |
136 | case EC_RES_INVALID_PARAM: |
137 | /* Pixel had no version command. */ | |
138 | if (ver_ptr) | |
139 | *ver_ptr = 0; | |
140 | if (flg_ptr) | |
141 | *flg_ptr = 0; | |
a8411784 JMC |
142 | ret = 1; |
143 | goto exit; | |
f3f837e5 BR |
144 | |
145 | case EC_RES_SUCCESS: | |
a8411784 | 146 | resp = (struct ec_response_lightbar *)msg->data; |
f3f837e5 BR |
147 | |
148 | /* Future devices w/lightbars should implement this command */ | |
149 | if (ver_ptr) | |
150 | *ver_ptr = resp->version.num; | |
151 | if (flg_ptr) | |
152 | *flg_ptr = resp->version.flags; | |
a8411784 JMC |
153 | ret = 1; |
154 | goto exit; | |
f3f837e5 BR |
155 | } |
156 | ||
157 | /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ | |
a8411784 JMC |
158 | ret = 0; |
159 | exit: | |
160 | kfree(msg); | |
161 | return ret; | |
f3f837e5 BR |
162 | } |
163 | ||
164 | static ssize_t version_show(struct device *dev, | |
165 | struct device_attribute *attr, char *buf) | |
166 | { | |
a8411784 | 167 | uint32_t version = 0, flags = 0; |
57b33ff0 GG |
168 | struct cros_ec_dev *ec = container_of(dev, |
169 | struct cros_ec_dev, class_dev); | |
f3f837e5 BR |
170 | int ret; |
171 | ||
172 | ret = lb_throttle(); | |
173 | if (ret) | |
174 | return ret; | |
175 | ||
176 | /* This should always succeed, because we check during init. */ | |
177 | if (!get_lightbar_version(ec, &version, &flags)) | |
178 | return -EIO; | |
179 | ||
180 | return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); | |
181 | } | |
182 | ||
183 | static ssize_t brightness_store(struct device *dev, | |
184 | struct device_attribute *attr, | |
185 | const char *buf, size_t count) | |
186 | { | |
187 | struct ec_params_lightbar *param; | |
a8411784 | 188 | struct cros_ec_command *msg; |
f3f837e5 BR |
189 | int ret; |
190 | unsigned int val; | |
57b33ff0 GG |
191 | struct cros_ec_dev *ec = container_of(dev, |
192 | struct cros_ec_dev, class_dev); | |
f3f837e5 BR |
193 | |
194 | if (kstrtouint(buf, 0, &val)) | |
195 | return -EINVAL; | |
196 | ||
57b33ff0 | 197 | msg = alloc_lightbar_cmd_msg(ec); |
a8411784 JMC |
198 | if (!msg) |
199 | return -ENOMEM; | |
200 | ||
201 | param = (struct ec_params_lightbar *)msg->data; | |
256ab950 SB |
202 | param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS; |
203 | param->set_brightness.num = val; | |
f3f837e5 BR |
204 | ret = lb_throttle(); |
205 | if (ret) | |
a8411784 | 206 | goto exit; |
f3f837e5 | 207 | |
57b33ff0 | 208 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
f3f837e5 | 209 | if (ret < 0) |
a8411784 | 210 | goto exit; |
f3f837e5 | 211 | |
a8411784 JMC |
212 | if (msg->result != EC_RES_SUCCESS) { |
213 | ret = -EINVAL; | |
214 | goto exit; | |
215 | } | |
f3f837e5 | 216 | |
a8411784 JMC |
217 | ret = count; |
218 | exit: | |
219 | kfree(msg); | |
220 | return ret; | |
f3f837e5 BR |
221 | } |
222 | ||
223 | ||
224 | /* | |
225 | * We expect numbers, and we'll keep reading until we find them, skipping over | |
226 | * any whitespace (sysfs guarantees that the input is null-terminated). Every | |
227 | * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first | |
228 | * parsing error, if we don't parse any numbers, or if we have numbers left | |
229 | * over. | |
230 | */ | |
231 | static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, | |
232 | const char *buf, size_t count) | |
233 | { | |
234 | struct ec_params_lightbar *param; | |
a8411784 | 235 | struct cros_ec_command *msg; |
57b33ff0 GG |
236 | struct cros_ec_dev *ec = container_of(dev, |
237 | struct cros_ec_dev, class_dev); | |
f3f837e5 BR |
238 | unsigned int val[4]; |
239 | int ret, i = 0, j = 0, ok = 0; | |
240 | ||
57b33ff0 | 241 | msg = alloc_lightbar_cmd_msg(ec); |
a8411784 JMC |
242 | if (!msg) |
243 | return -ENOMEM; | |
244 | ||
f3f837e5 BR |
245 | do { |
246 | /* Skip any whitespace */ | |
247 | while (*buf && isspace(*buf)) | |
248 | buf++; | |
249 | ||
250 | if (!*buf) | |
251 | break; | |
252 | ||
253 | ret = sscanf(buf, "%i", &val[i++]); | |
254 | if (ret == 0) | |
f14ae099 | 255 | goto exit; |
f3f837e5 BR |
256 | |
257 | if (i == 4) { | |
a8411784 | 258 | param = (struct ec_params_lightbar *)msg->data; |
256ab950 SB |
259 | param->cmd = LIGHTBAR_CMD_SET_RGB; |
260 | param->set_rgb.led = val[0]; | |
261 | param->set_rgb.red = val[1]; | |
262 | param->set_rgb.green = val[2]; | |
263 | param->set_rgb.blue = val[3]; | |
f3f837e5 BR |
264 | /* |
265 | * Throttle only the first of every four transactions, | |
266 | * so that the user can update all four LEDs at once. | |
267 | */ | |
268 | if ((j++ % 4) == 0) { | |
269 | ret = lb_throttle(); | |
270 | if (ret) | |
f14ae099 | 271 | goto exit; |
f3f837e5 BR |
272 | } |
273 | ||
57b33ff0 | 274 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
f3f837e5 | 275 | if (ret < 0) |
a8411784 | 276 | goto exit; |
f3f837e5 | 277 | |
f14ae099 | 278 | if (msg->result != EC_RES_SUCCESS) |
a8411784 | 279 | goto exit; |
f3f837e5 BR |
280 | |
281 | i = 0; | |
282 | ok = 1; | |
283 | } | |
284 | ||
285 | /* Skip over the number we just read */ | |
286 | while (*buf && !isspace(*buf)) | |
287 | buf++; | |
288 | ||
289 | } while (*buf); | |
290 | ||
a8411784 JMC |
291 | exit: |
292 | kfree(msg); | |
f3f837e5 BR |
293 | return (ok && i == 0) ? count : -EINVAL; |
294 | } | |
295 | ||
377415ab | 296 | static char const *seqname[] = { |
f3f837e5 BR |
297 | "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", |
298 | "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", | |
299 | }; | |
300 | ||
301 | static ssize_t sequence_show(struct device *dev, | |
302 | struct device_attribute *attr, char *buf) | |
303 | { | |
304 | struct ec_params_lightbar *param; | |
305 | struct ec_response_lightbar *resp; | |
a8411784 | 306 | struct cros_ec_command *msg; |
f3f837e5 | 307 | int ret; |
57b33ff0 GG |
308 | struct cros_ec_dev *ec = container_of(dev, |
309 | struct cros_ec_dev, class_dev); | |
f3f837e5 | 310 | |
57b33ff0 | 311 | msg = alloc_lightbar_cmd_msg(ec); |
a8411784 JMC |
312 | if (!msg) |
313 | return -ENOMEM; | |
314 | ||
315 | param = (struct ec_params_lightbar *)msg->data; | |
f3f837e5 BR |
316 | param->cmd = LIGHTBAR_CMD_GET_SEQ; |
317 | ret = lb_throttle(); | |
318 | if (ret) | |
a8411784 | 319 | goto exit; |
f3f837e5 | 320 | |
57b33ff0 | 321 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
f3f837e5 | 322 | if (ret < 0) |
a8411784 | 323 | goto exit; |
f3f837e5 | 324 | |
a8411784 JMC |
325 | if (msg->result != EC_RES_SUCCESS) { |
326 | ret = scnprintf(buf, PAGE_SIZE, | |
327 | "ERROR: EC returned %d\n", msg->result); | |
328 | goto exit; | |
329 | } | |
f3f837e5 | 330 | |
a8411784 | 331 | resp = (struct ec_response_lightbar *)msg->data; |
f3f837e5 | 332 | if (resp->get_seq.num >= ARRAY_SIZE(seqname)) |
a8411784 | 333 | ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); |
f3f837e5 | 334 | else |
a8411784 JMC |
335 | ret = scnprintf(buf, PAGE_SIZE, "%s\n", |
336 | seqname[resp->get_seq.num]); | |
337 | ||
338 | exit: | |
339 | kfree(msg); | |
340 | return ret; | |
f3f837e5 BR |
341 | } |
342 | ||
343 | static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, | |
344 | const char *buf, size_t count) | |
345 | { | |
346 | struct ec_params_lightbar *param; | |
a8411784 | 347 | struct cros_ec_command *msg; |
f3f837e5 BR |
348 | unsigned int num; |
349 | int ret, len; | |
57b33ff0 GG |
350 | struct cros_ec_dev *ec = container_of(dev, |
351 | struct cros_ec_dev, class_dev); | |
f3f837e5 BR |
352 | |
353 | for (len = 0; len < count; len++) | |
354 | if (!isalnum(buf[len])) | |
355 | break; | |
356 | ||
357 | for (num = 0; num < ARRAY_SIZE(seqname); num++) | |
358 | if (!strncasecmp(seqname[num], buf, len)) | |
359 | break; | |
360 | ||
361 | if (num >= ARRAY_SIZE(seqname)) { | |
362 | ret = kstrtouint(buf, 0, &num); | |
363 | if (ret) | |
364 | return ret; | |
365 | } | |
366 | ||
88dfb8b4 CE |
367 | msg = alloc_lightbar_cmd_msg(ec); |
368 | if (!msg) | |
369 | return -ENOMEM; | |
370 | ||
a8411784 | 371 | param = (struct ec_params_lightbar *)msg->data; |
f3f837e5 BR |
372 | param->cmd = LIGHTBAR_CMD_SEQ; |
373 | param->seq.num = num; | |
374 | ret = lb_throttle(); | |
375 | if (ret) | |
88dfb8b4 | 376 | goto exit; |
f3f837e5 | 377 | |
57b33ff0 | 378 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
f3f837e5 | 379 | if (ret < 0) |
88dfb8b4 | 380 | goto exit; |
f3f837e5 | 381 | |
88dfb8b4 CE |
382 | if (msg->result != EC_RES_SUCCESS) { |
383 | ret = -EINVAL; | |
384 | goto exit; | |
385 | } | |
f3f837e5 | 386 | |
88dfb8b4 CE |
387 | ret = count; |
388 | exit: | |
389 | kfree(msg); | |
390 | return ret; | |
f3f837e5 BR |
391 | } |
392 | ||
393 | /* Module initialization */ | |
394 | ||
395 | static DEVICE_ATTR_RW(interval_msec); | |
396 | static DEVICE_ATTR_RO(version); | |
397 | static DEVICE_ATTR_WO(brightness); | |
398 | static DEVICE_ATTR_WO(led_rgb); | |
399 | static DEVICE_ATTR_RW(sequence); | |
400 | static struct attribute *__lb_cmds_attrs[] = { | |
401 | &dev_attr_interval_msec.attr, | |
402 | &dev_attr_version.attr, | |
403 | &dev_attr_brightness.attr, | |
404 | &dev_attr_led_rgb.attr, | |
405 | &dev_attr_sequence.attr, | |
406 | NULL, | |
407 | }; | |
f3f837e5 | 408 | |
57b33ff0 GG |
409 | static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, |
410 | struct attribute *a, int n) | |
f3f837e5 | 411 | { |
57b33ff0 GG |
412 | struct device *dev = container_of(kobj, struct device, kobj); |
413 | struct cros_ec_dev *ec = container_of(dev, | |
414 | struct cros_ec_dev, class_dev); | |
415 | struct platform_device *pdev = container_of(ec->dev, | |
416 | struct platform_device, dev); | |
417 | if (pdev->id != 0) | |
418 | return 0; | |
f3f837e5 BR |
419 | |
420 | /* Only instantiate this stuff if the EC has a lightbar */ | |
57b33ff0 GG |
421 | if (get_lightbar_version(ec, NULL, NULL)) |
422 | return a->mode; | |
423 | else | |
424 | return 0; | |
f3f837e5 BR |
425 | } |
426 | ||
57b33ff0 GG |
427 | struct attribute_group cros_ec_lightbar_attr_group = { |
428 | .name = "lightbar", | |
429 | .attrs = __lb_cmds_attrs, | |
430 | .is_visible = cros_ec_lightbar_attrs_are_visible, | |
431 | }; |