Commit | Line | Data |
---|---|---|
e7c256fb BR |
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> | |
5668bfdd | 21 | #include <linux/mfd/core.h> |
e7c256fb BR |
22 | #include <linux/module.h> |
23 | #include <linux/platform_device.h> | |
405c8430 | 24 | #include <linux/pm.h> |
a8411784 | 25 | #include <linux/slab.h> |
e7c256fb BR |
26 | #include <linux/uaccess.h> |
27 | ||
28 | #include "cros_ec_dev.h" | |
29 | ||
ea01a31b TE |
30 | #define DRV_NAME "cros-ec-dev" |
31 | ||
e7c256fb BR |
32 | /* Device variables */ |
33 | #define CROS_MAX_DEV 128 | |
e7c256fb BR |
34 | static int ec_major; |
35 | ||
57b33ff0 GG |
36 | static const struct attribute_group *cros_ec_groups[] = { |
37 | &cros_ec_attr_group, | |
38 | &cros_ec_lightbar_attr_group, | |
18800fc7 | 39 | &cros_ec_vbc_attr_group, |
57b33ff0 GG |
40 | NULL, |
41 | }; | |
42 | ||
43 | static struct class cros_class = { | |
44 | .owner = THIS_MODULE, | |
45 | .name = "chromeos", | |
46 | .dev_groups = cros_ec_groups, | |
47 | }; | |
48 | ||
e7c256fb | 49 | /* Basic communication */ |
57b33ff0 | 50 | static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen) |
e7c256fb BR |
51 | { |
52 | struct ec_response_get_version *resp; | |
53 | static const char * const current_image_name[] = { | |
54 | "unknown", "read-only", "read-write", "invalid", | |
55 | }; | |
a8411784 | 56 | struct cros_ec_command *msg; |
e7c256fb BR |
57 | int ret; |
58 | ||
a8411784 JMC |
59 | msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); |
60 | if (!msg) | |
61 | return -ENOMEM; | |
62 | ||
63 | msg->version = 0; | |
57b33ff0 | 64 | msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; |
a8411784 JMC |
65 | msg->insize = sizeof(*resp); |
66 | msg->outsize = 0; | |
67 | ||
57b33ff0 | 68 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
e7c256fb | 69 | if (ret < 0) |
a8411784 | 70 | goto exit; |
e7c256fb | 71 | |
a8411784 | 72 | if (msg->result != EC_RES_SUCCESS) { |
e7c256fb BR |
73 | snprintf(str, maxlen, |
74 | "%s\nUnknown EC version: EC returned %d\n", | |
a8411784 JMC |
75 | CROS_EC_DEV_VERSION, msg->result); |
76 | ret = -EINVAL; | |
77 | goto exit; | |
e7c256fb BR |
78 | } |
79 | ||
a8411784 | 80 | resp = (struct ec_response_get_version *)msg->data; |
e7c256fb BR |
81 | if (resp->current_image >= ARRAY_SIZE(current_image_name)) |
82 | resp->current_image = 3; /* invalid */ | |
83 | ||
ef59c25d | 84 | snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, |
e7c256fb BR |
85 | resp->version_string_ro, resp->version_string_rw, |
86 | current_image_name[resp->current_image]); | |
87 | ||
a8411784 JMC |
88 | ret = 0; |
89 | exit: | |
90 | kfree(msg); | |
91 | return ret; | |
e7c256fb BR |
92 | } |
93 | ||
e4244ebd VP |
94 | static int cros_ec_check_features(struct cros_ec_dev *ec, int feature) |
95 | { | |
96 | struct cros_ec_command *msg; | |
97 | int ret; | |
98 | ||
99 | if (ec->features[0] == -1U && ec->features[1] == -1U) { | |
100 | /* features bitmap not read yet */ | |
101 | ||
102 | msg = kmalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL); | |
103 | if (!msg) | |
104 | return -ENOMEM; | |
105 | ||
106 | msg->version = 0; | |
107 | msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset; | |
108 | msg->insize = sizeof(ec->features); | |
109 | msg->outsize = 0; | |
110 | ||
111 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); | |
112 | if (ret < 0 || msg->result != EC_RES_SUCCESS) { | |
113 | dev_warn(ec->dev, "cannot get EC features: %d/%d\n", | |
114 | ret, msg->result); | |
115 | memset(ec->features, 0, sizeof(ec->features)); | |
116 | } | |
117 | ||
118 | memcpy(ec->features, msg->data, sizeof(ec->features)); | |
119 | ||
120 | dev_dbg(ec->dev, "EC features %08x %08x\n", | |
121 | ec->features[0], ec->features[1]); | |
122 | ||
123 | kfree(msg); | |
124 | } | |
125 | ||
126 | return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature); | |
127 | } | |
128 | ||
e7c256fb BR |
129 | /* Device file ops */ |
130 | static int ec_device_open(struct inode *inode, struct file *filp) | |
131 | { | |
57b33ff0 GG |
132 | struct cros_ec_dev *ec = container_of(inode->i_cdev, |
133 | struct cros_ec_dev, cdev); | |
134 | filp->private_data = ec; | |
135 | nonseekable_open(inode, filp); | |
e7c256fb BR |
136 | return 0; |
137 | } | |
138 | ||
139 | static int ec_device_release(struct inode *inode, struct file *filp) | |
140 | { | |
141 | return 0; | |
142 | } | |
143 | ||
144 | static ssize_t ec_device_read(struct file *filp, char __user *buffer, | |
145 | size_t length, loff_t *offset) | |
146 | { | |
57b33ff0 | 147 | struct cros_ec_dev *ec = filp->private_data; |
e7c256fb BR |
148 | char msg[sizeof(struct ec_response_get_version) + |
149 | sizeof(CROS_EC_DEV_VERSION)]; | |
150 | size_t count; | |
151 | int ret; | |
152 | ||
153 | if (*offset != 0) | |
154 | return 0; | |
155 | ||
156 | ret = ec_get_version(ec, msg, sizeof(msg)); | |
157 | if (ret) | |
158 | return ret; | |
159 | ||
160 | count = min(length, strlen(msg)); | |
161 | ||
162 | if (copy_to_user(buffer, msg, count)) | |
163 | return -EFAULT; | |
164 | ||
165 | *offset = count; | |
166 | return count; | |
167 | } | |
168 | ||
169 | /* Ioctls */ | |
57b33ff0 | 170 | static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) |
e7c256fb BR |
171 | { |
172 | long ret; | |
a8411784 JMC |
173 | struct cros_ec_command u_cmd; |
174 | struct cros_ec_command *s_cmd; | |
e7c256fb | 175 | |
a8411784 | 176 | if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) |
e7c256fb BR |
177 | return -EFAULT; |
178 | ||
5d749d0b GG |
179 | if ((u_cmd.outsize > EC_MAX_MSG_BYTES) || |
180 | (u_cmd.insize > EC_MAX_MSG_BYTES)) | |
181 | return -EINVAL; | |
182 | ||
a8411784 JMC |
183 | s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), |
184 | GFP_KERNEL); | |
185 | if (!s_cmd) | |
186 | return -ENOMEM; | |
187 | ||
188 | if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) { | |
189 | ret = -EFAULT; | |
190 | goto exit; | |
191 | } | |
192 | ||
096cdc6f DC |
193 | if (u_cmd.outsize != s_cmd->outsize || |
194 | u_cmd.insize != s_cmd->insize) { | |
195 | ret = -EINVAL; | |
196 | goto exit; | |
197 | } | |
198 | ||
57b33ff0 GG |
199 | s_cmd->command += ec->cmd_offset; |
200 | ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); | |
e7c256fb BR |
201 | /* Only copy data to userland if data was received. */ |
202 | if (ret < 0) | |
a8411784 | 203 | goto exit; |
e7c256fb | 204 | |
096cdc6f | 205 | if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) |
a8411784 JMC |
206 | ret = -EFAULT; |
207 | exit: | |
208 | kfree(s_cmd); | |
209 | return ret; | |
e7c256fb BR |
210 | } |
211 | ||
57b33ff0 | 212 | static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg) |
e7c256fb | 213 | { |
57b33ff0 | 214 | struct cros_ec_device *ec_dev = ec->ec_dev; |
e7c256fb BR |
215 | struct cros_ec_readmem s_mem = { }; |
216 | long num; | |
217 | ||
218 | /* Not every platform supports direct reads */ | |
57b33ff0 | 219 | if (!ec_dev->cmd_readmem) |
e7c256fb BR |
220 | return -ENOTTY; |
221 | ||
222 | if (copy_from_user(&s_mem, arg, sizeof(s_mem))) | |
223 | return -EFAULT; | |
224 | ||
57b33ff0 GG |
225 | num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, |
226 | s_mem.buffer); | |
e7c256fb BR |
227 | if (num <= 0) |
228 | return num; | |
229 | ||
230 | if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) | |
231 | return -EFAULT; | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | static long ec_device_ioctl(struct file *filp, unsigned int cmd, | |
237 | unsigned long arg) | |
238 | { | |
57b33ff0 | 239 | struct cros_ec_dev *ec = filp->private_data; |
e7c256fb BR |
240 | |
241 | if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) | |
242 | return -ENOTTY; | |
243 | ||
244 | switch (cmd) { | |
245 | case CROS_EC_DEV_IOCXCMD: | |
246 | return ec_device_ioctl_xcmd(ec, (void __user *)arg); | |
247 | case CROS_EC_DEV_IOCRDMEM: | |
248 | return ec_device_ioctl_readmem(ec, (void __user *)arg); | |
249 | } | |
250 | ||
251 | return -ENOTTY; | |
252 | } | |
253 | ||
254 | /* Module initialization */ | |
255 | static const struct file_operations fops = { | |
256 | .open = ec_device_open, | |
257 | .release = ec_device_release, | |
258 | .read = ec_device_read, | |
259 | .unlocked_ioctl = ec_device_ioctl, | |
2521ea3e GR |
260 | #ifdef CONFIG_COMPAT |
261 | .compat_ioctl = ec_device_ioctl, | |
262 | #endif | |
e7c256fb BR |
263 | }; |
264 | ||
57b33ff0 GG |
265 | static void __remove(struct device *dev) |
266 | { | |
267 | struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, | |
268 | class_dev); | |
269 | kfree(ec); | |
270 | } | |
271 | ||
5668bfdd EBS |
272 | static void cros_ec_sensors_register(struct cros_ec_dev *ec) |
273 | { | |
274 | /* | |
275 | * Issue a command to get the number of sensor reported. | |
276 | * Build an array of sensors driver and register them all. | |
277 | */ | |
278 | int ret, i, id, sensor_num; | |
279 | struct mfd_cell *sensor_cells; | |
280 | struct cros_ec_sensor_platform *sensor_platforms; | |
281 | int sensor_type[MOTIONSENSE_TYPE_MAX]; | |
282 | struct ec_params_motion_sense *params; | |
283 | struct ec_response_motion_sense *resp; | |
284 | struct cros_ec_command *msg; | |
285 | ||
286 | msg = kzalloc(sizeof(struct cros_ec_command) + | |
287 | max(sizeof(*params), sizeof(*resp)), GFP_KERNEL); | |
288 | if (msg == NULL) | |
289 | return; | |
290 | ||
291 | msg->version = 2; | |
292 | msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; | |
293 | msg->outsize = sizeof(*params); | |
294 | msg->insize = sizeof(*resp); | |
295 | ||
296 | params = (struct ec_params_motion_sense *)msg->data; | |
297 | params->cmd = MOTIONSENSE_CMD_DUMP; | |
298 | ||
299 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); | |
300 | if (ret < 0 || msg->result != EC_RES_SUCCESS) { | |
301 | dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n", | |
302 | ret, msg->result); | |
303 | goto error; | |
304 | } | |
305 | ||
306 | resp = (struct ec_response_motion_sense *)msg->data; | |
307 | sensor_num = resp->dump.sensor_count; | |
308 | /* Allocate 2 extra sensors in case lid angle or FIFO are needed */ | |
309 | sensor_cells = kzalloc(sizeof(struct mfd_cell) * (sensor_num + 2), | |
310 | GFP_KERNEL); | |
311 | if (sensor_cells == NULL) | |
312 | goto error; | |
313 | ||
314 | sensor_platforms = kzalloc(sizeof(struct cros_ec_sensor_platform) * | |
315 | (sensor_num + 1), GFP_KERNEL); | |
316 | if (sensor_platforms == NULL) | |
317 | goto error_platforms; | |
318 | ||
319 | memset(sensor_type, 0, sizeof(sensor_type)); | |
320 | id = 0; | |
321 | for (i = 0; i < sensor_num; i++) { | |
322 | params->cmd = MOTIONSENSE_CMD_INFO; | |
323 | params->info.sensor_num = i; | |
324 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); | |
325 | if (ret < 0 || msg->result != EC_RES_SUCCESS) { | |
326 | dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n", | |
327 | i, ret, msg->result); | |
328 | continue; | |
329 | } | |
330 | switch (resp->info.type) { | |
331 | case MOTIONSENSE_TYPE_ACCEL: | |
332 | sensor_cells[id].name = "cros-ec-accel"; | |
333 | break; | |
d732248f GG |
334 | case MOTIONSENSE_TYPE_BARO: |
335 | sensor_cells[id].name = "cros-ec-baro"; | |
336 | break; | |
5668bfdd EBS |
337 | case MOTIONSENSE_TYPE_GYRO: |
338 | sensor_cells[id].name = "cros-ec-gyro"; | |
339 | break; | |
340 | case MOTIONSENSE_TYPE_MAG: | |
341 | sensor_cells[id].name = "cros-ec-mag"; | |
342 | break; | |
343 | case MOTIONSENSE_TYPE_PROX: | |
344 | sensor_cells[id].name = "cros-ec-prox"; | |
345 | break; | |
346 | case MOTIONSENSE_TYPE_LIGHT: | |
347 | sensor_cells[id].name = "cros-ec-light"; | |
348 | break; | |
349 | case MOTIONSENSE_TYPE_ACTIVITY: | |
350 | sensor_cells[id].name = "cros-ec-activity"; | |
351 | break; | |
352 | default: | |
353 | dev_warn(ec->dev, "unknown type %d\n", resp->info.type); | |
354 | continue; | |
355 | } | |
356 | sensor_platforms[id].sensor_num = i; | |
357 | sensor_cells[id].id = sensor_type[resp->info.type]; | |
358 | sensor_cells[id].platform_data = &sensor_platforms[id]; | |
359 | sensor_cells[id].pdata_size = | |
360 | sizeof(struct cros_ec_sensor_platform); | |
361 | ||
362 | sensor_type[resp->info.type]++; | |
363 | id++; | |
364 | } | |
365 | if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) { | |
366 | sensor_platforms[id].sensor_num = sensor_num; | |
367 | ||
368 | sensor_cells[id].name = "cros-ec-angle"; | |
369 | sensor_cells[id].id = 0; | |
370 | sensor_cells[id].platform_data = &sensor_platforms[id]; | |
371 | sensor_cells[id].pdata_size = | |
372 | sizeof(struct cros_ec_sensor_platform); | |
373 | id++; | |
374 | } | |
375 | if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { | |
376 | sensor_cells[id].name = "cros-ec-ring"; | |
377 | id++; | |
378 | } | |
379 | ||
380 | ret = mfd_add_devices(ec->dev, 0, sensor_cells, id, | |
381 | NULL, 0, NULL); | |
382 | if (ret) | |
383 | dev_err(ec->dev, "failed to add EC sensors\n"); | |
384 | ||
385 | kfree(sensor_platforms); | |
386 | error_platforms: | |
387 | kfree(sensor_cells); | |
388 | error: | |
389 | kfree(msg); | |
390 | } | |
391 | ||
e7c256fb BR |
392 | static int ec_device_probe(struct platform_device *pdev) |
393 | { | |
57b33ff0 GG |
394 | int retval = -ENOMEM; |
395 | struct device *dev = &pdev->dev; | |
396 | struct cros_ec_platform *ec_platform = dev_get_platdata(dev); | |
57b33ff0 GG |
397 | struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); |
398 | ||
399 | if (!ec) | |
400 | return retval; | |
e7c256fb | 401 | |
57b33ff0 GG |
402 | dev_set_drvdata(dev, ec); |
403 | ec->ec_dev = dev_get_drvdata(dev->parent); | |
404 | ec->dev = dev; | |
405 | ec->cmd_offset = ec_platform->cmd_offset; | |
e4244ebd VP |
406 | ec->features[0] = -1U; /* Not cached yet */ |
407 | ec->features[1] = -1U; /* Not cached yet */ | |
57b33ff0 | 408 | device_initialize(&ec->class_dev); |
e7c256fb BR |
409 | cdev_init(&ec->cdev, &fops); |
410 | ||
57b33ff0 GG |
411 | /* |
412 | * Add the class device | |
413 | * Link to the character device for creating the /dev entry | |
414 | * in devtmpfs. | |
415 | */ | |
1c1d152c | 416 | ec->class_dev.devt = MKDEV(ec_major, pdev->id); |
57b33ff0 GG |
417 | ec->class_dev.class = &cros_class; |
418 | ec->class_dev.parent = dev; | |
419 | ec->class_dev.release = __remove; | |
420 | ||
421 | retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); | |
422 | if (retval) { | |
423 | dev_err(dev, "dev_set_name failed => %d\n", retval); | |
1c1d152c | 424 | goto failed; |
e7c256fb BR |
425 | } |
426 | ||
1c1d152c | 427 | retval = cdev_device_add(&ec->cdev, &ec->class_dev); |
57b33ff0 | 428 | if (retval) { |
1c1d152c LG |
429 | dev_err(dev, "cdev_device_add failed => %d\n", retval); |
430 | goto failed; | |
57b33ff0 | 431 | } |
71af4b52 | 432 | |
e8626459 EC |
433 | if (cros_ec_debugfs_init(ec)) |
434 | dev_warn(dev, "failed to create debugfs directory\n"); | |
435 | ||
5668bfdd EBS |
436 | /* check whether this EC is a sensor hub. */ |
437 | if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) | |
438 | cros_ec_sensors_register(ec); | |
439 | ||
405c8430 | 440 | /* Take control of the lightbar from the EC. */ |
995c0ec9 | 441 | lb_manual_suspend_ctrl(ec, 1); |
405c8430 | 442 | |
e7c256fb | 443 | return 0; |
57b33ff0 | 444 | |
1c1d152c LG |
445 | failed: |
446 | put_device(&ec->class_dev); | |
57b33ff0 | 447 | return retval; |
e7c256fb BR |
448 | } |
449 | ||
450 | static int ec_device_remove(struct platform_device *pdev) | |
451 | { | |
57b33ff0 | 452 | struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); |
e8626459 | 453 | |
405c8430 | 454 | /* Let the EC take over the lightbar again. */ |
995c0ec9 | 455 | lb_manual_suspend_ctrl(ec, 0); |
405c8430 | 456 | |
e8626459 EC |
457 | cros_ec_debugfs_remove(ec); |
458 | ||
e7c256fb | 459 | cdev_del(&ec->cdev); |
57b33ff0 | 460 | device_unregister(&ec->class_dev); |
e7c256fb BR |
461 | return 0; |
462 | } | |
463 | ||
afbf8ec7 | 464 | static const struct platform_device_id cros_ec_id[] = { |
ea01a31b | 465 | { DRV_NAME, 0 }, |
afbf8ec7 JMC |
466 | { /* sentinel */ }, |
467 | }; | |
468 | MODULE_DEVICE_TABLE(platform, cros_ec_id); | |
469 | ||
5d6a312e | 470 | static __maybe_unused int ec_device_suspend(struct device *dev) |
405c8430 EC |
471 | { |
472 | struct cros_ec_dev *ec = dev_get_drvdata(dev); | |
473 | ||
995c0ec9 | 474 | lb_suspend(ec); |
405c8430 EC |
475 | |
476 | return 0; | |
477 | } | |
478 | ||
5d6a312e | 479 | static __maybe_unused int ec_device_resume(struct device *dev) |
405c8430 EC |
480 | { |
481 | struct cros_ec_dev *ec = dev_get_drvdata(dev); | |
482 | ||
995c0ec9 | 483 | lb_resume(ec); |
405c8430 EC |
484 | |
485 | return 0; | |
486 | } | |
487 | ||
488 | static const struct dev_pm_ops cros_ec_dev_pm_ops = { | |
489 | #ifdef CONFIG_PM_SLEEP | |
490 | .suspend = ec_device_suspend, | |
491 | .resume = ec_device_resume, | |
492 | #endif | |
493 | }; | |
494 | ||
e7c256fb BR |
495 | static struct platform_driver cros_ec_dev_driver = { |
496 | .driver = { | |
ea01a31b | 497 | .name = DRV_NAME, |
405c8430 | 498 | .pm = &cros_ec_dev_pm_ops, |
e7c256fb BR |
499 | }, |
500 | .probe = ec_device_probe, | |
501 | .remove = ec_device_remove, | |
502 | }; | |
503 | ||
504 | static int __init cros_ec_dev_init(void) | |
505 | { | |
506 | int ret; | |
507 | dev_t dev = 0; | |
508 | ||
57b33ff0 GG |
509 | ret = class_register(&cros_class); |
510 | if (ret) { | |
e7c256fb | 511 | pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); |
57b33ff0 | 512 | return ret; |
e7c256fb BR |
513 | } |
514 | ||
515 | /* Get a range of minor numbers (starting with 0) to work with */ | |
516 | ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); | |
517 | if (ret < 0) { | |
518 | pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); | |
519 | goto failed_chrdevreg; | |
520 | } | |
521 | ec_major = MAJOR(dev); | |
522 | ||
523 | /* Register the driver */ | |
524 | ret = platform_driver_register(&cros_ec_dev_driver); | |
525 | if (ret < 0) { | |
526 | pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); | |
527 | goto failed_devreg; | |
528 | } | |
529 | return 0; | |
530 | ||
531 | failed_devreg: | |
532 | unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); | |
533 | failed_chrdevreg: | |
57b33ff0 | 534 | class_unregister(&cros_class); |
e7c256fb BR |
535 | return ret; |
536 | } | |
537 | ||
538 | static void __exit cros_ec_dev_exit(void) | |
539 | { | |
540 | platform_driver_unregister(&cros_ec_dev_driver); | |
541 | unregister_chrdev(ec_major, CROS_EC_DEV_NAME); | |
57b33ff0 | 542 | class_unregister(&cros_class); |
e7c256fb BR |
543 | } |
544 | ||
545 | module_init(cros_ec_dev_init); | |
546 | module_exit(cros_ec_dev_exit); | |
547 | ||
ea01a31b | 548 | MODULE_ALIAS("platform:" DRV_NAME); |
e7c256fb BR |
549 | MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); |
550 | MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); | |
551 | MODULE_VERSION("1.0"); | |
552 | MODULE_LICENSE("GPL"); |