aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/chrome/cros_ec_lightbar.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-04-26 16:36:02 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2015-04-26 16:36:02 -0400
commit36a8032d77649430f5ef11fbf0df2bb026be0b04 (patch)
tree18e386a957bde5f71e9efd2cddf1d8aadafe48e4 /drivers/platform/chrome/cros_ec_lightbar.c
parent7f9f44308c8993c9ab8078d174dad34bea3e82d7 (diff)
parent96cba9b00e297303774bec59e192064d20adeb3d (diff)
Merge tag 'chrome-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform
Pull chrome platform updates from Olof Johansson: "Here's a set of updates to the Chrome OS platform drivers for this merge window. Main new things this cycle is: - Driver changes to expose the lightbar to users. With this, you can make your own blinkenlights on Chromebook Pixels. - Changes in the way that the atmel_mxt trackpads are probed. The laptop driver is trying to be smart and not instantiate the devices that don't answer to probe. For the trackpad that can come up in two modes (bootloader or regular), this gets complicated since the driver already knows how to handle the two modes including the actual addresses used. So now the laptop driver needs to know more too, instantiating the regular address even if the bootloader one is the probe that passed. - mfd driver improvements by Javier Martines Canillas, and a few bugfixes from him, kbuild and myself" * tag 'chrome-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform: platform/chrome: chromeos_laptop - instantiate Atmel at primary address platform/chrome: cros_ec_lpc - Depend on X86 || COMPILE_TEST platform/chrome: cros_ec_lpc - Include linux/io.h header file platform/chrome: fix platform_no_drv_owner.cocci warnings platform/chrome: cros_ec_lightbar - fix duplicate const warning platform/chrome: cros_ec_dev - fix Unknown escape '%' warning platform/chrome: Expose Chrome OS Lightbar to users platform/chrome: Create sysfs attributes for the ChromeOS EC mfd: cros_ec: Instantiate ChromeOS EC character device platform/chrome: Add Chrome OS EC userspace device interface platform/chrome: Add cros_ec_lpc driver for x86 devices mfd: cros_ec: Add char dev and virtual dev pointers mfd: cros_ec: Use fixed size arrays to transfer data with the EC
Diffstat (limited to 'drivers/platform/chrome/cros_ec_lightbar.c')
-rw-r--r--drivers/platform/chrome/cros_ec_lightbar.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c
new file mode 100644
index 000000000000..b4ff47a9069a
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_lightbar.c
@@ -0,0 +1,367 @@
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>
34
35#include "cros_ec_dev.h"
36
37/* Rate-limit the lightbar interface to prevent DoS. */
38static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
39
40static ssize_t interval_msec_show(struct device *dev,
41 struct device_attribute *attr, char *buf)
42{
43 unsigned long msec = lb_interval_jiffies * 1000 / HZ;
44
45 return scnprintf(buf, PAGE_SIZE, "%lu\n", msec);
46}
47
48static ssize_t interval_msec_store(struct device *dev,
49 struct device_attribute *attr,
50 const char *buf, size_t count)
51{
52 unsigned long msec;
53
54 if (kstrtoul(buf, 0, &msec))
55 return -EINVAL;
56
57 lb_interval_jiffies = msec * HZ / 1000;
58
59 return count;
60}
61
62static DEFINE_MUTEX(lb_mutex);
63/* Return 0 if able to throttle correctly, error otherwise */
64static int lb_throttle(void)
65{
66 static unsigned long last_access;
67 unsigned long now, next_timeslot;
68 long delay;
69 int ret = 0;
70
71 mutex_lock(&lb_mutex);
72
73 now = jiffies;
74 next_timeslot = last_access + lb_interval_jiffies;
75
76 if (time_before(now, next_timeslot)) {
77 delay = (long)(next_timeslot) - (long)now;
78 set_current_state(TASK_INTERRUPTIBLE);
79 if (schedule_timeout(delay) > 0) {
80 /* interrupted - just abort */
81 ret = -EINTR;
82 goto out;
83 }
84 now = jiffies;
85 }
86
87 last_access = now;
88out:
89 mutex_unlock(&lb_mutex);
90
91 return ret;
92}
93
94#define INIT_MSG(P, R) { \
95 .command = EC_CMD_LIGHTBAR_CMD, \
96 .outsize = sizeof(*P), \
97 .insize = sizeof(*R), \
98 }
99
100static int get_lightbar_version(struct cros_ec_device *ec,
101 uint32_t *ver_ptr, uint32_t *flg_ptr)
102{
103 struct ec_params_lightbar *param;
104 struct ec_response_lightbar *resp;
105 struct cros_ec_command msg = INIT_MSG(param, resp);
106 int ret;
107
108 param = (struct ec_params_lightbar *)msg.outdata;
109 param->cmd = LIGHTBAR_CMD_VERSION;
110 ret = cros_ec_cmd_xfer(ec, &msg);
111 if (ret < 0)
112 return 0;
113
114 switch (msg.result) {
115 case EC_RES_INVALID_PARAM:
116 /* Pixel had no version command. */
117 if (ver_ptr)
118 *ver_ptr = 0;
119 if (flg_ptr)
120 *flg_ptr = 0;
121 return 1;
122
123 case EC_RES_SUCCESS:
124 resp = (struct ec_response_lightbar *)msg.indata;
125
126 /* Future devices w/lightbars should implement this command */
127 if (ver_ptr)
128 *ver_ptr = resp->version.num;
129 if (flg_ptr)
130 *flg_ptr = resp->version.flags;
131 return 1;
132 }
133
134 /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
135 return 0;
136}
137
138static ssize_t version_show(struct device *dev,
139 struct device_attribute *attr, char *buf)
140{
141 uint32_t version, flags;
142 struct cros_ec_device *ec = dev_get_drvdata(dev);
143 int ret;
144
145 ret = lb_throttle();
146 if (ret)
147 return ret;
148
149 /* This should always succeed, because we check during init. */
150 if (!get_lightbar_version(ec, &version, &flags))
151 return -EIO;
152
153 return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags);
154}
155
156static ssize_t brightness_store(struct device *dev,
157 struct device_attribute *attr,
158 const char *buf, size_t count)
159{
160 struct ec_params_lightbar *param;
161 struct ec_response_lightbar *resp;
162 struct cros_ec_command msg = INIT_MSG(param, resp);
163 int ret;
164 unsigned int val;
165 struct cros_ec_device *ec = dev_get_drvdata(dev);
166
167 if (kstrtouint(buf, 0, &val))
168 return -EINVAL;
169
170 param = (struct ec_params_lightbar *)msg.outdata;
171 param->cmd = LIGHTBAR_CMD_BRIGHTNESS;
172 param->brightness.num = val;
173 ret = lb_throttle();
174 if (ret)
175 return ret;
176
177 ret = cros_ec_cmd_xfer(ec, &msg);
178 if (ret < 0)
179 return ret;
180
181 if (msg.result != EC_RES_SUCCESS)
182 return -EINVAL;
183
184 return count;
185}
186
187
188/*
189 * We expect numbers, and we'll keep reading until we find them, skipping over
190 * any whitespace (sysfs guarantees that the input is null-terminated). Every
191 * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first
192 * parsing error, if we don't parse any numbers, or if we have numbers left
193 * over.
194 */
195static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr,
196 const char *buf, size_t count)
197{
198 struct ec_params_lightbar *param;
199 struct ec_response_lightbar *resp;
200 struct cros_ec_command msg = INIT_MSG(param, resp);
201 struct cros_ec_device *ec = dev_get_drvdata(dev);
202 unsigned int val[4];
203 int ret, i = 0, j = 0, ok = 0;
204
205 do {
206 /* Skip any whitespace */
207 while (*buf && isspace(*buf))
208 buf++;
209
210 if (!*buf)
211 break;
212
213 ret = sscanf(buf, "%i", &val[i++]);
214 if (ret == 0)
215 return -EINVAL;
216
217 if (i == 4) {
218 param = (struct ec_params_lightbar *)msg.outdata;
219 param->cmd = LIGHTBAR_CMD_RGB;
220 param->rgb.led = val[0];
221 param->rgb.red = val[1];
222 param->rgb.green = val[2];
223 param->rgb.blue = val[3];
224 /*
225 * Throttle only the first of every four transactions,
226 * so that the user can update all four LEDs at once.
227 */
228 if ((j++ % 4) == 0) {
229 ret = lb_throttle();
230 if (ret)
231 return ret;
232 }
233
234 ret = cros_ec_cmd_xfer(ec, &msg);
235 if (ret < 0)
236 return ret;
237
238 if (msg.result != EC_RES_SUCCESS)
239 return -EINVAL;
240
241 i = 0;
242 ok = 1;
243 }
244
245 /* Skip over the number we just read */
246 while (*buf && !isspace(*buf))
247 buf++;
248
249 } while (*buf);
250
251 return (ok && i == 0) ? count : -EINVAL;
252}
253
254static char const *seqname[] = {
255 "ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
256 "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI",
257};
258
259static ssize_t sequence_show(struct device *dev,
260 struct device_attribute *attr, char *buf)
261{
262 struct ec_params_lightbar *param;
263 struct ec_response_lightbar *resp;
264 struct cros_ec_command msg = INIT_MSG(param, resp);
265 int ret;
266 struct cros_ec_device *ec = dev_get_drvdata(dev);
267
268 param = (struct ec_params_lightbar *)msg.outdata;
269 param->cmd = LIGHTBAR_CMD_GET_SEQ;
270 ret = lb_throttle();
271 if (ret)
272 return ret;
273
274 ret = cros_ec_cmd_xfer(ec, &msg);
275 if (ret < 0)
276 return ret;
277
278 if (msg.result != EC_RES_SUCCESS)
279 return scnprintf(buf, PAGE_SIZE,
280 "ERROR: EC returned %d\n", msg.result);
281
282 resp = (struct ec_response_lightbar *)msg.indata;
283 if (resp->get_seq.num >= ARRAY_SIZE(seqname))
284 return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
285 else
286 return scnprintf(buf, PAGE_SIZE, "%s\n",
287 seqname[resp->get_seq.num]);
288}
289
290static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
291 const char *buf, size_t count)
292{
293 struct ec_params_lightbar *param;
294 struct ec_response_lightbar *resp;
295 struct cros_ec_command msg = INIT_MSG(param, resp);
296 unsigned int num;
297 int ret, len;
298 struct cros_ec_device *ec = dev_get_drvdata(dev);
299
300 for (len = 0; len < count; len++)
301 if (!isalnum(buf[len]))
302 break;
303
304 for (num = 0; num < ARRAY_SIZE(seqname); num++)
305 if (!strncasecmp(seqname[num], buf, len))
306 break;
307
308 if (num >= ARRAY_SIZE(seqname)) {
309 ret = kstrtouint(buf, 0, &num);
310 if (ret)
311 return ret;
312 }
313
314 param = (struct ec_params_lightbar *)msg.outdata;
315 param->cmd = LIGHTBAR_CMD_SEQ;
316 param->seq.num = num;
317 ret = lb_throttle();
318 if (ret)
319 return ret;
320
321 ret = cros_ec_cmd_xfer(ec, &msg);
322 if (ret < 0)
323 return ret;
324
325 if (msg.result != EC_RES_SUCCESS)
326 return -EINVAL;
327
328 return count;
329}
330
331/* Module initialization */
332
333static DEVICE_ATTR_RW(interval_msec);
334static DEVICE_ATTR_RO(version);
335static DEVICE_ATTR_WO(brightness);
336static DEVICE_ATTR_WO(led_rgb);
337static DEVICE_ATTR_RW(sequence);
338static struct attribute *__lb_cmds_attrs[] = {
339 &dev_attr_interval_msec.attr,
340 &dev_attr_version.attr,
341 &dev_attr_brightness.attr,
342 &dev_attr_led_rgb.attr,
343 &dev_attr_sequence.attr,
344 NULL,
345};
346static struct attribute_group lb_cmds_attr_group = {
347 .name = "lightbar",
348 .attrs = __lb_cmds_attrs,
349};
350
351void ec_dev_lightbar_init(struct cros_ec_device *ec)
352{
353 int ret = 0;
354
355 /* Only instantiate this stuff if the EC has a lightbar */
356 if (!get_lightbar_version(ec, NULL, NULL))
357 return;
358
359 ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group);
360 if (ret)
361 pr_warn("sysfs_create_group() failed: %d\n", ret);
362}
363
364void ec_dev_lightbar_remove(struct cros_ec_device *ec)
365{
366 sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group);
367}