aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/chrome/cros_ec_dev.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_dev.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_dev.c')
-rw-r--r--drivers/platform/chrome/cros_ec_dev.c274
1 files changed, 274 insertions, 0 deletions
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c
new file mode 100644
index 000000000000..6090d0b2826f
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_dev.c
@@ -0,0 +1,274 @@
1/*
2 * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space
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#include <linux/fs.h>
21#include <linux/module.h>
22#include <linux/platform_device.h>
23#include <linux/uaccess.h>
24
25#include "cros_ec_dev.h"
26
27/* Device variables */
28#define CROS_MAX_DEV 128
29static struct class *cros_class;
30static int ec_major;
31
32/* Basic communication */
33static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen)
34{
35 struct ec_response_get_version *resp;
36 static const char * const current_image_name[] = {
37 "unknown", "read-only", "read-write", "invalid",
38 };
39 struct cros_ec_command msg = {
40 .version = 0,
41 .command = EC_CMD_GET_VERSION,
42 .outdata = { 0 },
43 .outsize = 0,
44 .indata = { 0 },
45 .insize = sizeof(*resp),
46 };
47 int ret;
48
49 ret = cros_ec_cmd_xfer(ec, &msg);
50 if (ret < 0)
51 return ret;
52
53 if (msg.result != EC_RES_SUCCESS) {
54 snprintf(str, maxlen,
55 "%s\nUnknown EC version: EC returned %d\n",
56 CROS_EC_DEV_VERSION, msg.result);
57 return 0;
58 }
59
60 resp = (struct ec_response_get_version *)msg.indata;
61 if (resp->current_image >= ARRAY_SIZE(current_image_name))
62 resp->current_image = 3; /* invalid */
63
64 snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
65 resp->version_string_ro, resp->version_string_rw,
66 current_image_name[resp->current_image]);
67
68 return 0;
69}
70
71/* Device file ops */
72static int ec_device_open(struct inode *inode, struct file *filp)
73{
74 filp->private_data = container_of(inode->i_cdev,
75 struct cros_ec_device, cdev);
76 return 0;
77}
78
79static int ec_device_release(struct inode *inode, struct file *filp)
80{
81 return 0;
82}
83
84static ssize_t ec_device_read(struct file *filp, char __user *buffer,
85 size_t length, loff_t *offset)
86{
87 struct cros_ec_device *ec = filp->private_data;
88 char msg[sizeof(struct ec_response_get_version) +
89 sizeof(CROS_EC_DEV_VERSION)];
90 size_t count;
91 int ret;
92
93 if (*offset != 0)
94 return 0;
95
96 ret = ec_get_version(ec, msg, sizeof(msg));
97 if (ret)
98 return ret;
99
100 count = min(length, strlen(msg));
101
102 if (copy_to_user(buffer, msg, count))
103 return -EFAULT;
104
105 *offset = count;
106 return count;
107}
108
109/* Ioctls */
110static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg)
111{
112 long ret;
113 struct cros_ec_command s_cmd = { };
114
115 if (copy_from_user(&s_cmd, arg, sizeof(s_cmd)))
116 return -EFAULT;
117
118 ret = cros_ec_cmd_xfer(ec, &s_cmd);
119 /* Only copy data to userland if data was received. */
120 if (ret < 0)
121 return ret;
122
123 if (copy_to_user(arg, &s_cmd, sizeof(s_cmd)))
124 return -EFAULT;
125
126 return 0;
127}
128
129static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg)
130{
131 struct cros_ec_readmem s_mem = { };
132 long num;
133
134 /* Not every platform supports direct reads */
135 if (!ec->cmd_readmem)
136 return -ENOTTY;
137
138 if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
139 return -EFAULT;
140
141 num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer);
142 if (num <= 0)
143 return num;
144
145 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem)))
146 return -EFAULT;
147
148 return 0;
149}
150
151static long ec_device_ioctl(struct file *filp, unsigned int cmd,
152 unsigned long arg)
153{
154 struct cros_ec_device *ec = filp->private_data;
155
156 if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
157 return -ENOTTY;
158
159 switch (cmd) {
160 case CROS_EC_DEV_IOCXCMD:
161 return ec_device_ioctl_xcmd(ec, (void __user *)arg);
162 case CROS_EC_DEV_IOCRDMEM:
163 return ec_device_ioctl_readmem(ec, (void __user *)arg);
164 }
165
166 return -ENOTTY;
167}
168
169/* Module initialization */
170static const struct file_operations fops = {
171 .open = ec_device_open,
172 .release = ec_device_release,
173 .read = ec_device_read,
174 .unlocked_ioctl = ec_device_ioctl,
175};
176
177static int ec_device_probe(struct platform_device *pdev)
178{
179 struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
180 int retval = -ENOTTY;
181 dev_t devno = MKDEV(ec_major, 0);
182
183 /* Instantiate it (and remember the EC) */
184 cdev_init(&ec->cdev, &fops);
185
186 retval = cdev_add(&ec->cdev, devno, 1);
187 if (retval) {
188 dev_err(&pdev->dev, ": failed to add character device\n");
189 return retval;
190 }
191
192 ec->vdev = device_create(cros_class, NULL, devno, ec,
193 CROS_EC_DEV_NAME);
194 if (IS_ERR(ec->vdev)) {
195 retval = PTR_ERR(ec->vdev);
196 dev_err(&pdev->dev, ": failed to create device\n");
197 cdev_del(&ec->cdev);
198 return retval;
199 }
200
201 /* Initialize extra interfaces */
202 ec_dev_sysfs_init(ec);
203 ec_dev_lightbar_init(ec);
204
205 return 0;
206}
207
208static int ec_device_remove(struct platform_device *pdev)
209{
210 struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
211
212 ec_dev_lightbar_remove(ec);
213 ec_dev_sysfs_remove(ec);
214 device_destroy(cros_class, MKDEV(ec_major, 0));
215 cdev_del(&ec->cdev);
216 return 0;
217}
218
219static struct platform_driver cros_ec_dev_driver = {
220 .driver = {
221 .name = "cros-ec-ctl",
222 },
223 .probe = ec_device_probe,
224 .remove = ec_device_remove,
225};
226
227static int __init cros_ec_dev_init(void)
228{
229 int ret;
230 dev_t dev = 0;
231
232 cros_class = class_create(THIS_MODULE, "chromeos");
233 if (IS_ERR(cros_class)) {
234 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
235 return PTR_ERR(cros_class);
236 }
237
238 /* Get a range of minor numbers (starting with 0) to work with */
239 ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME);
240 if (ret < 0) {
241 pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n");
242 goto failed_chrdevreg;
243 }
244 ec_major = MAJOR(dev);
245
246 /* Register the driver */
247 ret = platform_driver_register(&cros_ec_dev_driver);
248 if (ret < 0) {
249 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret);
250 goto failed_devreg;
251 }
252 return 0;
253
254failed_devreg:
255 unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV);
256failed_chrdevreg:
257 class_destroy(cros_class);
258 return ret;
259}
260
261static void __exit cros_ec_dev_exit(void)
262{
263 platform_driver_unregister(&cros_ec_dev_driver);
264 unregister_chrdev(ec_major, CROS_EC_DEV_NAME);
265 class_destroy(cros_class);
266}
267
268module_init(cros_ec_dev_init);
269module_exit(cros_ec_dev_exit);
270
271MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>");
272MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller");
273MODULE_VERSION("1.0");
274MODULE_LICENSE("GPL");