diff options
Diffstat (limited to 'drivers/platform/chrome/cros_ec_dev.c')
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.c | 274 |
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 | ||
29 | static struct class *cros_class; | ||
30 | static int ec_major; | ||
31 | |||
32 | /* Basic communication */ | ||
33 | static 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 */ | ||
72 | static 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 | |||
79 | static int ec_device_release(struct inode *inode, struct file *filp) | ||
80 | { | ||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | static 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 */ | ||
110 | static 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 | |||
129 | static 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 | |||
151 | static 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 */ | ||
170 | static 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 | |||
177 | static 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 | |||
208 | static 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 | |||
219 | static 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 | |||
227 | static 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 | |||
254 | failed_devreg: | ||
255 | unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); | ||
256 | failed_chrdevreg: | ||
257 | class_destroy(cros_class); | ||
258 | return ret; | ||
259 | } | ||
260 | |||
261 | static 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 | |||
268 | module_init(cros_ec_dev_init); | ||
269 | module_exit(cros_ec_dev_exit); | ||
270 | |||
271 | MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); | ||
272 | MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); | ||
273 | MODULE_VERSION("1.0"); | ||
274 | MODULE_LICENSE("GPL"); | ||