]>
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)); | |
df7c3bf2 SB |
116 | } else { |
117 | memcpy(ec->features, msg->data, sizeof(ec->features)); | |
e4244ebd VP |
118 | } |
119 | ||
e4244ebd VP |
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 | ||
5668bfdd EBS |
265 | static void cros_ec_sensors_register(struct cros_ec_dev *ec) |
266 | { | |
267 | /* | |
268 | * Issue a command to get the number of sensor reported. | |
269 | * Build an array of sensors driver and register them all. | |
270 | */ | |
271 | int ret, i, id, sensor_num; | |
272 | struct mfd_cell *sensor_cells; | |
273 | struct cros_ec_sensor_platform *sensor_platforms; | |
274 | int sensor_type[MOTIONSENSE_TYPE_MAX]; | |
275 | struct ec_params_motion_sense *params; | |
276 | struct ec_response_motion_sense *resp; | |
277 | struct cros_ec_command *msg; | |
278 | ||
279 | msg = kzalloc(sizeof(struct cros_ec_command) + | |
280 | max(sizeof(*params), sizeof(*resp)), GFP_KERNEL); | |
281 | if (msg == NULL) | |
282 | return; | |
283 | ||
284 | msg->version = 2; | |
285 | msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; | |
286 | msg->outsize = sizeof(*params); | |
287 | msg->insize = sizeof(*resp); | |
288 | ||
289 | params = (struct ec_params_motion_sense *)msg->data; | |
290 | params->cmd = MOTIONSENSE_CMD_DUMP; | |
291 | ||
292 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); | |
293 | if (ret < 0 || msg->result != EC_RES_SUCCESS) { | |
294 | dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n", | |
295 | ret, msg->result); | |
296 | goto error; | |
297 | } | |
298 | ||
299 | resp = (struct ec_response_motion_sense *)msg->data; | |
300 | sensor_num = resp->dump.sensor_count; | |
c1d1e91a | 301 | /* Allocate 1 extra sensors in FIFO are needed */ |
6396bb22 | 302 | sensor_cells = kcalloc(sensor_num + 1, sizeof(struct mfd_cell), |
5668bfdd EBS |
303 | GFP_KERNEL); |
304 | if (sensor_cells == NULL) | |
305 | goto error; | |
306 | ||
6396bb22 KC |
307 | sensor_platforms = kcalloc(sensor_num + 1, |
308 | sizeof(struct cros_ec_sensor_platform), | |
309 | GFP_KERNEL); | |
5668bfdd EBS |
310 | if (sensor_platforms == NULL) |
311 | goto error_platforms; | |
312 | ||
313 | memset(sensor_type, 0, sizeof(sensor_type)); | |
314 | id = 0; | |
315 | for (i = 0; i < sensor_num; i++) { | |
316 | params->cmd = MOTIONSENSE_CMD_INFO; | |
317 | params->info.sensor_num = i; | |
318 | ret = cros_ec_cmd_xfer(ec->ec_dev, msg); | |
319 | if (ret < 0 || msg->result != EC_RES_SUCCESS) { | |
320 | dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n", | |
321 | i, ret, msg->result); | |
322 | continue; | |
323 | } | |
324 | switch (resp->info.type) { | |
325 | case MOTIONSENSE_TYPE_ACCEL: | |
326 | sensor_cells[id].name = "cros-ec-accel"; | |
327 | break; | |
d732248f GG |
328 | case MOTIONSENSE_TYPE_BARO: |
329 | sensor_cells[id].name = "cros-ec-baro"; | |
330 | break; | |
5668bfdd EBS |
331 | case MOTIONSENSE_TYPE_GYRO: |
332 | sensor_cells[id].name = "cros-ec-gyro"; | |
333 | break; | |
334 | case MOTIONSENSE_TYPE_MAG: | |
335 | sensor_cells[id].name = "cros-ec-mag"; | |
336 | break; | |
337 | case MOTIONSENSE_TYPE_PROX: | |
338 | sensor_cells[id].name = "cros-ec-prox"; | |
339 | break; | |
340 | case MOTIONSENSE_TYPE_LIGHT: | |
341 | sensor_cells[id].name = "cros-ec-light"; | |
342 | break; | |
343 | case MOTIONSENSE_TYPE_ACTIVITY: | |
344 | sensor_cells[id].name = "cros-ec-activity"; | |
345 | break; | |
346 | default: | |
347 | dev_warn(ec->dev, "unknown type %d\n", resp->info.type); | |
348 | continue; | |
349 | } | |
350 | sensor_platforms[id].sensor_num = i; | |
351 | sensor_cells[id].id = sensor_type[resp->info.type]; | |
352 | sensor_cells[id].platform_data = &sensor_platforms[id]; | |
353 | sensor_cells[id].pdata_size = | |
354 | sizeof(struct cros_ec_sensor_platform); | |
355 | ||
356 | sensor_type[resp->info.type]++; | |
357 | id++; | |
358 | } | |
5668bfdd | 359 | |
c1d1e91a GG |
360 | if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) |
361 | ec->has_kb_wake_angle = true; | |
362 | ||
5668bfdd EBS |
363 | if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { |
364 | sensor_cells[id].name = "cros-ec-ring"; | |
365 | id++; | |
366 | } | |
367 | ||
368 | ret = mfd_add_devices(ec->dev, 0, sensor_cells, id, | |
369 | NULL, 0, NULL); | |
370 | if (ret) | |
371 | dev_err(ec->dev, "failed to add EC sensors\n"); | |
372 | ||
373 | kfree(sensor_platforms); | |
374 | error_platforms: | |
375 | kfree(sensor_cells); | |
376 | error: | |
377 | kfree(msg); | |
378 | } | |
379 | ||
95a4d07f EBS |
380 | static const struct mfd_cell cros_ec_rtc_cells[] = { |
381 | { .name = "cros-ec-rtc" } | |
382 | }; | |
383 | ||
e7c256fb BR |
384 | static int ec_device_probe(struct platform_device *pdev) |
385 | { | |
57b33ff0 GG |
386 | int retval = -ENOMEM; |
387 | struct device *dev = &pdev->dev; | |
388 | struct cros_ec_platform *ec_platform = dev_get_platdata(dev); | |
3aa2177e | 389 | struct cros_ec_dev *ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); |
57b33ff0 GG |
390 | |
391 | if (!ec) | |
392 | return retval; | |
e7c256fb | 393 | |
57b33ff0 GG |
394 | dev_set_drvdata(dev, ec); |
395 | ec->ec_dev = dev_get_drvdata(dev->parent); | |
396 | ec->dev = dev; | |
397 | ec->cmd_offset = ec_platform->cmd_offset; | |
e4244ebd VP |
398 | ec->features[0] = -1U; /* Not cached yet */ |
399 | ec->features[1] = -1U; /* Not cached yet */ | |
57b33ff0 | 400 | device_initialize(&ec->class_dev); |
e7c256fb BR |
401 | cdev_init(&ec->cdev, &fops); |
402 | ||
57b33ff0 GG |
403 | /* |
404 | * Add the class device | |
405 | * Link to the character device for creating the /dev entry | |
406 | * in devtmpfs. | |
407 | */ | |
1c1d152c | 408 | ec->class_dev.devt = MKDEV(ec_major, pdev->id); |
57b33ff0 GG |
409 | ec->class_dev.class = &cros_class; |
410 | ec->class_dev.parent = dev; | |
57b33ff0 GG |
411 | |
412 | retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); | |
413 | if (retval) { | |
414 | dev_err(dev, "dev_set_name failed => %d\n", retval); | |
1c1d152c | 415 | goto failed; |
e7c256fb BR |
416 | } |
417 | ||
c1d1e91a GG |
418 | /* check whether this EC is a sensor hub. */ |
419 | if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) | |
420 | cros_ec_sensors_register(ec); | |
421 | ||
95a4d07f EBS |
422 | /* Check whether this EC instance has RTC host command support */ |
423 | if (cros_ec_check_features(ec, EC_FEATURE_RTC)) { | |
424 | retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, | |
425 | cros_ec_rtc_cells, | |
426 | ARRAY_SIZE(cros_ec_rtc_cells), | |
427 | NULL, 0, NULL); | |
428 | if (retval) | |
429 | dev_err(ec->dev, | |
430 | "failed to add cros-ec-rtc device: %d\n", | |
431 | retval); | |
432 | } | |
433 | ||
c1d1e91a GG |
434 | /* Take control of the lightbar from the EC. */ |
435 | lb_manual_suspend_ctrl(ec, 1); | |
436 | ||
437 | /* We can now add the sysfs class, we know which parameter to show */ | |
1c1d152c | 438 | retval = cdev_device_add(&ec->cdev, &ec->class_dev); |
57b33ff0 | 439 | if (retval) { |
1c1d152c LG |
440 | dev_err(dev, "cdev_device_add failed => %d\n", retval); |
441 | goto failed; | |
57b33ff0 | 442 | } |
71af4b52 | 443 | |
e8626459 EC |
444 | if (cros_ec_debugfs_init(ec)) |
445 | dev_warn(dev, "failed to create debugfs directory\n"); | |
446 | ||
e7c256fb | 447 | return 0; |
57b33ff0 | 448 | |
1c1d152c LG |
449 | failed: |
450 | put_device(&ec->class_dev); | |
57b33ff0 | 451 | return retval; |
e7c256fb BR |
452 | } |
453 | ||
454 | static int ec_device_remove(struct platform_device *pdev) | |
455 | { | |
57b33ff0 | 456 | struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); |
e8626459 | 457 | |
405c8430 | 458 | /* Let the EC take over the lightbar again. */ |
995c0ec9 | 459 | lb_manual_suspend_ctrl(ec, 0); |
405c8430 | 460 | |
e8626459 EC |
461 | cros_ec_debugfs_remove(ec); |
462 | ||
e7c256fb | 463 | cdev_del(&ec->cdev); |
57b33ff0 | 464 | device_unregister(&ec->class_dev); |
e7c256fb BR |
465 | return 0; |
466 | } | |
467 | ||
e15b7f44 DHW |
468 | static void ec_device_shutdown(struct platform_device *pdev) |
469 | { | |
470 | struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); | |
471 | ||
472 | /* Be sure to clear up debugfs delayed works */ | |
473 | cros_ec_debugfs_remove(ec); | |
474 | } | |
475 | ||
afbf8ec7 | 476 | static const struct platform_device_id cros_ec_id[] = { |
ea01a31b | 477 | { DRV_NAME, 0 }, |
abeed71b | 478 | { /* sentinel */ } |
afbf8ec7 JMC |
479 | }; |
480 | MODULE_DEVICE_TABLE(platform, cros_ec_id); | |
481 | ||
5d6a312e | 482 | static __maybe_unused int ec_device_suspend(struct device *dev) |
405c8430 EC |
483 | { |
484 | struct cros_ec_dev *ec = dev_get_drvdata(dev); | |
485 | ||
44d99d73 DA |
486 | cros_ec_debugfs_suspend(ec); |
487 | ||
995c0ec9 | 488 | lb_suspend(ec); |
405c8430 EC |
489 | |
490 | return 0; | |
491 | } | |
492 | ||
5d6a312e | 493 | static __maybe_unused int ec_device_resume(struct device *dev) |
405c8430 EC |
494 | { |
495 | struct cros_ec_dev *ec = dev_get_drvdata(dev); | |
496 | ||
44d99d73 DA |
497 | cros_ec_debugfs_resume(ec); |
498 | ||
995c0ec9 | 499 | lb_resume(ec); |
405c8430 EC |
500 | |
501 | return 0; | |
502 | } | |
503 | ||
504 | static const struct dev_pm_ops cros_ec_dev_pm_ops = { | |
505 | #ifdef CONFIG_PM_SLEEP | |
506 | .suspend = ec_device_suspend, | |
507 | .resume = ec_device_resume, | |
508 | #endif | |
509 | }; | |
510 | ||
e7c256fb BR |
511 | static struct platform_driver cros_ec_dev_driver = { |
512 | .driver = { | |
ea01a31b | 513 | .name = DRV_NAME, |
405c8430 | 514 | .pm = &cros_ec_dev_pm_ops, |
e7c256fb BR |
515 | }, |
516 | .probe = ec_device_probe, | |
517 | .remove = ec_device_remove, | |
e15b7f44 | 518 | .shutdown = ec_device_shutdown, |
e7c256fb BR |
519 | }; |
520 | ||
521 | static int __init cros_ec_dev_init(void) | |
522 | { | |
523 | int ret; | |
524 | dev_t dev = 0; | |
525 | ||
57b33ff0 GG |
526 | ret = class_register(&cros_class); |
527 | if (ret) { | |
e7c256fb | 528 | pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); |
57b33ff0 | 529 | return ret; |
e7c256fb BR |
530 | } |
531 | ||
532 | /* Get a range of minor numbers (starting with 0) to work with */ | |
533 | ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); | |
534 | if (ret < 0) { | |
535 | pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); | |
536 | goto failed_chrdevreg; | |
537 | } | |
538 | ec_major = MAJOR(dev); | |
539 | ||
540 | /* Register the driver */ | |
541 | ret = platform_driver_register(&cros_ec_dev_driver); | |
542 | if (ret < 0) { | |
543 | pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); | |
544 | goto failed_devreg; | |
545 | } | |
546 | return 0; | |
547 | ||
548 | failed_devreg: | |
549 | unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); | |
550 | failed_chrdevreg: | |
57b33ff0 | 551 | class_unregister(&cros_class); |
e7c256fb BR |
552 | return ret; |
553 | } | |
554 | ||
555 | static void __exit cros_ec_dev_exit(void) | |
556 | { | |
557 | platform_driver_unregister(&cros_ec_dev_driver); | |
558 | unregister_chrdev(ec_major, CROS_EC_DEV_NAME); | |
57b33ff0 | 559 | class_unregister(&cros_class); |
e7c256fb BR |
560 | } |
561 | ||
562 | module_init(cros_ec_dev_init); | |
563 | module_exit(cros_ec_dev_exit); | |
564 | ||
ea01a31b | 565 | MODULE_ALIAS("platform:" DRV_NAME); |
e7c256fb BR |
566 | MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); |
567 | MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); | |
568 | MODULE_VERSION("1.0"); | |
569 | MODULE_LICENSE("GPL"); |