diff options
Diffstat (limited to 'drivers/spi/spidev.c')
-rw-r--r-- | drivers/spi/spidev.c | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c new file mode 100644 index 000000000000..c0a6dce800a3 --- /dev/null +++ b/drivers/spi/spidev.c | |||
@@ -0,0 +1,584 @@ | |||
1 | /* | ||
2 | * spidev.c -- simple synchronous userspace interface to SPI devices | ||
3 | * | ||
4 | * Copyright (C) 2006 SWAPP | ||
5 | * Andrea Paterniani <a.paterniani@swapp-eng.it> | ||
6 | * Copyright (C) 2007 David Brownell (simplification, cleanup) | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/init.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/ioctl.h> | ||
26 | #include <linux/fs.h> | ||
27 | #include <linux/device.h> | ||
28 | #include <linux/list.h> | ||
29 | #include <linux/errno.h> | ||
30 | #include <linux/mutex.h> | ||
31 | #include <linux/slab.h> | ||
32 | |||
33 | #include <linux/spi/spi.h> | ||
34 | #include <linux/spi/spidev.h> | ||
35 | |||
36 | #include <asm/uaccess.h> | ||
37 | |||
38 | |||
39 | /* | ||
40 | * This supports acccess to SPI devices using normal userspace I/O calls. | ||
41 | * Note that while traditional UNIX/POSIX I/O semantics are half duplex, | ||
42 | * and often mask message boundaries, full SPI support requires full duplex | ||
43 | * transfers. There are several kinds of of internal message boundaries to | ||
44 | * handle chipselect management and other protocol options. | ||
45 | * | ||
46 | * SPI has a character major number assigned. We allocate minor numbers | ||
47 | * dynamically using a bitmask. You must use hotplug tools, such as udev | ||
48 | * (or mdev with busybox) to create and destroy the /dev/spidevB.C device | ||
49 | * nodes, since there is no fixed association of minor numbers with any | ||
50 | * particular SPI bus or device. | ||
51 | */ | ||
52 | #define SPIDEV_MAJOR 153 /* assigned */ | ||
53 | #define N_SPI_MINORS 32 /* ... up to 256 */ | ||
54 | |||
55 | static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; | ||
56 | |||
57 | |||
58 | /* Bit masks for spi_device.mode management */ | ||
59 | #define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL) | ||
60 | |||
61 | |||
62 | struct spidev_data { | ||
63 | struct device dev; | ||
64 | struct spi_device *spi; | ||
65 | struct list_head device_entry; | ||
66 | |||
67 | struct mutex buf_lock; | ||
68 | unsigned users; | ||
69 | u8 *buffer; | ||
70 | }; | ||
71 | |||
72 | static LIST_HEAD(device_list); | ||
73 | static DEFINE_MUTEX(device_list_lock); | ||
74 | |||
75 | static unsigned bufsiz = 4096; | ||
76 | module_param(bufsiz, uint, S_IRUGO); | ||
77 | MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); | ||
78 | |||
79 | /*-------------------------------------------------------------------------*/ | ||
80 | |||
81 | /* Read-only message with current device setup */ | ||
82 | static ssize_t | ||
83 | spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) | ||
84 | { | ||
85 | struct spidev_data *spidev; | ||
86 | struct spi_device *spi; | ||
87 | ssize_t status = 0; | ||
88 | |||
89 | /* chipselect only toggles at start or end of operation */ | ||
90 | if (count > bufsiz) | ||
91 | return -EMSGSIZE; | ||
92 | |||
93 | spidev = filp->private_data; | ||
94 | spi = spidev->spi; | ||
95 | |||
96 | mutex_lock(&spidev->buf_lock); | ||
97 | status = spi_read(spi, spidev->buffer, count); | ||
98 | if (status == 0) { | ||
99 | unsigned long missing; | ||
100 | |||
101 | missing = copy_to_user(buf, spidev->buffer, count); | ||
102 | if (count && missing == count) | ||
103 | status = -EFAULT; | ||
104 | else | ||
105 | status = count - missing; | ||
106 | } | ||
107 | mutex_unlock(&spidev->buf_lock); | ||
108 | |||
109 | return status; | ||
110 | } | ||
111 | |||
112 | /* Write-only message with current device setup */ | ||
113 | static ssize_t | ||
114 | spidev_write(struct file *filp, const char __user *buf, | ||
115 | size_t count, loff_t *f_pos) | ||
116 | { | ||
117 | struct spidev_data *spidev; | ||
118 | struct spi_device *spi; | ||
119 | ssize_t status = 0; | ||
120 | unsigned long missing; | ||
121 | |||
122 | /* chipselect only toggles at start or end of operation */ | ||
123 | if (count > bufsiz) | ||
124 | return -EMSGSIZE; | ||
125 | |||
126 | spidev = filp->private_data; | ||
127 | spi = spidev->spi; | ||
128 | |||
129 | mutex_lock(&spidev->buf_lock); | ||
130 | missing = copy_from_user(spidev->buffer, buf, count); | ||
131 | if (missing == 0) { | ||
132 | status = spi_write(spi, spidev->buffer, count); | ||
133 | if (status == 0) | ||
134 | status = count; | ||
135 | } else | ||
136 | status = -EFAULT; | ||
137 | mutex_unlock(&spidev->buf_lock); | ||
138 | |||
139 | return status; | ||
140 | } | ||
141 | |||
142 | static int spidev_message(struct spidev_data *spidev, | ||
143 | struct spi_ioc_transfer *u_xfers, unsigned n_xfers) | ||
144 | { | ||
145 | struct spi_message msg; | ||
146 | struct spi_transfer *k_xfers; | ||
147 | struct spi_transfer *k_tmp; | ||
148 | struct spi_ioc_transfer *u_tmp; | ||
149 | struct spi_device *spi = spidev->spi; | ||
150 | unsigned n, total; | ||
151 | u8 *buf; | ||
152 | int status = -EFAULT; | ||
153 | |||
154 | spi_message_init(&msg); | ||
155 | k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); | ||
156 | if (k_xfers == NULL) | ||
157 | return -ENOMEM; | ||
158 | |||
159 | /* Construct spi_message, copying any tx data to bounce buffer. | ||
160 | * We walk the array of user-provided transfers, using each one | ||
161 | * to initialize a kernel version of the same transfer. | ||
162 | */ | ||
163 | mutex_lock(&spidev->buf_lock); | ||
164 | buf = spidev->buffer; | ||
165 | total = 0; | ||
166 | for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; | ||
167 | n; | ||
168 | n--, k_tmp++, u_tmp++) { | ||
169 | k_tmp->len = u_tmp->len; | ||
170 | |||
171 | if (u_tmp->rx_buf) { | ||
172 | k_tmp->rx_buf = buf; | ||
173 | if (!access_ok(VERIFY_WRITE, u_tmp->rx_buf, u_tmp->len)) | ||
174 | goto done; | ||
175 | } | ||
176 | if (u_tmp->tx_buf) { | ||
177 | k_tmp->tx_buf = buf; | ||
178 | if (copy_from_user(buf, (const u8 __user *)u_tmp->tx_buf, | ||
179 | u_tmp->len)) | ||
180 | goto done; | ||
181 | } | ||
182 | |||
183 | total += k_tmp->len; | ||
184 | if (total > bufsiz) { | ||
185 | status = -EMSGSIZE; | ||
186 | goto done; | ||
187 | } | ||
188 | buf += k_tmp->len; | ||
189 | |||
190 | k_tmp->cs_change = !!u_tmp->cs_change; | ||
191 | k_tmp->bits_per_word = u_tmp->bits_per_word; | ||
192 | k_tmp->delay_usecs = u_tmp->delay_usecs; | ||
193 | k_tmp->speed_hz = u_tmp->speed_hz; | ||
194 | #ifdef VERBOSE | ||
195 | dev_dbg(&spi->dev, | ||
196 | " xfer len %zd %s%s%s%dbits %u usec %uHz\n", | ||
197 | u_tmp->len, | ||
198 | u_tmp->rx_buf ? "rx " : "", | ||
199 | u_tmp->tx_buf ? "tx " : "", | ||
200 | u_tmp->cs_change ? "cs " : "", | ||
201 | u_tmp->bits_per_word ? : spi->bits_per_word, | ||
202 | u_tmp->delay_usecs, | ||
203 | u_tmp->speed_hz ? : spi->max_speed_hz); | ||
204 | #endif | ||
205 | spi_message_add_tail(k_tmp, &msg); | ||
206 | } | ||
207 | |||
208 | status = spi_sync(spi, &msg); | ||
209 | if (status < 0) | ||
210 | goto done; | ||
211 | |||
212 | /* copy any rx data out of bounce buffer */ | ||
213 | buf = spidev->buffer; | ||
214 | for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { | ||
215 | if (u_tmp->rx_buf) { | ||
216 | if (__copy_to_user((u8 __user *)u_tmp->rx_buf, buf, | ||
217 | u_tmp->len)) { | ||
218 | status = -EFAULT; | ||
219 | goto done; | ||
220 | } | ||
221 | } | ||
222 | buf += u_tmp->len; | ||
223 | } | ||
224 | status = total; | ||
225 | |||
226 | done: | ||
227 | mutex_unlock(&spidev->buf_lock); | ||
228 | kfree(k_xfers); | ||
229 | return status; | ||
230 | } | ||
231 | |||
232 | static int | ||
233 | spidev_ioctl(struct inode *inode, struct file *filp, | ||
234 | unsigned int cmd, unsigned long arg) | ||
235 | { | ||
236 | int err = 0; | ||
237 | int retval = 0; | ||
238 | struct spidev_data *spidev; | ||
239 | struct spi_device *spi; | ||
240 | u32 tmp; | ||
241 | unsigned n_ioc; | ||
242 | struct spi_ioc_transfer *ioc; | ||
243 | |||
244 | /* Check type and command number */ | ||
245 | if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) | ||
246 | return -ENOTTY; | ||
247 | |||
248 | /* Check access direction once here; don't repeat below. | ||
249 | * IOC_DIR is from the user perspective, while access_ok is | ||
250 | * from the kernel perspective; so they look reversed. | ||
251 | */ | ||
252 | if (_IOC_DIR(cmd) & _IOC_READ) | ||
253 | err = !access_ok(VERIFY_WRITE, | ||
254 | (void __user *)arg, _IOC_SIZE(cmd)); | ||
255 | if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) | ||
256 | err = !access_ok(VERIFY_READ, | ||
257 | (void __user *)arg, _IOC_SIZE(cmd)); | ||
258 | if (err) | ||
259 | return -EFAULT; | ||
260 | |||
261 | spidev = filp->private_data; | ||
262 | spi = spidev->spi; | ||
263 | |||
264 | switch (cmd) { | ||
265 | /* read requests */ | ||
266 | case SPI_IOC_RD_MODE: | ||
267 | retval = __put_user(spi->mode & SPI_MODE_MASK, | ||
268 | (__u8 __user *)arg); | ||
269 | break; | ||
270 | case SPI_IOC_RD_LSB_FIRST: | ||
271 | retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, | ||
272 | (__u8 __user *)arg); | ||
273 | break; | ||
274 | case SPI_IOC_RD_BITS_PER_WORD: | ||
275 | retval = __put_user(spi->bits_per_word, (__u8 __user *)arg); | ||
276 | break; | ||
277 | case SPI_IOC_RD_MAX_SPEED_HZ: | ||
278 | retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg); | ||
279 | break; | ||
280 | |||
281 | /* write requests */ | ||
282 | case SPI_IOC_WR_MODE: | ||
283 | retval = __get_user(tmp, (u8 __user *)arg); | ||
284 | if (retval == 0) { | ||
285 | u8 save = spi->mode; | ||
286 | |||
287 | if (tmp & ~SPI_MODE_MASK) { | ||
288 | retval = -EINVAL; | ||
289 | break; | ||
290 | } | ||
291 | |||
292 | tmp |= spi->mode & ~SPI_MODE_MASK; | ||
293 | spi->mode = (u8)tmp; | ||
294 | retval = spi_setup(spi); | ||
295 | if (retval < 0) | ||
296 | spi->mode = save; | ||
297 | else | ||
298 | dev_dbg(&spi->dev, "spi mode %02x\n", tmp); | ||
299 | } | ||
300 | break; | ||
301 | case SPI_IOC_WR_LSB_FIRST: | ||
302 | retval = __get_user(tmp, (__u8 __user *)arg); | ||
303 | if (retval == 0) { | ||
304 | u8 save = spi->mode; | ||
305 | |||
306 | if (tmp) | ||
307 | spi->mode |= SPI_LSB_FIRST; | ||
308 | else | ||
309 | spi->mode &= ~SPI_LSB_FIRST; | ||
310 | retval = spi_setup(spi); | ||
311 | if (retval < 0) | ||
312 | spi->mode = save; | ||
313 | else | ||
314 | dev_dbg(&spi->dev, "%csb first\n", | ||
315 | tmp ? 'l' : 'm'); | ||
316 | } | ||
317 | break; | ||
318 | case SPI_IOC_WR_BITS_PER_WORD: | ||
319 | retval = __get_user(tmp, (__u8 __user *)arg); | ||
320 | if (retval == 0) { | ||
321 | u8 save = spi->bits_per_word; | ||
322 | |||
323 | spi->bits_per_word = tmp; | ||
324 | retval = spi_setup(spi); | ||
325 | if (retval < 0) | ||
326 | spi->bits_per_word = save; | ||
327 | else | ||
328 | dev_dbg(&spi->dev, "%d bits per word\n", tmp); | ||
329 | } | ||
330 | break; | ||
331 | case SPI_IOC_WR_MAX_SPEED_HZ: | ||
332 | retval = __get_user(tmp, (__u32 __user *)arg); | ||
333 | if (retval == 0) { | ||
334 | u32 save = spi->max_speed_hz; | ||
335 | |||
336 | spi->max_speed_hz = tmp; | ||
337 | retval = spi_setup(spi); | ||
338 | if (retval < 0) | ||
339 | spi->max_speed_hz = save; | ||
340 | else | ||
341 | dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); | ||
342 | } | ||
343 | break; | ||
344 | |||
345 | default: | ||
346 | /* segmented and/or full-duplex I/O request */ | ||
347 | if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) | ||
348 | || _IOC_DIR(cmd) != _IOC_WRITE) | ||
349 | return -ENOTTY; | ||
350 | |||
351 | tmp = _IOC_SIZE(cmd); | ||
352 | if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { | ||
353 | retval = -EINVAL; | ||
354 | break; | ||
355 | } | ||
356 | n_ioc = tmp / sizeof(struct spi_ioc_transfer); | ||
357 | if (n_ioc == 0) | ||
358 | break; | ||
359 | |||
360 | /* copy into scratch area */ | ||
361 | ioc = kmalloc(tmp, GFP_KERNEL); | ||
362 | if (!ioc) { | ||
363 | retval = -ENOMEM; | ||
364 | break; | ||
365 | } | ||
366 | if (__copy_from_user(ioc, (void __user *)arg, tmp)) { | ||
367 | retval = -EFAULT; | ||
368 | break; | ||
369 | } | ||
370 | |||
371 | /* translate to spi_message, execute */ | ||
372 | retval = spidev_message(spidev, ioc, n_ioc); | ||
373 | kfree(ioc); | ||
374 | break; | ||
375 | } | ||
376 | return retval; | ||
377 | } | ||
378 | |||
379 | static int spidev_open(struct inode *inode, struct file *filp) | ||
380 | { | ||
381 | struct spidev_data *spidev; | ||
382 | int status = -ENXIO; | ||
383 | |||
384 | mutex_lock(&device_list_lock); | ||
385 | |||
386 | list_for_each_entry(spidev, &device_list, device_entry) { | ||
387 | if (spidev->dev.devt == inode->i_rdev) { | ||
388 | status = 0; | ||
389 | break; | ||
390 | } | ||
391 | } | ||
392 | if (status == 0) { | ||
393 | if (!spidev->buffer) { | ||
394 | spidev->buffer = kmalloc(bufsiz, GFP_KERNEL); | ||
395 | if (!spidev->buffer) { | ||
396 | dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); | ||
397 | status = -ENOMEM; | ||
398 | } | ||
399 | } | ||
400 | if (status == 0) { | ||
401 | spidev->users++; | ||
402 | filp->private_data = spidev; | ||
403 | nonseekable_open(inode, filp); | ||
404 | } | ||
405 | } else | ||
406 | pr_debug("spidev: nothing for minor %d\n", iminor(inode)); | ||
407 | |||
408 | mutex_unlock(&device_list_lock); | ||
409 | return status; | ||
410 | } | ||
411 | |||
412 | static int spidev_release(struct inode *inode, struct file *filp) | ||
413 | { | ||
414 | struct spidev_data *spidev; | ||
415 | int status = 0; | ||
416 | |||
417 | mutex_lock(&device_list_lock); | ||
418 | spidev = filp->private_data; | ||
419 | filp->private_data = NULL; | ||
420 | spidev->users--; | ||
421 | if (!spidev->users) { | ||
422 | kfree(spidev->buffer); | ||
423 | spidev->buffer = NULL; | ||
424 | } | ||
425 | mutex_unlock(&device_list_lock); | ||
426 | |||
427 | return status; | ||
428 | } | ||
429 | |||
430 | static struct file_operations spidev_fops = { | ||
431 | .owner = THIS_MODULE, | ||
432 | /* REVISIT switch to aio primitives, so that userspace | ||
433 | * gets more complete API coverage. It'll simplify things | ||
434 | * too, except for the locking. | ||
435 | */ | ||
436 | .write = spidev_write, | ||
437 | .read = spidev_read, | ||
438 | .ioctl = spidev_ioctl, | ||
439 | .open = spidev_open, | ||
440 | .release = spidev_release, | ||
441 | }; | ||
442 | |||
443 | /*-------------------------------------------------------------------------*/ | ||
444 | |||
445 | /* The main reason to have this class is to make mdev/udev create the | ||
446 | * /dev/spidevB.C character device nodes exposing our userspace API. | ||
447 | * It also simplifies memory management. | ||
448 | */ | ||
449 | |||
450 | static void spidev_classdev_release(struct device *dev) | ||
451 | { | ||
452 | struct spidev_data *spidev; | ||
453 | |||
454 | spidev = container_of(dev, struct spidev_data, dev); | ||
455 | kfree(spidev); | ||
456 | } | ||
457 | |||
458 | static struct class spidev_class = { | ||
459 | .name = "spidev", | ||
460 | .owner = THIS_MODULE, | ||
461 | .dev_release = spidev_classdev_release, | ||
462 | }; | ||
463 | |||
464 | /*-------------------------------------------------------------------------*/ | ||
465 | |||
466 | static int spidev_probe(struct spi_device *spi) | ||
467 | { | ||
468 | struct spidev_data *spidev; | ||
469 | int status; | ||
470 | unsigned long minor; | ||
471 | |||
472 | /* Allocate driver data */ | ||
473 | spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); | ||
474 | if (!spidev) | ||
475 | return -ENOMEM; | ||
476 | |||
477 | /* Initialize the driver data */ | ||
478 | spidev->spi = spi; | ||
479 | mutex_init(&spidev->buf_lock); | ||
480 | |||
481 | INIT_LIST_HEAD(&spidev->device_entry); | ||
482 | |||
483 | /* If we can allocate a minor number, hook up this device. | ||
484 | * Reusing minors is fine so long as udev or mdev is working. | ||
485 | */ | ||
486 | mutex_lock(&device_list_lock); | ||
487 | minor = find_first_zero_bit(minors, ARRAY_SIZE(minors)); | ||
488 | if (minor < N_SPI_MINORS) { | ||
489 | spidev->dev.parent = &spi->dev; | ||
490 | spidev->dev.class = &spidev_class; | ||
491 | spidev->dev.devt = MKDEV(SPIDEV_MAJOR, minor); | ||
492 | snprintf(spidev->dev.bus_id, sizeof spidev->dev.bus_id, | ||
493 | "spidev%d.%d", | ||
494 | spi->master->bus_num, spi->chip_select); | ||
495 | status = device_register(&spidev->dev); | ||
496 | } else { | ||
497 | dev_dbg(&spi->dev, "no minor number available!\n"); | ||
498 | status = -ENODEV; | ||
499 | } | ||
500 | if (status == 0) { | ||
501 | set_bit(minor, minors); | ||
502 | dev_set_drvdata(&spi->dev, spidev); | ||
503 | list_add(&spidev->device_entry, &device_list); | ||
504 | } | ||
505 | mutex_unlock(&device_list_lock); | ||
506 | |||
507 | if (status != 0) | ||
508 | kfree(spidev); | ||
509 | |||
510 | return status; | ||
511 | } | ||
512 | |||
513 | static int spidev_remove(struct spi_device *spi) | ||
514 | { | ||
515 | struct spidev_data *spidev = dev_get_drvdata(&spi->dev); | ||
516 | |||
517 | mutex_lock(&device_list_lock); | ||
518 | |||
519 | list_del(&spidev->device_entry); | ||
520 | dev_set_drvdata(&spi->dev, NULL); | ||
521 | clear_bit(MINOR(spidev->dev.devt), minors); | ||
522 | device_unregister(&spidev->dev); | ||
523 | |||
524 | mutex_unlock(&device_list_lock); | ||
525 | |||
526 | return 0; | ||
527 | } | ||
528 | |||
529 | static struct spi_driver spidev_spi = { | ||
530 | .driver = { | ||
531 | .name = "spidev", | ||
532 | .owner = THIS_MODULE, | ||
533 | }, | ||
534 | .probe = spidev_probe, | ||
535 | .remove = __devexit_p(spidev_remove), | ||
536 | |||
537 | /* NOTE: suspend/resume methods are not necessary here. | ||
538 | * We don't do anything except pass the requests to/from | ||
539 | * the underlying controller. The refrigerator handles | ||
540 | * most issues; the controller driver handles the rest. | ||
541 | */ | ||
542 | }; | ||
543 | |||
544 | /*-------------------------------------------------------------------------*/ | ||
545 | |||
546 | static int __init spidev_init(void) | ||
547 | { | ||
548 | int status; | ||
549 | |||
550 | /* Claim our 256 reserved device numbers. Then register a class | ||
551 | * that will key udev/mdev to add/remove /dev nodes. Last, register | ||
552 | * the driver which manages those device numbers. | ||
553 | */ | ||
554 | BUILD_BUG_ON(N_SPI_MINORS > 256); | ||
555 | status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); | ||
556 | if (status < 0) | ||
557 | return status; | ||
558 | |||
559 | status = class_register(&spidev_class); | ||
560 | if (status < 0) { | ||
561 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | ||
562 | return status; | ||
563 | } | ||
564 | |||
565 | status = spi_register_driver(&spidev_spi); | ||
566 | if (status < 0) { | ||
567 | class_unregister(&spidev_class); | ||
568 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | ||
569 | } | ||
570 | return status; | ||
571 | } | ||
572 | module_init(spidev_init); | ||
573 | |||
574 | static void __exit spidev_exit(void) | ||
575 | { | ||
576 | spi_unregister_driver(&spidev_spi); | ||
577 | class_unregister(&spidev_class); | ||
578 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | ||
579 | } | ||
580 | module_exit(spidev_exit); | ||
581 | |||
582 | MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>"); | ||
583 | MODULE_DESCRIPTION("User mode SPI device interface"); | ||
584 | MODULE_LICENSE("GPL"); | ||