]>
Commit | Line | Data |
---|---|---|
ed9eccbe DS |
1 | /* |
2 | module/drivers.c | |
3 | functions for manipulating drivers | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 1997-2000 David A. Schleef <[email protected]> | |
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 | ||
ed9eccbe DS |
24 | #include <linux/device.h> |
25 | #include <linux/module.h> | |
26 | #include <linux/pci.h> | |
d607ffac PH |
27 | #include <pcmcia/cistpl.h> |
28 | #include <pcmcia/ds.h> | |
c28264da | 29 | #include <linux/usb.h> |
ed9eccbe | 30 | #include <linux/errno.h> |
78b10615 | 31 | #include <linux/kconfig.h> |
ed9eccbe DS |
32 | #include <linux/kernel.h> |
33 | #include <linux/sched.h> | |
34 | #include <linux/fcntl.h> | |
35 | #include <linux/delay.h> | |
36 | #include <linux/ioport.h> | |
37 | #include <linux/mm.h> | |
38 | #include <linux/slab.h> | |
ed9eccbe DS |
39 | #include <linux/highmem.h> /* for SuSE brokenness */ |
40 | #include <linux/vmalloc.h> | |
41 | #include <linux/cdev.h> | |
42 | #include <linux/dma-mapping.h> | |
5617f9da | 43 | #include <linux/io.h> |
ed9eccbe | 44 | |
242e7ad9 | 45 | #include "comedidev.h" |
3a5fa275 | 46 | #include "comedi_internal.h" |
242e7ad9 | 47 | |
139dfbdf | 48 | struct comedi_driver *comedi_drivers; |
ed9eccbe | 49 | |
8b9ba6e5 | 50 | int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices) |
2f0b9d08 | 51 | { |
03afcf47 | 52 | struct comedi_subdevice *s; |
8b9ba6e5 | 53 | int i; |
2f0b9d08 | 54 | |
7f801c41 HS |
55 | if (num_subdevices < 1) |
56 | return -EINVAL; | |
03afcf47 HS |
57 | |
58 | s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL); | |
59 | if (!s) | |
2f0b9d08 | 60 | return -ENOMEM; |
03afcf47 | 61 | dev->subdevices = s; |
fba1d0fa | 62 | dev->n_subdevices = num_subdevices; |
03afcf47 | 63 | |
2f0b9d08 | 64 | for (i = 0; i < num_subdevices; ++i) { |
5e4c58ce | 65 | s = &dev->subdevices[i]; |
03afcf47 | 66 | s->device = dev; |
90a35c15 | 67 | s->index = i; |
03afcf47 HS |
68 | s->async_dma_dir = DMA_NONE; |
69 | spin_lock_init(&s->spin_lock); | |
70 | s->minor = -1; | |
2f0b9d08 HS |
71 | } |
72 | return 0; | |
73 | } | |
74 | EXPORT_SYMBOL_GPL(comedi_alloc_subdevices); | |
75 | ||
71b5f4f1 | 76 | static void cleanup_device(struct comedi_device *dev) |
ed9eccbe DS |
77 | { |
78 | int i; | |
34c43922 | 79 | struct comedi_subdevice *s; |
ed9eccbe DS |
80 | |
81 | if (dev->subdevices) { | |
82 | for (i = 0; i < dev->n_subdevices; i++) { | |
5e4c58ce | 83 | s = &dev->subdevices[i]; |
ed9eccbe DS |
84 | comedi_free_subdevice_minor(s); |
85 | if (s->async) { | |
86 | comedi_buf_alloc(dev, s, 0); | |
87 | kfree(s->async); | |
88 | } | |
89 | } | |
90 | kfree(dev->subdevices); | |
91 | dev->subdevices = NULL; | |
92 | dev->n_subdevices = 0; | |
93 | } | |
dedd1325 BP |
94 | kfree(dev->private); |
95 | dev->private = NULL; | |
7029a874 | 96 | dev->driver = NULL; |
ed9eccbe DS |
97 | dev->board_name = NULL; |
98 | dev->board_ptr = NULL; | |
99 | dev->iobase = 0; | |
100 | dev->irq = 0; | |
101 | dev->read_subdev = NULL; | |
102 | dev->write_subdev = NULL; | |
103 | dev->open = NULL; | |
104 | dev->close = NULL; | |
105 | comedi_set_hw_dev(dev, NULL); | |
106 | } | |
107 | ||
71b5f4f1 | 108 | static void __comedi_device_detach(struct comedi_device *dev) |
ed9eccbe DS |
109 | { |
110 | dev->attached = 0; | |
5617f9da | 111 | if (dev->driver) |
ed9eccbe | 112 | dev->driver->detach(dev); |
5617f9da | 113 | else |
4f870fe6 IA |
114 | dev_warn(dev->class_dev, |
115 | "BUG: dev->driver=NULL in comedi_device_detach()\n"); | |
ed9eccbe DS |
116 | cleanup_device(dev); |
117 | } | |
118 | ||
71b5f4f1 | 119 | void comedi_device_detach(struct comedi_device *dev) |
ed9eccbe DS |
120 | { |
121 | if (!dev->attached) | |
122 | return; | |
123 | __comedi_device_detach(dev); | |
124 | } | |
125 | ||
01fca378 | 126 | static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s) |
ed9eccbe | 127 | { |
01fca378 | 128 | return -EINVAL; |
ed9eccbe DS |
129 | } |
130 | ||
01fca378 HS |
131 | int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s, |
132 | struct comedi_insn *insn, unsigned int *data) | |
ed9eccbe | 133 | { |
01fca378 | 134 | return -EINVAL; |
ed9eccbe DS |
135 | } |
136 | ||
01fca378 HS |
137 | static int insn_rw_emulate_bits(struct comedi_device *dev, |
138 | struct comedi_subdevice *s, | |
139 | struct comedi_insn *insn, unsigned int *data) | |
ed9eccbe | 140 | { |
01fca378 HS |
141 | struct comedi_insn new_insn; |
142 | int ret; | |
143 | static const unsigned channels_per_bitfield = 32; | |
ed9eccbe | 144 | |
01fca378 HS |
145 | unsigned chan = CR_CHAN(insn->chanspec); |
146 | const unsigned base_bitfield_channel = | |
147 | (chan < channels_per_bitfield) ? 0 : chan; | |
148 | unsigned int new_data[2]; | |
149 | memset(new_data, 0, sizeof(new_data)); | |
150 | memset(&new_insn, 0, sizeof(new_insn)); | |
151 | new_insn.insn = INSN_BITS; | |
152 | new_insn.chanspec = base_bitfield_channel; | |
153 | new_insn.n = 2; | |
154 | new_insn.subdev = insn->subdev; | |
ed9eccbe | 155 | |
01fca378 HS |
156 | if (insn->insn == INSN_WRITE) { |
157 | if (!(s->subdev_flags & SDF_WRITABLE)) | |
158 | return -EINVAL; | |
159 | new_data[0] = 1 << (chan - base_bitfield_channel); /* mask */ | |
160 | new_data[1] = data[0] ? (1 << (chan - base_bitfield_channel)) | |
161 | : 0; /* bits */ | |
ed9eccbe DS |
162 | } |
163 | ||
01fca378 HS |
164 | ret = s->insn_bits(dev, s, &new_insn, new_data); |
165 | if (ret < 0) | |
166 | return ret; | |
ed9eccbe | 167 | |
01fca378 HS |
168 | if (insn->insn == INSN_READ) |
169 | data[0] = (new_data[1] >> (chan - base_bitfield_channel)) & 1; | |
170 | ||
171 | return 1; | |
ed9eccbe DS |
172 | } |
173 | ||
71b5f4f1 | 174 | static int postconfig(struct comedi_device *dev) |
ed9eccbe DS |
175 | { |
176 | int i; | |
34c43922 | 177 | struct comedi_subdevice *s; |
d163679c | 178 | struct comedi_async *async = NULL; |
ed9eccbe DS |
179 | int ret; |
180 | ||
181 | for (i = 0; i < dev->n_subdevices; i++) { | |
5e4c58ce | 182 | s = &dev->subdevices[i]; |
ed9eccbe DS |
183 | |
184 | if (s->type == COMEDI_SUBD_UNUSED) | |
185 | continue; | |
186 | ||
187 | if (s->len_chanlist == 0) | |
188 | s->len_chanlist = 1; | |
189 | ||
190 | if (s->do_cmd) { | |
4d7df821 IA |
191 | unsigned int buf_size; |
192 | ||
ed9eccbe | 193 | BUG_ON((s->subdev_flags & (SDF_CMD_READ | |
0a85b6f0 | 194 | SDF_CMD_WRITE)) == 0); |
ed9eccbe DS |
195 | BUG_ON(!s->do_cmdtest); |
196 | ||
0a85b6f0 MT |
197 | async = |
198 | kzalloc(sizeof(struct comedi_async), GFP_KERNEL); | |
ed9eccbe | 199 | if (async == NULL) { |
4f870fe6 IA |
200 | dev_warn(dev->class_dev, |
201 | "failed to allocate async struct\n"); | |
ed9eccbe DS |
202 | return -ENOMEM; |
203 | } | |
204 | init_waitqueue_head(&async->wait_head); | |
205 | async->subdevice = s; | |
206 | s->async = async; | |
207 | ||
4d7df821 IA |
208 | async->max_bufsize = |
209 | comedi_default_buf_maxsize_kb * 1024; | |
210 | buf_size = comedi_default_buf_size_kb * 1024; | |
211 | if (buf_size > async->max_bufsize) | |
212 | buf_size = async->max_bufsize; | |
ed9eccbe DS |
213 | |
214 | async->prealloc_buf = NULL; | |
215 | async->prealloc_bufsz = 0; | |
4d7df821 | 216 | if (comedi_buf_alloc(dev, s, buf_size) < 0) { |
4f870fe6 IA |
217 | dev_warn(dev->class_dev, |
218 | "Buffer allocation failed\n"); | |
ed9eccbe DS |
219 | return -ENOMEM; |
220 | } | |
221 | if (s->buf_change) { | |
4d7df821 | 222 | ret = s->buf_change(dev, s, buf_size); |
ed9eccbe DS |
223 | if (ret < 0) |
224 | return ret; | |
225 | } | |
226 | comedi_alloc_subdevice_minor(dev, s); | |
227 | } | |
228 | ||
229 | if (!s->range_table && !s->range_table_list) | |
230 | s->range_table = &range_unknown; | |
231 | ||
232 | if (!s->insn_read && s->insn_bits) | |
233 | s->insn_read = insn_rw_emulate_bits; | |
234 | if (!s->insn_write && s->insn_bits) | |
235 | s->insn_write = insn_rw_emulate_bits; | |
236 | ||
237 | if (!s->insn_read) | |
238 | s->insn_read = insn_inval; | |
239 | if (!s->insn_write) | |
240 | s->insn_write = insn_inval; | |
241 | if (!s->insn_bits) | |
242 | s->insn_bits = insn_inval; | |
243 | if (!s->insn_config) | |
244 | s->insn_config = insn_inval; | |
245 | ||
246 | if (!s->poll) | |
247 | s->poll = poll_invalid; | |
248 | } | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
01fca378 HS |
253 | /* do a little post-config cleanup */ |
254 | /* called with module refcount incremented, decrements it */ | |
255 | static int comedi_device_postconfig(struct comedi_device *dev) | |
256 | { | |
257 | int ret = postconfig(dev); | |
258 | module_put(dev->driver->module); | |
259 | if (ret < 0) { | |
260 | __comedi_device_detach(dev); | |
261 | return ret; | |
262 | } | |
263 | if (!dev->board_name) { | |
264 | dev_warn(dev->class_dev, "BUG: dev->board_name=NULL\n"); | |
265 | dev->board_name = "BUG"; | |
266 | } | |
267 | smp_wmb(); | |
268 | dev->attached = 1; | |
269 | return 0; | |
270 | } | |
271 | ||
4e2f002f IA |
272 | /* |
273 | * Generic recognize function for drivers that register their supported | |
274 | * board names. | |
275 | * | |
276 | * 'driv->board_name' points to a 'const char *' member within the | |
277 | * zeroth element of an array of some private board information | |
278 | * structure, say 'struct foo_board' containing a member 'const char | |
279 | * *board_name' that is initialized to point to a board name string that | |
280 | * is one of the candidates matched against this function's 'name' | |
281 | * parameter. | |
282 | * | |
283 | * 'driv->offset' is the size of the private board information | |
284 | * structure, say 'sizeof(struct foo_board)', and 'driv->num_names' is | |
285 | * the length of the array of private board information structures. | |
286 | * | |
287 | * If one of the board names in the array of private board information | |
288 | * structures matches the name supplied to this function, the function | |
289 | * returns a pointer to the pointer to the board name, otherwise it | |
290 | * returns NULL. The return value ends up in the 'board_ptr' member of | |
291 | * a 'struct comedi_device' that the low-level comedi driver's | |
292 | * 'attach()' hook can convert to a point to a particular element of its | |
293 | * array of private board information structures by subtracting the | |
294 | * offset of the member that points to the board name. (No subtraction | |
295 | * is required if the board name pointer is the first member of the | |
296 | * private board information structure, which is generally the case.) | |
297 | */ | |
7029a874 | 298 | static void *comedi_recognize(struct comedi_driver *driv, const char *name) |
ed9eccbe | 299 | { |
1c9de58a DC |
300 | char **name_ptr = (char **)driv->board_name; |
301 | int i; | |
302 | ||
ed9eccbe DS |
303 | for (i = 0; i < driv->num_names; i++) { |
304 | if (strcmp(*name_ptr, name) == 0) | |
1c9de58a DC |
305 | return name_ptr; |
306 | name_ptr = (void *)name_ptr + driv->offset; | |
ed9eccbe DS |
307 | } |
308 | ||
309 | return NULL; | |
310 | } | |
311 | ||
7029a874 | 312 | static void comedi_report_boards(struct comedi_driver *driv) |
ed9eccbe DS |
313 | { |
314 | unsigned int i; | |
315 | const char *const *name_ptr; | |
316 | ||
4f870fe6 IA |
317 | pr_info("comedi: valid board names for %s driver are:\n", |
318 | driv->driver_name); | |
ed9eccbe DS |
319 | |
320 | name_ptr = driv->board_name; | |
321 | for (i = 0; i < driv->num_names; i++) { | |
4f870fe6 | 322 | pr_info(" %s\n", *name_ptr); |
ed9eccbe DS |
323 | name_ptr = (const char **)((char *)name_ptr + driv->offset); |
324 | } | |
325 | ||
326 | if (driv->num_names == 0) | |
4f870fe6 | 327 | pr_info(" %s\n", driv->driver_name); |
ed9eccbe DS |
328 | } |
329 | ||
01fca378 | 330 | int comedi_device_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
ed9eccbe | 331 | { |
01fca378 HS |
332 | struct comedi_driver *driv; |
333 | int ret; | |
334 | ||
335 | if (dev->attached) | |
336 | return -EBUSY; | |
337 | ||
338 | for (driv = comedi_drivers; driv; driv = driv->next) { | |
339 | if (!try_module_get(driv->module)) | |
340 | continue; | |
341 | if (driv->num_names) { | |
342 | dev->board_ptr = comedi_recognize(driv, it->board_name); | |
343 | if (dev->board_ptr) | |
344 | break; | |
345 | } else if (strcmp(driv->driver_name, it->board_name) == 0) | |
346 | break; | |
347 | module_put(driv->module); | |
348 | } | |
349 | if (driv == NULL) { | |
350 | /* recognize has failed if we get here */ | |
351 | /* report valid board names before returning error */ | |
352 | for (driv = comedi_drivers; driv; driv = driv->next) { | |
353 | if (!try_module_get(driv->module)) | |
354 | continue; | |
355 | comedi_report_boards(driv); | |
356 | module_put(driv->module); | |
357 | } | |
358 | return -EIO; | |
359 | } | |
360 | if (driv->attach == NULL) { | |
361 | /* driver does not support manual configuration */ | |
362 | dev_warn(dev->class_dev, | |
363 | "driver '%s' does not support attach using comedi_config\n", | |
364 | driv->driver_name); | |
365 | module_put(driv->module); | |
366 | return -ENOSYS; | |
367 | } | |
368 | /* initialize dev->driver here so | |
369 | * comedi_error() can be called from attach */ | |
370 | dev->driver = driv; | |
371 | ret = driv->attach(dev, it); | |
372 | if (ret < 0) { | |
373 | module_put(dev->driver->module); | |
374 | __comedi_device_detach(dev); | |
375 | return ret; | |
376 | } | |
377 | return comedi_device_postconfig(dev); | |
ed9eccbe DS |
378 | } |
379 | ||
01fca378 | 380 | int comedi_driver_register(struct comedi_driver *driver) |
ed9eccbe | 381 | { |
01fca378 HS |
382 | driver->next = comedi_drivers; |
383 | comedi_drivers = driver; | |
384 | ||
385 | return 0; | |
ed9eccbe | 386 | } |
01fca378 | 387 | EXPORT_SYMBOL(comedi_driver_register); |
ed9eccbe | 388 | |
01fca378 | 389 | int comedi_driver_unregister(struct comedi_driver *driver) |
ed9eccbe | 390 | { |
01fca378 HS |
391 | struct comedi_driver *prev; |
392 | int i; | |
ed9eccbe | 393 | |
01fca378 HS |
394 | /* check for devices using this driver */ |
395 | for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { | |
396 | struct comedi_device *dev = comedi_dev_from_minor(i); | |
ed9eccbe | 397 | |
01fca378 HS |
398 | if (!dev) |
399 | continue; | |
ed9eccbe | 400 | |
01fca378 HS |
401 | mutex_lock(&dev->mutex); |
402 | if (dev->attached && dev->driver == driver) { | |
403 | if (dev->use_count) | |
404 | dev_warn(dev->class_dev, | |
405 | "BUG! detaching device with use_count=%d\n", | |
406 | dev->use_count); | |
407 | comedi_device_detach(dev); | |
408 | } | |
409 | mutex_unlock(&dev->mutex); | |
410 | } | |
ed9eccbe | 411 | |
01fca378 HS |
412 | if (comedi_drivers == driver) { |
413 | comedi_drivers = driver->next; | |
414 | return 0; | |
415 | } | |
ed9eccbe | 416 | |
01fca378 HS |
417 | for (prev = comedi_drivers; prev->next; prev = prev->next) { |
418 | if (prev->next == driver) { | |
419 | prev->next = driver->next; | |
420 | return 0; | |
421 | } | |
422 | } | |
423 | return -EINVAL; | |
ed9eccbe | 424 | } |
01fca378 | 425 | EXPORT_SYMBOL(comedi_driver_unregister); |
ed9eccbe | 426 | |
a588da1d IA |
427 | int comedi_auto_config(struct device *hardware_device, |
428 | struct comedi_driver *driver, unsigned long context) | |
f4011670 IA |
429 | { |
430 | int minor; | |
f4011670 IA |
431 | struct comedi_device *comedi_dev; |
432 | int ret; | |
433 | ||
434 | if (!comedi_autoconfig) | |
435 | return 0; | |
436 | ||
a588da1d IA |
437 | if (!driver->auto_attach) { |
438 | dev_warn(hardware_device, | |
439 | "BUG! comedi driver '%s' has no auto_attach handler\n", | |
440 | driver->driver_name); | |
441 | return -EINVAL; | |
442 | } | |
443 | ||
f4011670 IA |
444 | minor = comedi_alloc_board_minor(hardware_device); |
445 | if (minor < 0) | |
446 | return minor; | |
447 | ||
4da5fa9a | 448 | comedi_dev = comedi_dev_from_minor(minor); |
f4011670 IA |
449 | |
450 | mutex_lock(&comedi_dev->mutex); | |
451 | if (comedi_dev->attached) | |
452 | ret = -EBUSY; | |
4f870fe6 | 453 | else if (!try_module_get(driver->module)) |
f4011670 | 454 | ret = -EIO; |
4f870fe6 | 455 | else { |
26cbd465 | 456 | comedi_set_hw_dev(comedi_dev, hardware_device); |
f4011670 | 457 | comedi_dev->driver = driver; |
a588da1d | 458 | ret = driver->auto_attach(comedi_dev, context); |
f4011670 IA |
459 | if (ret < 0) { |
460 | module_put(driver->module); | |
461 | __comedi_device_detach(comedi_dev); | |
462 | } else { | |
463 | ret = comedi_device_postconfig(comedi_dev); | |
464 | } | |
465 | } | |
466 | mutex_unlock(&comedi_dev->mutex); | |
467 | ||
468 | if (ret < 0) | |
469 | comedi_free_board_minor(minor); | |
470 | return ret; | |
471 | } | |
8ed705af IA |
472 | EXPORT_SYMBOL_GPL(comedi_auto_config); |
473 | ||
474 | void comedi_auto_unconfig(struct device *hardware_device) | |
ed9eccbe | 475 | { |
c43435d7 | 476 | int minor; |
ed9eccbe | 477 | |
c43435d7 IA |
478 | if (hardware_device == NULL) |
479 | return; | |
480 | minor = comedi_find_board_minor(hardware_device); | |
481 | if (minor < 0) | |
482 | return; | |
483 | BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS); | |
484 | comedi_free_board_minor(minor); | |
ed9eccbe | 485 | } |
8ed705af | 486 | EXPORT_SYMBOL_GPL(comedi_auto_unconfig); |
ed9eccbe | 487 | |
55c03cff HS |
488 | /** |
489 | * comedi_pci_enable() - Enable the PCI device and request the regions. | |
490 | * @pdev: pci_dev struct | |
491 | * @res_name: name for the requested reqource | |
492 | */ | |
493 | int comedi_pci_enable(struct pci_dev *pdev, const char *res_name) | |
494 | { | |
495 | int rc; | |
496 | ||
497 | rc = pci_enable_device(pdev); | |
498 | if (rc < 0) | |
499 | return rc; | |
500 | ||
501 | rc = pci_request_regions(pdev, res_name); | |
502 | if (rc < 0) | |
503 | pci_disable_device(pdev); | |
504 | ||
505 | return rc; | |
506 | } | |
507 | EXPORT_SYMBOL_GPL(comedi_pci_enable); | |
508 | ||
509 | /** | |
510 | * comedi_pci_disable() - Release the regions and disable the PCI device. | |
511 | * @pdev: pci_dev struct | |
512 | * | |
513 | * This must be matched with a previous successful call to comedi_pci_enable(). | |
514 | */ | |
515 | void comedi_pci_disable(struct pci_dev *pdev) | |
516 | { | |
517 | pci_release_regions(pdev); | |
518 | pci_disable_device(pdev); | |
519 | } | |
520 | EXPORT_SYMBOL_GPL(comedi_pci_disable); | |
521 | ||
d4899c6f HS |
522 | int comedi_pci_driver_register(struct comedi_driver *comedi_driver, |
523 | struct pci_driver *pci_driver) | |
524 | { | |
525 | int ret; | |
526 | ||
527 | ret = comedi_driver_register(comedi_driver); | |
528 | if (ret < 0) | |
529 | return ret; | |
530 | ||
d4899c6f HS |
531 | ret = pci_register_driver(pci_driver); |
532 | if (ret < 0) { | |
533 | comedi_driver_unregister(comedi_driver); | |
534 | return ret; | |
535 | } | |
536 | ||
537 | return 0; | |
538 | } | |
539 | EXPORT_SYMBOL_GPL(comedi_pci_driver_register); | |
540 | ||
541 | void comedi_pci_driver_unregister(struct comedi_driver *comedi_driver, | |
542 | struct pci_driver *pci_driver) | |
543 | { | |
544 | pci_unregister_driver(pci_driver); | |
545 | comedi_driver_unregister(comedi_driver); | |
546 | } | |
547 | EXPORT_SYMBOL_GPL(comedi_pci_driver_unregister); | |
548 | ||
d607ffac PH |
549 | #if IS_ENABLED(CONFIG_PCMCIA) |
550 | int comedi_pcmcia_driver_register(struct comedi_driver *comedi_driver, | |
551 | struct pcmcia_driver *pcmcia_driver) | |
552 | { | |
553 | int ret; | |
554 | ||
555 | ret = comedi_driver_register(comedi_driver); | |
556 | if (ret < 0) | |
557 | return ret; | |
558 | ||
559 | ret = pcmcia_register_driver(pcmcia_driver); | |
560 | if (ret < 0) { | |
561 | comedi_driver_unregister(comedi_driver); | |
562 | return ret; | |
563 | } | |
564 | ||
565 | return 0; | |
566 | } | |
567 | EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_register); | |
568 | ||
569 | void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver, | |
570 | struct pcmcia_driver *pcmcia_driver) | |
571 | { | |
572 | pcmcia_unregister_driver(pcmcia_driver); | |
573 | comedi_driver_unregister(comedi_driver); | |
574 | } | |
575 | EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_unregister); | |
576 | ||
577 | #endif | |
578 | ||
78b10615 RD |
579 | #if IS_ENABLED(CONFIG_USB) |
580 | ||
64255031 HS |
581 | int comedi_usb_driver_register(struct comedi_driver *comedi_driver, |
582 | struct usb_driver *usb_driver) | |
583 | { | |
584 | int ret; | |
585 | ||
586 | ret = comedi_driver_register(comedi_driver); | |
587 | if (ret < 0) | |
588 | return ret; | |
589 | ||
590 | ret = usb_register(usb_driver); | |
591 | if (ret < 0) { | |
592 | comedi_driver_unregister(comedi_driver); | |
593 | return ret; | |
594 | } | |
595 | ||
596 | return 0; | |
597 | } | |
598 | EXPORT_SYMBOL_GPL(comedi_usb_driver_register); | |
599 | ||
600 | void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver, | |
601 | struct usb_driver *usb_driver) | |
602 | { | |
603 | usb_deregister(usb_driver); | |
604 | comedi_driver_unregister(comedi_driver); | |
605 | } | |
606 | EXPORT_SYMBOL_GPL(comedi_usb_driver_unregister); | |
78b10615 RD |
607 | |
608 | #endif |