1 /*
2 comedi/drivers/vmk80xx.c
3 Velleman USB Board Low-Level Driver
4
5 Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
6
7 COMEDI - Linux Control and Measurement Device Interface
8 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
24 */
25 /*
26 Driver: vmk80xx
27 Description: Velleman USB Board Low-Level Driver
28 Devices: K8055/K8061 aka VM110/VM140
29 Author: Manuel Gebele <forensixs@gmx.de>
30 Updated: Sun, 10 May 2009 11:14:59 +0200
31 Status: works
32
33 Supports:
34 - analog input
35 - analog output
36 - digital input
37 - digital output
38 - counter
39 - pwm
40 */
41 /*
42 Changelog:
43
44 0.8.81 -3- code completely rewritten (adjust driver logic)
45 0.8.81 -2- full support for K8061
46 0.8.81 -1- fix some mistaken among others the number of
47 supported boards and I/O handling
48
49 0.7.76 -4- renamed to vmk80xx
50 0.7.76 -3- detect K8061 (only theoretically supported)
51 0.7.76 -2- code completely rewritten (adjust driver logic)
52 0.7.76 -1- support for digital and counter subdevice
53 */
54
55 #include <linux/kernel.h>
56 #include <linux/module.h>
57 #include <linux/mutex.h>
58 #include <linux/errno.h>
59 #include <linux/input.h>
60 #include <linux/slab.h>
61 #include <linux/poll.h>
62 #include <linux/usb.h>
63 #include <linux/uaccess.h>
64
65 #include "../comedidev.h"
66
67 MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
68 MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
69 MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140");
70 MODULE_VERSION("0.8.01");
71 MODULE_LICENSE("GPL");
72
73 enum {
74 DEVICE_VMK8055,
75 DEVICE_VMK8061
76 };
77
78 static struct usb_device_id vmk80xx_id_table[] = {
79 { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 },
80 { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 },
81 { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 },
82 { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 },
83 { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 },
84 { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 },
85 { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 },
86 { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 },
87 { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 },
88 { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 },
89 { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 },
90 { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 },
91 { } /* terminating entry */
92 };
93
94 MODULE_DEVICE_TABLE(usb, vmk80xx_id_table);
95
96 #define VMK8055_DI_REG 0x00
97 #define VMK8055_DO_REG 0x01
98 #define VMK8055_AO1_REG 0x02
99 #define VMK8055_AO2_REG 0x03
100 #define VMK8055_AI1_REG 0x02
101 #define VMK8055_AI2_REG 0x03
102 #define VMK8055_CNT1_REG 0x04
103 #define VMK8055_CNT2_REG 0x06
104
105 #define VMK8061_CH_REG 0x01
106 #define VMK8061_DI_REG 0x01
107 #define VMK8061_DO_REG 0x01
108 #define VMK8061_PWM_REG1 0x01
109 #define VMK8061_PWM_REG2 0x02
110 #define VMK8061_CNT_REG 0x02
111 #define VMK8061_AO_REG 0x02
112 #define VMK8061_AI_REG1 0x02
113 #define VMK8061_AI_REG2 0x03
114
115 #define VMK8055_CMD_RST 0x00
116 #define VMK8055_CMD_DEB1_TIME 0x01
117 #define VMK8055_CMD_DEB2_TIME 0x02
118 #define VMK8055_CMD_RST_CNT1 0x03
119 #define VMK8055_CMD_RST_CNT2 0x04
120 #define VMK8055_CMD_WRT_AD 0x05
121
122 #define VMK8061_CMD_RD_AI 0x00
123 #define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */
124 #define VMK8061_CMD_SET_AO 0x02
125 #define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */
126 #define VMK8061_CMD_OUT_PWM 0x04
127 #define VMK8061_CMD_RD_DI 0x05
128 #define VMK8061_CMD_DO 0x06 /* !non-active! */
129 #define VMK8061_CMD_CLR_DO 0x07
130 #define VMK8061_CMD_SET_DO 0x08
131 #define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */
132 #define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */
133 #define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */
134 #define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */
135 #define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */
136 #define VMK8061_CMD_RD_DO 0x0e
137 #define VMK8061_CMD_RD_AO 0x0f
138 #define VMK8061_CMD_RD_PWM 0x10
139
140 #define VMK80XX_MAX_BOARDS COMEDI_NUM_BOARD_MINORS
141
142 #define TRANS_OUT_BUSY 1
143 #define TRANS_IN_BUSY 2
144 #define TRANS_IN_RUNNING 3
145
146 #define IC3_VERSION (1 << 0)
147 #define IC6_VERSION (1 << 1)
148
149 #define URB_RCV_FLAG (1 << 0)
150 #define URB_SND_FLAG (1 << 1)
151
152 #define CONFIG_VMK80XX_DEBUG
153 #undef CONFIG_VMK80XX_DEBUG
154
155 #ifdef CONFIG_VMK80XX_DEBUG
156 static int dbgvm = 1;
157 #else
158 static int dbgvm;
159 #endif
160
161 #ifdef CONFIG_COMEDI_DEBUG
162 static int dbgcm = 1;
163 #else
164 static int dbgcm;
165 #endif
166
167 #define dbgvm(fmt, arg...) \
168 do { \
169 if (dbgvm) \
170 printk(KERN_DEBUG fmt, ##arg); \
171 } while (0)
172
173 #define dbgcm(fmt, arg...) \
174 do { \
175 if (dbgcm) \
176 printk(KERN_DEBUG fmt, ##arg); \
177 } while (0)
178
179 enum vmk80xx_model {
180 VMK8055_MODEL,
181 VMK8061_MODEL
182 };
183
184 struct firmware_version {
185 unsigned char ic3_vers[32]; /* USB-Controller */
186 unsigned char ic6_vers[32]; /* CPU */
187 };
188
189 static const struct comedi_lrange vmk8055_range = {
190 1, { UNI_RANGE(5) }
191 };
192
193 static const struct comedi_lrange vmk8061_range = {
194 2, { UNI_RANGE(5), UNI_RANGE(10) }
195 };
196
197 struct vmk80xx_board {
198 const char *name;
199 enum vmk80xx_model model;
200 const struct comedi_lrange *range;
201 __u8 ai_chans;
202 __le16 ai_bits;
203 __u8 ao_chans;
204 __le16 ao_bits;
205 __u8 di_chans;
206 __le16 di_bits;
207 __u8 do_chans;
208 __le16 do_bits;
209 __u8 cnt_chans;
210 __le16 cnt_bits;
211 __u8 pwm_chans;
212 __le16 pwm_bits;
213 };
214
215 enum {
216 VMK80XX_SUBD_AI,
217 VMK80XX_SUBD_AO,
218 VMK80XX_SUBD_DI,
219 VMK80XX_SUBD_DO,
220 VMK80XX_SUBD_CNT,
221 VMK80XX_SUBD_PWM,
222 };
223
224 struct vmk80xx_usb {
225 struct usb_device *udev;
226 struct usb_interface *intf;
227 struct usb_endpoint_descriptor *ep_rx;
228 struct usb_endpoint_descriptor *ep_tx;
229 struct usb_anchor rx_anchor;
230 struct usb_anchor tx_anchor;
231 struct vmk80xx_board board;
232 struct firmware_version fw;
233 struct semaphore limit_sem;
234 wait_queue_head_t read_wait;
235 wait_queue_head_t write_wait;
236 unsigned char *usb_rx_buf;
237 unsigned char *usb_tx_buf;
238 unsigned long flags;
239 int probed;
240 int attached;
241 int count;
242 };
243
244 static struct vmk80xx_usb vmb[VMK80XX_MAX_BOARDS];
245
246 static DEFINE_MUTEX(glb_mutex);
247
248 static void vmk80xx_tx_callback(struct urb *urb)
249 {
250 struct vmk80xx_usb *dev = urb->context;
251 int stat = urb->status;
252
253 dbgvm("vmk80xx: %s\n", __func__);
254
255 if (stat && !(stat == -ENOENT
256 || stat == -ECONNRESET
257 || stat == -ESHUTDOWN))
258 dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n",
259 __func__, stat);
260
261 if (!test_bit(TRANS_OUT_BUSY, &dev->flags))
262 return;
263
264 clear_bit(TRANS_OUT_BUSY, &dev->flags);
265
266 wake_up_interruptible(&dev->write_wait);
267 }
268
269 static void vmk80xx_rx_callback(struct urb *urb)
270 {
271 struct vmk80xx_usb *dev = urb->context;
272 int stat = urb->status;
273
274 dbgvm("vmk80xx: %s\n", __func__);
275
276 switch (stat) {
277 case 0:
278 break;
279 case -ENOENT:
280 case -ECONNRESET:
281 case -ESHUTDOWN:
282 break;
283 default:
284 dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n",
285 __func__, stat);
286 goto resubmit;
287 }
288
289 goto exit;
290 resubmit:
291 if (test_bit(TRANS_IN_RUNNING, &dev->flags) && dev->intf) {
292 usb_anchor_urb(urb, &dev->rx_anchor);
293
294 if (!usb_submit_urb(urb, GFP_KERNEL))
295 goto exit;
296
297 err("comedi#: vmk80xx: %s - submit urb failed\n", __func__);
298
299 usb_unanchor_urb(urb);
300 }
301 exit:
302 clear_bit(TRANS_IN_BUSY, &dev->flags);
303
304 wake_up_interruptible(&dev->read_wait);
305 }
306
307 static int vmk80xx_check_data_link(struct vmk80xx_usb *dev)
308 {
309 unsigned int tx_pipe, rx_pipe;
310 unsigned char tx[1], rx[2];
311
312 dbgvm("vmk80xx: %s\n", __func__);
313
314 tx_pipe = usb_sndbulkpipe(dev->udev, 0x01);
315 rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81);
316
317 tx[0] = VMK8061_CMD_RD_PWR_STAT;
318
319 /* Check that IC6 (PIC16F871) is powered and
320 * running and the data link between IC3 and
321 * IC6 is working properly */
322 usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL,
323 dev->ep_tx->bInterval);
324 usb_bulk_msg(dev->udev, rx_pipe, rx, 2, NULL,
325 HZ * 10);
326
327 return (int)rx[1];
328 }
329
330 static void vmk80xx_read_eeprom(struct vmk80xx_usb *dev, int flag)
331 {
332 unsigned int tx_pipe, rx_pipe;
333 unsigned char tx[1], rx[64];
334 int cnt;
335
336 dbgvm("vmk80xx: %s\n", __func__);
337
338 tx_pipe = usb_sndbulkpipe(dev->udev, 0x01);
339 rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81);
340
341 tx[0] = VMK8061_CMD_RD_VERSION;
342
343 /* Read the firmware version info of IC3 and
344 * IC6 from the internal EEPROM of the IC */
345 usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL,
346 dev->ep_tx->bInterval);
347 usb_bulk_msg(dev->udev, rx_pipe, rx, 64, &cnt,
348 HZ * 10);
349
350 rx[cnt] = '\0';
351
352 if (flag & IC3_VERSION)
353 strncpy(dev->fw.ic3_vers, rx + 1, 24);
354 else /* IC6_VERSION */
355 strncpy(dev->fw.ic6_vers, rx + 25, 24);
356 }
357
358 static int vmk80xx_reset_device(struct vmk80xx_usb *dev)
359 {
360 struct urb *urb;
361 unsigned int tx_pipe;
362 int ival;
363 size_t size;
364
365 dbgvm("vmk80xx: %s\n", __func__);
366
367 urb = usb_alloc_urb(0, GFP_KERNEL);
368 if (!urb)
369 return -ENOMEM;
370
371 tx_pipe = usb_sndintpipe(dev->udev, 0x01);
372
373 ival = dev->ep_tx->bInterval;
374 size = le16_to_cpu(dev->ep_tx->wMaxPacketSize);
375
376 dev->usb_tx_buf[0] = VMK8055_CMD_RST;
377 dev->usb_tx_buf[1] = 0x00;
378 dev->usb_tx_buf[2] = 0x00;
379 dev->usb_tx_buf[3] = 0x00;
380 dev->usb_tx_buf[4] = 0x00;
381 dev->usb_tx_buf[5] = 0x00;
382 dev->usb_tx_buf[6] = 0x00;
383 dev->usb_tx_buf[7] = 0x00;
384
385 usb_fill_int_urb(urb, dev->udev, tx_pipe, dev->usb_tx_buf,
386 size, vmk80xx_tx_callback, dev, ival);
387
388 usb_anchor_urb(urb, &dev->tx_anchor);
389
390 return usb_submit_urb(urb, GFP_KERNEL);
391 }
392
393 static void vmk80xx_build_int_urb(struct urb *urb, int flag)
394 {
395 struct vmk80xx_usb *dev = urb->context;
396 __u8 rx_addr, tx_addr;
397 unsigned int pipe;
398 unsigned char *buf;
399 size_t size;
400 void (*callback)(struct urb *);
401 int ival;
402
403 dbgvm("vmk80xx: %s\n", __func__);
404
405 if (flag & URB_RCV_FLAG) {
406 rx_addr = dev->ep_rx->bEndpointAddress;
407 pipe = usb_rcvintpipe(dev->udev, rx_addr);
408 buf = dev->usb_rx_buf;
409 size = le16_to_cpu(dev->ep_rx->wMaxPacketSize);
410 callback = vmk80xx_rx_callback;
411 ival = dev->ep_rx->bInterval;
412 } else { /* URB_SND_FLAG */
413 tx_addr = dev->ep_tx->bEndpointAddress;
414 pipe = usb_sndintpipe(dev->udev, tx_addr);
415 buf = dev->usb_tx_buf;
416 size = le16_to_cpu(dev->ep_tx->wMaxPacketSize);
417 callback = vmk80xx_tx_callback;
418 ival = dev->ep_tx->bInterval;
419 }
420
421 usb_fill_int_urb(urb, dev->udev, pipe, buf,
422 size, callback, dev, ival);
423 }
424
425 static void vmk80xx_do_bulk_msg(struct vmk80xx_usb *dev)
426 {
427 __u8 tx_addr, rx_addr;
428 unsigned int tx_pipe, rx_pipe;
429 size_t size;
430
431 dbgvm("vmk80xx: %s\n", __func__);
432
433 set_bit(TRANS_IN_BUSY, &dev->flags);
434 set_bit(TRANS_OUT_BUSY, &dev->flags);
435
436 tx_addr = dev->ep_tx->bEndpointAddress;
437 rx_addr = dev->ep_rx->bEndpointAddress;
438 tx_pipe = usb_sndbulkpipe(dev->udev, tx_addr);
439 rx_pipe = usb_rcvbulkpipe(dev->udev, rx_addr);
440
441 /* The max packet size attributes of the K8061
442 * input/output endpoints are identical */
443 size = le16_to_cpu(dev->ep_tx->wMaxPacketSize);
444
445 usb_bulk_msg(dev->udev, tx_pipe, dev->usb_tx_buf,
446 size, NULL, dev->ep_tx->bInterval);
447 usb_bulk_msg(dev->udev, rx_pipe, dev->usb_rx_buf,
448 size, NULL, HZ * 10);
449
450 clear_bit(TRANS_OUT_BUSY, &dev->flags);
451 clear_bit(TRANS_IN_BUSY, &dev->flags);
452 }
453
454 static int vmk80xx_read_packet(struct vmk80xx_usb *dev)
455 {
456 struct urb *urb;
457 int retval;
458
459 dbgvm("vmk80xx: %s\n", __func__);
460
461 if (!dev->intf)
462 return -ENODEV;
463
464 /* Only useful for interrupt transfers */
465 if (test_bit(TRANS_IN_BUSY, &dev->flags))
466 if (wait_event_interruptible(dev->read_wait,
467 !test_bit(TRANS_IN_BUSY, &dev->flags)))
468 return -ERESTART;
469
470 if (dev->board.model == VMK8061_MODEL) {
471 vmk80xx_do_bulk_msg(dev);
472
473 return 0;
474 }
475
476 urb = usb_alloc_urb(0, GFP_KERNEL);
477 if (!urb)
478 return -ENOMEM;
479
480 urb->context = dev;
481 vmk80xx_build_int_urb(urb, URB_RCV_FLAG);
482
483 set_bit(TRANS_IN_RUNNING, &dev->flags);
484 set_bit(TRANS_IN_BUSY, &dev->flags);
485
486 usb_anchor_urb(urb, &dev->rx_anchor);
487
488 retval = usb_submit_urb(urb, GFP_KERNEL);
489 if (!retval)
490 goto exit;
491
492 clear_bit(TRANS_IN_RUNNING, &dev->flags);
493 usb_unanchor_urb(urb);
494
495 exit:
496 usb_free_urb(urb);
497
498 return retval;
499 }
500
501 static int vmk80xx_write_packet(struct vmk80xx_usb *dev, int cmd)
502 {
503 struct urb *urb;
504 int retval;
505
506 dbgvm("vmk80xx: %s\n", __func__);
507
508 if (!dev->intf)
509 return -ENODEV;
510
511 if (test_bit(TRANS_OUT_BUSY, &dev->flags))
512 if (wait_event_interruptible(dev->write_wait,
513 !test_bit(TRANS_OUT_BUSY, &dev->flags)))
514 return -ERESTART;
515
516 if (dev->board.model == VMK8061_MODEL) {
517 dev->usb_tx_buf[0] = cmd;
518 vmk80xx_do_bulk_msg(dev);
519
520 return 0;
521 }
522
523 urb = usb_alloc_urb(0, GFP_KERNEL);
524 if (!urb)
525 return -ENOMEM;
526
527 urb->context = dev;
528 vmk80xx_build_int_urb(urb, URB_SND_FLAG);
529
530 set_bit(TRANS_OUT_BUSY, &dev->flags);
531
532 usb_anchor_urb(urb, &dev->tx_anchor);
533
534 dev->usb_tx_buf[0] = cmd;
535
536 retval = usb_submit_urb(urb, GFP_KERNEL);
537 if (!retval)
538 goto exit;
539
540 clear_bit(TRANS_OUT_BUSY, &dev->flags);
541 usb_unanchor_urb(urb);
542
543 exit:
544 usb_free_urb(urb);
545
546 return retval;
547 }
548
549 #define DIR_IN 1
550 #define DIR_OUT 2
551
552 #define rudimentary_check(dir) \
553 do { \
554 if (!dev) \
555 return -EFAULT; \
556 if (!dev->probed) \
557 return -ENODEV; \
558 if (!dev->attached) \
559 return -ENODEV; \
560 if ((dir) & DIR_IN) { \
561 if (test_bit(TRANS_IN_BUSY, &dev->flags)) \
562 return -EBUSY; \
563 } else { /* DIR_OUT */ \
564 if (test_bit(TRANS_OUT_BUSY, &dev->flags)) \
565 return -EBUSY; \
566 } \
567 } while (0)
568
569 static int vmk80xx_ai_rinsn(struct comedi_device *cdev,
570 struct comedi_subdevice *s,
571 struct comedi_insn *insn, unsigned int *data)
572 {
573 struct vmk80xx_usb *dev = cdev->private;
574 int chan, reg[2];
575 int n;
576
577 dbgvm("vmk80xx: %s\n", __func__);
578
579 rudimentary_check(DIR_IN);
580
581 down(&dev->limit_sem);
582 chan = CR_CHAN(insn->chanspec);
583
584 switch (dev->board.model) {
585 case VMK8055_MODEL:
586 if (!chan)
587 reg[0] = VMK8055_AI1_REG;
588 else
589 reg[0] = VMK8055_AI2_REG;
590 break;
591 case VMK8061_MODEL:
592 reg[0] = VMK8061_AI_REG1;
593 reg[1] = VMK8061_AI_REG2;
594 dev->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
595 dev->usb_tx_buf[VMK8061_CH_REG] = chan;
596 break;
597 }
598
599 for (n = 0; n < insn->n; n++) {
600 if (vmk80xx_read_packet(dev))
601 break;
602
603 if (dev->board.model == VMK8055_MODEL) {
604 data[n] = dev->usb_rx_buf[reg[0]];
605 continue;
606 }
607
608 /* VMK8061_MODEL */
609 data[n] = dev->usb_rx_buf[reg[0]] + 256 *
610 dev->usb_rx_buf[reg[1]];
611 }
612
613 up(&dev->limit_sem);
614
615 return n;
616 }
617
618 static int vmk80xx_ao_winsn(struct comedi_device *cdev,
619 struct comedi_subdevice *s,
620 struct comedi_insn *insn, unsigned int *data)
621 {
622 struct vmk80xx_usb *dev = cdev->private;
623 int chan, cmd, reg;
624 int n;
625
626 dbgvm("vmk80xx: %s\n", __func__);
627
628 rudimentary_check(DIR_OUT);
629
630 down(&dev->limit_sem);
631 chan = CR_CHAN(insn->chanspec);
632
633 switch (dev->board.model) {
634 case VMK8055_MODEL:
635 cmd = VMK8055_CMD_WRT_AD;
636 if (!chan)
637 reg = VMK8055_AO1_REG;
638 else
639 reg = VMK8055_AO2_REG;
640 break;
641 default: /* NOTE: avoid compiler warnings */
642 cmd = VMK8061_CMD_SET_AO;
643 reg = VMK8061_AO_REG;
644 dev->usb_tx_buf[VMK8061_CH_REG] = chan;
645 break;
646 }
647
648 for (n = 0; n < insn->n; n++) {
649 dev->usb_tx_buf[reg] = data[n];
650
651 if (vmk80xx_write_packet(dev, cmd))
652 break;
653 }
654
655 up(&dev->limit_sem);
656
657 return n;
658 }
659
660 static int vmk80xx_ao_rinsn(struct comedi_device *cdev,
661 struct comedi_subdevice *s,
662 struct comedi_insn *insn, unsigned int *data)
663 {
664 struct vmk80xx_usb *dev = cdev->private;
665 int chan, reg;
666 int n;
667
668 dbgvm("vmk80xx: %s\n", __func__);
669
670 rudimentary_check(DIR_IN);
671
672 down(&dev->limit_sem);
673 chan = CR_CHAN(insn->chanspec);
674
675 reg = VMK8061_AO_REG - 1;
676
677 dev->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
678
679 for (n = 0; n < insn->n; n++) {
680 if (vmk80xx_read_packet(dev))
681 break;
682
683 data[n] = dev->usb_rx_buf[reg+chan];
684 }
685
686 up(&dev->limit_sem);
687
688 return n;
689 }
690
691 static int vmk80xx_di_rinsn(struct comedi_device *cdev,
692 struct comedi_subdevice *s,
693 struct comedi_insn *insn, unsigned int *data)
694 {
695 struct vmk80xx_usb *dev = cdev->private;
696 int chan;
697 unsigned char *rx_buf;
698 int reg, inp;
699 int n;
700
701 dbgvm("vmk80xx: %s\n", __func__);
702
703 rudimentary_check(DIR_IN);
704
705 down(&dev->limit_sem);
706 chan = CR_CHAN(insn->chanspec);
707
708 rx_buf = dev->usb_rx_buf;
709
710 if (dev->board.model == VMK8061_MODEL) {
711 reg = VMK8061_DI_REG;
712 dev->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
713 } else
714 reg = VMK8055_DI_REG;
715
716 for (n = 0; n < insn->n; n++) {
717 if (vmk80xx_read_packet(dev))
718 break;
719
720 if (dev->board.model == VMK8055_MODEL)
721 inp = (((rx_buf[reg] >> 4) & 0x03) |
722 ((rx_buf[reg] << 2) & 0x04) |
723 ((rx_buf[reg] >> 3) & 0x18));
724 else
725 inp = rx_buf[reg];
726
727 data[n] = ((inp & (1 << chan)) > 0);
728 }
729
730 up(&dev->limit_sem);
731
732 return n;
733 }
734
735 static int vmk80xx_do_winsn(struct comedi_device *cdev,
736 struct comedi_subdevice *s,
737 struct comedi_insn *insn, unsigned int *data)
738 {
739
740 struct vmk80xx_usb *dev = cdev->private;
741 int chan;
742 unsigned char *tx_buf;
743 int reg, cmd;
744 int n;
745
746 dbgvm("vmk80xx: %s\n", __func__);
747
748 rudimentary_check(DIR_OUT);
749
750 down(&dev->limit_sem);
751 chan = CR_CHAN(insn->chanspec);
752
753 tx_buf = dev->usb_tx_buf;
754
755 for (n = 0; n < insn->n; n++) {
756 if (dev->board.model == VMK8055_MODEL) {
757 reg = VMK8055_DO_REG;
758 cmd = VMK8055_CMD_WRT_AD;
759 if (data[n] == 1)
760 tx_buf[reg] |= (1 << chan);
761 else
762 tx_buf[reg] ^= (1 << chan);
763
764 goto write_packet;
765 }
766
767 /* VMK8061_MODEL */
768 reg = VMK8061_DO_REG;
769 if (data[n] == 1) {
770 cmd = VMK8061_CMD_SET_DO;
771 tx_buf[reg] = 1 << chan;
772 } else {
773 cmd = VMK8061_CMD_CLR_DO;
774 tx_buf[reg] = 0xff - (1 << chan);
775 }
776
777 write_packet:
778 if (vmk80xx_write_packet(dev, cmd))
779 break;
780 }
781
782 up(&dev->limit_sem);
783
784 return n;
785 }
786
787 static int vmk80xx_do_rinsn(struct comedi_device *cdev,
788 struct comedi_subdevice *s,
789 struct comedi_insn *insn, unsigned int *data)
790 {
791 struct vmk80xx_usb *dev = cdev->private;
792 int chan, reg, mask;
793 int n;
794
795 dbgvm("vmk80xx: %s\n", __func__);
796
797 rudimentary_check(DIR_IN);
798
799 down(&dev->limit_sem);
800 chan = CR_CHAN(insn->chanspec);
801
802 reg = VMK8061_DO_REG;
803 mask = 1 << chan;
804
805 dev->usb_tx_buf[0] = VMK8061_CMD_RD_DO;
806
807 for (n = 0; n < insn->n; n++) {
808 if (vmk80xx_read_packet(dev))
809 break;
810
811 data[n] = (dev->usb_rx_buf[reg] & mask) >> chan;
812 }
813
814 up(&dev->limit_sem);
815
816 return n;
817 }
818
819 static int vmk80xx_cnt_rinsn(struct comedi_device *cdev,
820 struct comedi_subdevice *s,
821 struct comedi_insn *insn, unsigned int *data)
822 {
823 struct vmk80xx_usb *dev = cdev->private;
824 int chan, reg[2];
825 int n;
826
827 dbgvm("vmk80xx: %s\n", __func__);
828
829 rudimentary_check(DIR_IN);
830
831 down(&dev->limit_sem);
832 chan = CR_CHAN(insn->chanspec);
833
834 switch (dev->board.model) {
835 case VMK8055_MODEL:
836 if (!chan)
837 reg[0] = VMK8055_CNT1_REG;
838 else
839 reg[0] = VMK8055_CNT2_REG;
840 break;
841 case VMK8061_MODEL:
842 reg[0] = VMK8061_CNT_REG;
843 reg[1] = VMK8061_CNT_REG;
844 dev->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
845 break;
846 }
847
848 for (n = 0; n < insn->n; n++) {
849 if (vmk80xx_read_packet(dev))
850 break;
851
852 if (dev->board.model == VMK8055_MODEL) {
853 data[n] = dev->usb_rx_buf[reg[0]];
854 continue;
855 }
856
857 /* VMK8061_MODEL */
858 data[n] = dev->usb_rx_buf[reg[0]*(chan+1)+1]
859 + 256 * dev->usb_rx_buf[reg[1]*2+2];
860 }
861
862 up(&dev->limit_sem);
863
864 return n;
865 }
866
867 static int vmk80xx_cnt_cinsn(struct comedi_device *cdev,
868 struct comedi_subdevice *s,
869 struct comedi_insn *insn, unsigned int *data)
870 {
871 struct vmk80xx_usb *dev = cdev->private;
872 unsigned int insn_cmd;
873 int chan, cmd, reg;
874 int n;
875
876 dbgvm("vmk80xx: %s\n", __func__);
877
878 rudimentary_check(DIR_OUT);
879
880 down(&dev->limit_sem);
881
882 insn_cmd = data[0];
883 if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET)
884 return -EINVAL;
885
886 chan = CR_CHAN(insn->chanspec);
887
888 if (dev->board.model == VMK8055_MODEL) {
889 if (!chan) {
890 cmd = VMK8055_CMD_RST_CNT1;
891 reg = VMK8055_CNT1_REG;
892 } else {
893 cmd = VMK8055_CMD_RST_CNT2;
894 reg = VMK8055_CNT2_REG;
895 }
896
897 dev->usb_tx_buf[reg] = 0x00;
898 } else
899 cmd = VMK8061_CMD_RST_CNT;
900
901 for (n = 0; n < insn->n; n++)
902 if (vmk80xx_write_packet(dev, cmd))
903 break;
904
905 up(&dev->limit_sem);
906
907 return n;
908 }
909
910 static int vmk80xx_cnt_winsn(struct comedi_device *cdev,
911 struct comedi_subdevice *s,
912 struct comedi_insn *insn, unsigned int *data)
913 {
914 struct vmk80xx_usb *dev = cdev->private;
915 unsigned long debtime, val;
916 int chan, cmd;
917 int n;
918
919 dbgvm("vmk80xx: %s\n", __func__);
920
921 rudimentary_check(DIR_OUT);
922
923 down(&dev->limit_sem);
924 chan = CR_CHAN(insn->chanspec);
925
926 if (!chan)
927 cmd = VMK8055_CMD_DEB1_TIME;
928 else
929 cmd = VMK8055_CMD_DEB2_TIME;
930
931 for (n = 0; n < insn->n; n++) {
932 debtime = data[n];
933 if (debtime == 0)
934 debtime = 1;
935
936 /* TODO: Prevent overflows */
937 if (debtime > 7450)
938 debtime = 7450;
939
940 val = int_sqrt(debtime * 1000 / 115);
941 if (((val + 1) * val) < debtime * 1000 / 115)
942 val += 1;
943
944 dev->usb_tx_buf[6+chan] = val;
945
946 if (vmk80xx_write_packet(dev, cmd))
947 break;
948 }
949
950 up(&dev->limit_sem);
951
952 return n;
953 }
954
955 static int vmk80xx_pwm_rinsn(struct comedi_device *cdev,
956 struct comedi_subdevice *s,
957 struct comedi_insn *insn, unsigned int *data)
958 {
959 struct vmk80xx_usb *dev = cdev->private;
960 int reg[2];
961 int n;
962
963 dbgvm("vmk80xx: %s\n", __func__);
964
965 rudimentary_check(DIR_IN);
966
967 down(&dev->limit_sem);
968
969 reg[0] = VMK8061_PWM_REG1;
970 reg[1] = VMK8061_PWM_REG2;
971
972 dev->usb_tx_buf[0] = VMK8061_CMD_RD_PWM;
973
974 for (n = 0; n < insn->n; n++) {
975 if (vmk80xx_read_packet(dev))
976 break;
977
978 data[n] = dev->usb_rx_buf[reg[0]] + 4 *
979 dev->usb_rx_buf[reg[1]];
980 }
981
982 up(&dev->limit_sem);
983
984 return n;
985 }
986
987 static int vmk80xx_pwm_winsn(struct comedi_device *cdev,
988 struct comedi_subdevice *s,
989 struct comedi_insn *insn, unsigned int *data)
990 {
991 struct vmk80xx_usb *dev = cdev->private;
992 unsigned char *tx_buf;
993 int reg[2], cmd;
994 int n;
995
996 dbgvm("vmk80xx: %s\n", __func__);
997
998 rudimentary_check(DIR_OUT);
999
1000 down(&dev->limit_sem);
1001
1002 tx_buf = dev->usb_tx_buf;
1003
1004 reg[0] = VMK8061_PWM_REG1;
1005 reg[1] = VMK8061_PWM_REG2;
1006
1007 cmd = VMK8061_CMD_OUT_PWM;
1008
1009 /*
1010 * The followin piece of code was translated from the inline
1011 * assembler code in the DLL source code.
1012 *
1013 * asm
1014 * mov eax, k ; k is the value (data[n])
1015 * and al, 03h ; al are the lower 8 bits of eax
1016 * mov lo, al ; lo is the low part (tx_buf[reg[0]])
1017 * mov eax, k
1018 * shr eax, 2 ; right shift eax register by 2
1019 * mov hi, al ; hi is the high part (tx_buf[reg[1]])
1020 * end;
1021 */
1022 for (n = 0; n < insn->n; n++) {
1023 tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03);
1024 tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff;
1025
1026 if (vmk80xx_write_packet(dev, cmd))
1027 break;
1028 }
1029
1030 up(&dev->limit_sem);
1031
1032 return n;
1033 }
1034
1035 static int
1036 vmk80xx_attach(struct comedi_device *cdev, struct comedi_devconfig *it)
1037 {
1038 int i;
1039 struct vmk80xx_usb *dev;
1040 int n_subd;
1041 struct comedi_subdevice *s;
1042 int minor;
1043
1044 dbgvm("vmk80xx: %s\n", __func__);
1045
1046 mutex_lock(&glb_mutex);
1047
1048 for (i = 0; i < VMK80XX_MAX_BOARDS; i++)
1049 if (vmb[i].probed && !vmb[i].attached)
1050 break;
1051
1052 if (i == VMK80XX_MAX_BOARDS) {
1053 mutex_unlock(&glb_mutex);
1054 return -ENODEV;
1055 }
1056
1057 dev = &vmb[i];
1058
1059 down(&dev->limit_sem);
1060
1061 cdev->board_name = dev->board.name;
1062 cdev->private = dev;
1063
1064 if (dev->board.model == VMK8055_MODEL)
1065 n_subd = 5;
1066 else
1067 n_subd = 6;
1068
1069 if (alloc_subdevices(cdev, n_subd) < 0) {
1070 up(&dev->limit_sem);
1071 mutex_unlock(&glb_mutex);
1072 return -ENOMEM;
1073 }
1074
1075 /* Analog input subdevice */
1076 s = cdev->subdevices + VMK80XX_SUBD_AI;
1077 s->type = COMEDI_SUBD_AI;
1078 s->subdev_flags = SDF_READABLE | SDF_GROUND;
1079 s->n_chan = dev->board.ai_chans;
1080 s->maxdata = (1 << dev->board.ai_bits) - 1;
1081 s->range_table = dev->board.range;
1082 s->insn_read = vmk80xx_ai_rinsn;
1083
1084 /* Analog output subdevice */
1085 s = cdev->subdevices + VMK80XX_SUBD_AO;
1086 s->type = COMEDI_SUBD_AO;
1087 s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
1088 s->n_chan = dev->board.ao_chans;
1089 s->maxdata = (1 << dev->board.ao_bits) - 1;
1090 s->range_table = dev->board.range;
1091 s->insn_write = vmk80xx_ao_winsn;
1092
1093 if (dev->board.model == VMK8061_MODEL) {
1094 s->subdev_flags |= SDF_READABLE;
1095 s->insn_read = vmk80xx_ao_rinsn;
1096 }
1097
1098 /* Digital input subdevice */
1099 s = cdev->subdevices + VMK80XX_SUBD_DI;
1100 s->type = COMEDI_SUBD_DI;
1101 s->subdev_flags = SDF_READABLE | SDF_GROUND;
1102 s->n_chan = dev->board.di_chans;
1103 s->maxdata = (1 << dev->board.di_bits) - 1;
1104 s->insn_read = vmk80xx_di_rinsn;
1105
1106 /* Digital output subdevice */
1107 s = cdev->subdevices + VMK80XX_SUBD_DO;
1108 s->type = COMEDI_SUBD_DO;
1109 s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
1110 s->n_chan = dev->board.do_chans;
1111 s->maxdata = (1 << dev->board.do_bits) - 1;
1112 s->insn_write = vmk80xx_do_winsn;
1113
1114 if (dev->board.model == VMK8061_MODEL) {
1115 s->subdev_flags |= SDF_READABLE;
1116 s->insn_read = vmk80xx_do_rinsn;
1117 }
1118
1119 /* Counter subdevice */
1120 s = cdev->subdevices + VMK80XX_SUBD_CNT;
1121 s->type = COMEDI_SUBD_COUNTER;
1122 s->subdev_flags = SDF_READABLE;
1123 s->n_chan = dev->board.cnt_chans;
1124 s->insn_read = vmk80xx_cnt_rinsn;
1125 s->insn_config = vmk80xx_cnt_cinsn;
1126
1127 if (dev->board.model == VMK8055_MODEL) {
1128 s->subdev_flags |= SDF_WRITEABLE;
1129 s->maxdata = (1 << dev->board.cnt_bits) - 1;
1130 s->insn_write = vmk80xx_cnt_winsn;
1131 }
1132
1133 /* PWM subdevice */
1134 if (dev->board.model == VMK8061_MODEL) {
1135 s = cdev->subdevices + VMK80XX_SUBD_PWM;
1136 s->type = COMEDI_SUBD_PWM;
1137 s->subdev_flags = SDF_READABLE | SDF_WRITEABLE;
1138 s->n_chan = dev->board.pwm_chans;
1139 s->maxdata = (1 << dev->board.pwm_bits) - 1;
1140 s->insn_read = vmk80xx_pwm_rinsn;
1141 s->insn_write = vmk80xx_pwm_winsn;
1142 }
1143
1144 dev->attached = 1;
1145
1146 minor = cdev->minor;
1147
1148 printk(KERN_INFO
1149 "comedi%d: vmk80xx: board #%d [%s] attached to comedi\n",
1150 minor, dev->count, dev->board.name);
1151
1152 up(&dev->limit_sem);
1153 mutex_unlock(&glb_mutex);
1154
1155 return 0;
1156 }
1157
1158 static int vmk80xx_detach(struct comedi_device *cdev)
1159 {
1160 struct vmk80xx_usb *dev;
1161 int minor;
1162
1163 dbgvm("vmk80xx: %s\n", __func__);
1164
1165 if (!cdev)
1166 return -EFAULT;
1167
1168 dev = cdev->private;
1169 if (!dev)
1170 return -EFAULT;
1171
1172 down(&dev->limit_sem);
1173
1174 cdev->private = NULL;
1175 dev->attached = 0;
1176
1177 minor = cdev->minor;
1178
1179 printk(KERN_INFO
1180 "comedi%d: vmk80xx: board #%d [%s] detached from comedi\n",
1181 minor, dev->count, dev->board.name);
1182
1183 up(&dev->limit_sem);
1184
1185 return 0;
1186 }
1187
1188 static int
1189 vmk80xx_probe(struct usb_interface *intf, const struct usb_device_id *id)
1190 {
1191 int i;
1192 struct vmk80xx_usb *dev;
1193 struct usb_host_interface *iface_desc;
1194 struct usb_endpoint_descriptor *ep_desc;
1195 size_t size;
1196
1197 dbgvm("vmk80xx: %s\n", __func__);
1198
1199 mutex_lock(&glb_mutex);
1200
1201 for (i = 0; i < VMK80XX_MAX_BOARDS; i++)
1202 if (!vmb[i].probed)
1203 break;
1204
1205 if (i == VMK80XX_MAX_BOARDS) {
1206 mutex_unlock(&glb_mutex);
1207 return -EMFILE;
1208 }
1209
1210 dev = &vmb[i];
1211
1212 memset(dev, 0x00, sizeof(struct vmk80xx_usb));
1213 dev->count = i;
1214
1215 iface_desc = intf->cur_altsetting;
1216 if (iface_desc->desc.bNumEndpoints != 2)
1217 goto error;
1218
1219 for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
1220 ep_desc = &iface_desc->endpoint[i].desc;
1221
1222 if (usb_endpoint_is_int_in(ep_desc)) {
1223 dev->ep_rx = ep_desc;
1224 continue;
1225 }
1226
1227 if (usb_endpoint_is_int_out(ep_desc)) {
1228 dev->ep_tx = ep_desc;
1229 continue;
1230 }
1231
1232 if (usb_endpoint_is_bulk_in(ep_desc)) {
1233 dev->ep_rx = ep_desc;
1234 continue;
1235 }
1236
1237 if (usb_endpoint_is_bulk_out(ep_desc)) {
1238 dev->ep_tx = ep_desc;
1239 continue;
1240 }
1241 }
1242
1243 if (!dev->ep_rx || !dev->ep_tx)
1244 goto error;
1245
1246 size = le16_to_cpu(dev->ep_rx->wMaxPacketSize);
1247 dev->usb_rx_buf = kmalloc(size, GFP_KERNEL);
1248 if (!dev->usb_rx_buf) {
1249 mutex_unlock(&glb_mutex);
1250 return -ENOMEM;
1251 }
1252
1253 size = le16_to_cpu(dev->ep_tx->wMaxPacketSize);
1254 dev->usb_tx_buf = kmalloc(size, GFP_KERNEL);
1255 if (!dev->usb_tx_buf) {
1256 kfree(dev->usb_rx_buf);
1257 mutex_unlock(&glb_mutex);
1258 return -ENOMEM;
1259 }
1260
1261 dev->udev = interface_to_usbdev(intf);
1262 dev->intf = intf;
1263
1264 sema_init(&dev->limit_sem, 8);
1265 init_waitqueue_head(&dev->read_wait);
1266 init_waitqueue_head(&dev->write_wait);
1267
1268 init_usb_anchor(&dev->rx_anchor);
1269 init_usb_anchor(&dev->tx_anchor);
1270
1271 usb_set_intfdata(intf, dev);
1272
1273 switch (id->driver_info) {
1274 case DEVICE_VMK8055:
1275 dev->board.name = "K8055 (VM110)";
1276 dev->board.model = VMK8055_MODEL;
1277 dev->board.range = &vmk8055_range;
1278 dev->board.ai_chans = 2;
1279 dev->board.ai_bits = 8;
1280 dev->board.ao_chans = 2;
1281 dev->board.ao_bits = 8;
1282 dev->board.di_chans = 5;
1283 dev->board.di_bits = 1;
1284 dev->board.do_chans = 8;
1285 dev->board.do_bits = 1;
1286 dev->board.cnt_chans = 2;
1287 dev->board.cnt_bits = 16;
1288 dev->board.pwm_chans = 0;
1289 dev->board.pwm_bits = 0;
1290 break;
1291 case DEVICE_VMK8061:
1292 dev->board.name = "K8061 (VM140)";
1293 dev->board.model = VMK8061_MODEL;
1294 dev->board.range = &vmk8061_range;
1295 dev->board.ai_chans = 8;
1296 dev->board.ai_bits = 10;
1297 dev->board.ao_chans = 8;
1298 dev->board.ao_bits = 8;
1299 dev->board.di_chans = 8;
1300 dev->board.di_bits = 1;
1301 dev->board.do_chans = 8;
1302 dev->board.do_bits = 1;
1303 dev->board.cnt_chans = 2;
1304 dev->board.cnt_bits = 0;
1305 dev->board.pwm_chans = 1;
1306 dev->board.pwm_bits = 10;
1307 break;
1308 }
1309
1310 if (dev->board.model == VMK8061_MODEL) {
1311 vmk80xx_read_eeprom(dev, IC3_VERSION);
1312 printk(KERN_INFO "comedi#: vmk80xx: %s\n",
1313 dev->fw.ic3_vers);
1314
1315 if (vmk80xx_check_data_link(dev)) {
1316 vmk80xx_read_eeprom(dev, IC6_VERSION);
1317 printk(KERN_INFO "comedi#: vmk80xx: %s\n",
1318 dev->fw.ic6_vers);
1319 } else
1320 dbgcm("comedi#: vmk80xx: no conn. to CPU\n");
1321 }
1322
1323 if (dev->board.model == VMK8055_MODEL)
1324 vmk80xx_reset_device(dev);
1325
1326 dev->probed = 1;
1327
1328 printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now attached\n",
1329 dev->count, dev->board.name);
1330
1331 mutex_unlock(&glb_mutex);
1332
1333 return 0;
1334 error:
1335 mutex_unlock(&glb_mutex);
1336
1337 return -ENODEV;
1338 }
1339
1340 static void vmk80xx_disconnect(struct usb_interface *intf)
1341 {
1342 struct vmk80xx_usb *dev = usb_get_intfdata(intf);
1343
1344 dbgvm("vmk80xx: %s\n", __func__);
1345
1346 if (!dev)
1347 return;
1348
1349 mutex_lock(&glb_mutex);
1350 down(&dev->limit_sem);
1351
1352 dev->probed = 0;
1353 usb_set_intfdata(dev->intf, NULL);
1354
1355 usb_kill_anchored_urbs(&dev->rx_anchor);
1356 usb_kill_anchored_urbs(&dev->tx_anchor);
1357
1358 kfree(dev->usb_rx_buf);
1359 kfree(dev->usb_tx_buf);
1360
1361 printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now detached\n",
1362 dev->count, dev->board.name);
1363
1364 up(&dev->limit_sem);
1365 mutex_unlock(&glb_mutex);
1366 }
1367
1368 /* TODO: Add support for suspend, resume, pre_reset,
1369 * post_reset and flush */
1370 static struct usb_driver vmk80xx_driver = {
1371 .name = "vmk80xx",
1372 .probe = vmk80xx_probe,
1373 .disconnect = vmk80xx_disconnect,
1374 .id_table = vmk80xx_id_table
1375 };
1376
1377 static struct comedi_driver driver_vmk80xx = {
1378 .module = THIS_MODULE,
1379 .driver_name = "vmk80xx",
1380 .attach = vmk80xx_attach,
1381 .detach = vmk80xx_detach
1382 };
1383
1384 static int __init vmk80xx_init(void)
1385 {
1386 printk(KERN_INFO "vmk80xx: version 0.8.01 "
1387 "Manuel Gebele <forensixs@gmx.de>\n");
1388 usb_register(&vmk80xx_driver);
1389 return comedi_driver_register(&driver_vmk80xx);
1390 }
1391
1392 static void __exit vmk80xx_exit(void)
1393 {
1394 comedi_driver_unregister(&driver_vmk80xx);
1395 usb_deregister(&vmk80xx_driver);
1396 }
1397
1398 module_init(vmk80xx_init);
1399 module_exit(vmk80xx_exit);
1400
|
This page was automatically generated by the
LXR engine.
|