1 // SPDX-License-Identifier: GPL-2.0+
4 * Velleman USB Board Low-Level Driver
6 * Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
8 * COMEDI - Linux Control and Measurement Device Interface
9 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
14 * Description: Velleman USB Board Low-Level Driver
15 * Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140),
16 * VM110 (K8055/VM110), VM140 (K8061/VM140)
17 * Author: Manuel Gebele <forensixs@gmx.de>
18 * Updated: Sun, 10 May 2009 11:14:59 +0200
30 #include <linux/kernel.h>
31 #include <linux/module.h>
32 #include <linux/mutex.h>
33 #include <linux/errno.h>
34 #include <linux/input.h>
35 #include <linux/slab.h>
36 #include <linux/poll.h>
37 #include <linux/uaccess.h>
39 #include "../comedi_usb.h"
46 #define VMK8055_DI_REG 0x00
47 #define VMK8055_DO_REG 0x01
48 #define VMK8055_AO1_REG 0x02
49 #define VMK8055_AO2_REG 0x03
50 #define VMK8055_AI1_REG 0x02
51 #define VMK8055_AI2_REG 0x03
52 #define VMK8055_CNT1_REG 0x04
53 #define VMK8055_CNT2_REG 0x06
55 #define VMK8061_CH_REG 0x01
56 #define VMK8061_DI_REG 0x01
57 #define VMK8061_DO_REG 0x01
58 #define VMK8061_PWM_REG1 0x01
59 #define VMK8061_PWM_REG2 0x02
60 #define VMK8061_CNT_REG 0x02
61 #define VMK8061_AO_REG 0x02
62 #define VMK8061_AI_REG1 0x02
63 #define VMK8061_AI_REG2 0x03
65 #define VMK8055_CMD_RST 0x00
66 #define VMK8055_CMD_DEB1_TIME 0x01
67 #define VMK8055_CMD_DEB2_TIME 0x02
68 #define VMK8055_CMD_RST_CNT1 0x03
69 #define VMK8055_CMD_RST_CNT2 0x04
70 #define VMK8055_CMD_WRT_AD 0x05
72 #define VMK8061_CMD_RD_AI 0x00
73 #define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */
74 #define VMK8061_CMD_SET_AO 0x02
75 #define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */
76 #define VMK8061_CMD_OUT_PWM 0x04
77 #define VMK8061_CMD_RD_DI 0x05
78 #define VMK8061_CMD_DO 0x06 /* !non-active! */
79 #define VMK8061_CMD_CLR_DO 0x07
80 #define VMK8061_CMD_SET_DO 0x08
81 #define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */
82 #define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */
83 #define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */
84 #define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */
85 #define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */
86 #define VMK8061_CMD_RD_DO 0x0e
87 #define VMK8061_CMD_RD_AO 0x0f
88 #define VMK8061_CMD_RD_PWM 0x10
90 #define IC3_VERSION BIT(0)
91 #define IC6_VERSION BIT(1)
98 static const struct comedi_lrange vmk8061_range = {
105 struct vmk80xx_board {
107 enum vmk80xx_model model;
108 const struct comedi_lrange *range;
110 unsigned int ai_maxdata;
113 unsigned int cnt_maxdata;
115 unsigned int pwm_maxdata;
118 static const struct vmk80xx_board vmk80xx_boardinfo[] = {
120 .name = "K8055 (VM110)",
121 .model = VMK8055_MODEL,
122 .range = &range_unipolar5,
124 .ai_maxdata = 0x00ff,
127 .cnt_maxdata = 0xffff,
130 .name = "K8061 (VM140)",
131 .model = VMK8061_MODEL,
132 .range = &vmk8061_range,
134 .ai_maxdata = 0x03ff,
137 .cnt_maxdata = 0, /* unknown, device is not writeable */
139 .pwm_maxdata = 0x03ff,
143 struct vmk80xx_private {
144 struct usb_endpoint_descriptor *ep_rx;
145 struct usb_endpoint_descriptor *ep_tx;
146 struct semaphore limit_sem;
147 unsigned char *usb_rx_buf;
148 unsigned char *usb_tx_buf;
149 enum vmk80xx_model model;
152 static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
154 struct vmk80xx_private *devpriv = dev->private;
155 struct usb_device *usb = comedi_to_usb_dev(dev);
158 unsigned int tx_pipe;
159 unsigned int rx_pipe;
162 tx_addr = devpriv->ep_tx->bEndpointAddress;
163 rx_addr = devpriv->ep_rx->bEndpointAddress;
164 tx_pipe = usb_sndbulkpipe(usb, tx_addr);
165 rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
168 * The max packet size attributes of the K8061
169 * input/output endpoints are identical
171 size = usb_endpoint_maxp(devpriv->ep_tx);
173 usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf,
174 size, NULL, devpriv->ep_tx->bInterval);
175 usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10);
178 static int vmk80xx_read_packet(struct comedi_device *dev)
180 struct vmk80xx_private *devpriv = dev->private;
181 struct usb_device *usb = comedi_to_usb_dev(dev);
182 struct usb_endpoint_descriptor *ep;
185 if (devpriv->model == VMK8061_MODEL) {
186 vmk80xx_do_bulk_msg(dev);
191 pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
192 return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
193 usb_endpoint_maxp(ep), NULL,
197 static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
199 struct vmk80xx_private *devpriv = dev->private;
200 struct usb_device *usb = comedi_to_usb_dev(dev);
201 struct usb_endpoint_descriptor *ep;
204 devpriv->usb_tx_buf[0] = cmd;
206 if (devpriv->model == VMK8061_MODEL) {
207 vmk80xx_do_bulk_msg(dev);
212 pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
213 return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
214 usb_endpoint_maxp(ep), NULL,
218 static int vmk80xx_reset_device(struct comedi_device *dev)
220 struct vmk80xx_private *devpriv = dev->private;
224 size = usb_endpoint_maxp(devpriv->ep_tx);
225 memset(devpriv->usb_tx_buf, 0, size);
226 retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST);
229 /* set outputs to known state as we cannot read them */
230 return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD);
233 static int vmk80xx_ai_insn_read(struct comedi_device *dev,
234 struct comedi_subdevice *s,
235 struct comedi_insn *insn,
238 struct vmk80xx_private *devpriv = dev->private;
243 down(&devpriv->limit_sem);
244 chan = CR_CHAN(insn->chanspec);
246 switch (devpriv->model) {
249 reg[0] = VMK8055_AI1_REG;
251 reg[0] = VMK8055_AI2_REG;
255 reg[0] = VMK8061_AI_REG1;
256 reg[1] = VMK8061_AI_REG2;
257 devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
258 devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
262 for (n = 0; n < insn->n; n++) {
263 if (vmk80xx_read_packet(dev))
266 if (devpriv->model == VMK8055_MODEL) {
267 data[n] = devpriv->usb_rx_buf[reg[0]];
272 data[n] = devpriv->usb_rx_buf[reg[0]] + 256 *
273 devpriv->usb_rx_buf[reg[1]];
276 up(&devpriv->limit_sem);
281 static int vmk80xx_ao_insn_write(struct comedi_device *dev,
282 struct comedi_subdevice *s,
283 struct comedi_insn *insn,
286 struct vmk80xx_private *devpriv = dev->private;
292 down(&devpriv->limit_sem);
293 chan = CR_CHAN(insn->chanspec);
295 switch (devpriv->model) {
297 cmd = VMK8055_CMD_WRT_AD;
299 reg = VMK8055_AO1_REG;
301 reg = VMK8055_AO2_REG;
303 default: /* NOTE: avoid compiler warnings */
304 cmd = VMK8061_CMD_SET_AO;
305 reg = VMK8061_AO_REG;
306 devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
310 for (n = 0; n < insn->n; n++) {
311 devpriv->usb_tx_buf[reg] = data[n];
313 if (vmk80xx_write_packet(dev, cmd))
317 up(&devpriv->limit_sem);
322 static int vmk80xx_ao_insn_read(struct comedi_device *dev,
323 struct comedi_subdevice *s,
324 struct comedi_insn *insn,
327 struct vmk80xx_private *devpriv = dev->private;
332 down(&devpriv->limit_sem);
333 chan = CR_CHAN(insn->chanspec);
335 reg = VMK8061_AO_REG - 1;
337 devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
339 for (n = 0; n < insn->n; n++) {
340 if (vmk80xx_read_packet(dev))
343 data[n] = devpriv->usb_rx_buf[reg + chan];
346 up(&devpriv->limit_sem);
351 static int vmk80xx_di_insn_bits(struct comedi_device *dev,
352 struct comedi_subdevice *s,
353 struct comedi_insn *insn,
356 struct vmk80xx_private *devpriv = dev->private;
357 unsigned char *rx_buf;
361 down(&devpriv->limit_sem);
363 rx_buf = devpriv->usb_rx_buf;
365 if (devpriv->model == VMK8061_MODEL) {
366 reg = VMK8061_DI_REG;
367 devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
369 reg = VMK8055_DI_REG;
372 retval = vmk80xx_read_packet(dev);
375 if (devpriv->model == VMK8055_MODEL)
376 data[1] = (((rx_buf[reg] >> 4) & 0x03) |
377 ((rx_buf[reg] << 2) & 0x04) |
378 ((rx_buf[reg] >> 3) & 0x18));
380 data[1] = rx_buf[reg];
385 up(&devpriv->limit_sem);
390 static int vmk80xx_do_insn_bits(struct comedi_device *dev,
391 struct comedi_subdevice *s,
392 struct comedi_insn *insn,
395 struct vmk80xx_private *devpriv = dev->private;
396 unsigned char *rx_buf = devpriv->usb_rx_buf;
397 unsigned char *tx_buf = devpriv->usb_tx_buf;
401 if (devpriv->model == VMK8061_MODEL) {
402 reg = VMK8061_DO_REG;
403 cmd = VMK8061_CMD_DO;
404 } else { /* VMK8055_MODEL */
405 reg = VMK8055_DO_REG;
406 cmd = VMK8055_CMD_WRT_AD;
409 down(&devpriv->limit_sem);
411 if (comedi_dio_update_state(s, data)) {
412 tx_buf[reg] = s->state;
413 ret = vmk80xx_write_packet(dev, cmd);
418 if (devpriv->model == VMK8061_MODEL) {
419 tx_buf[0] = VMK8061_CMD_RD_DO;
420 ret = vmk80xx_read_packet(dev);
423 data[1] = rx_buf[reg];
429 up(&devpriv->limit_sem);
431 return ret ? ret : insn->n;
434 static int vmk80xx_cnt_insn_read(struct comedi_device *dev,
435 struct comedi_subdevice *s,
436 struct comedi_insn *insn,
439 struct vmk80xx_private *devpriv = dev->private;
444 down(&devpriv->limit_sem);
445 chan = CR_CHAN(insn->chanspec);
447 switch (devpriv->model) {
450 reg[0] = VMK8055_CNT1_REG;
452 reg[0] = VMK8055_CNT2_REG;
456 reg[0] = VMK8061_CNT_REG;
457 reg[1] = VMK8061_CNT_REG;
458 devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
462 for (n = 0; n < insn->n; n++) {
463 if (vmk80xx_read_packet(dev))
466 if (devpriv->model == VMK8055_MODEL)
467 data[n] = devpriv->usb_rx_buf[reg[0]];
468 else /* VMK8061_MODEL */
469 data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1]
470 + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2];
473 up(&devpriv->limit_sem);
478 static int vmk80xx_cnt_insn_config(struct comedi_device *dev,
479 struct comedi_subdevice *s,
480 struct comedi_insn *insn,
483 struct vmk80xx_private *devpriv = dev->private;
484 unsigned int chan = CR_CHAN(insn->chanspec);
489 down(&devpriv->limit_sem);
491 case INSN_CONFIG_RESET:
492 if (devpriv->model == VMK8055_MODEL) {
494 cmd = VMK8055_CMD_RST_CNT1;
495 reg = VMK8055_CNT1_REG;
497 cmd = VMK8055_CMD_RST_CNT2;
498 reg = VMK8055_CNT2_REG;
500 devpriv->usb_tx_buf[reg] = 0x00;
502 cmd = VMK8061_CMD_RST_CNT;
504 ret = vmk80xx_write_packet(dev, cmd);
510 up(&devpriv->limit_sem);
512 return ret ? ret : insn->n;
515 static int vmk80xx_cnt_insn_write(struct comedi_device *dev,
516 struct comedi_subdevice *s,
517 struct comedi_insn *insn,
520 struct vmk80xx_private *devpriv = dev->private;
521 unsigned long debtime;
527 down(&devpriv->limit_sem);
528 chan = CR_CHAN(insn->chanspec);
531 cmd = VMK8055_CMD_DEB1_TIME;
533 cmd = VMK8055_CMD_DEB2_TIME;
535 for (n = 0; n < insn->n; n++) {
540 /* TODO: Prevent overflows */
544 val = int_sqrt(debtime * 1000 / 115);
545 if (((val + 1) * val) < debtime * 1000 / 115)
548 devpriv->usb_tx_buf[6 + chan] = val;
550 if (vmk80xx_write_packet(dev, cmd))
554 up(&devpriv->limit_sem);
559 static int vmk80xx_pwm_insn_read(struct comedi_device *dev,
560 struct comedi_subdevice *s,
561 struct comedi_insn *insn,
564 struct vmk80xx_private *devpriv = dev->private;
565 unsigned char *tx_buf;
566 unsigned char *rx_buf;
570 down(&devpriv->limit_sem);
572 tx_buf = devpriv->usb_tx_buf;
573 rx_buf = devpriv->usb_rx_buf;
575 reg[0] = VMK8061_PWM_REG1;
576 reg[1] = VMK8061_PWM_REG2;
578 tx_buf[0] = VMK8061_CMD_RD_PWM;
580 for (n = 0; n < insn->n; n++) {
581 if (vmk80xx_read_packet(dev))
584 data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]];
587 up(&devpriv->limit_sem);
592 static int vmk80xx_pwm_insn_write(struct comedi_device *dev,
593 struct comedi_subdevice *s,
594 struct comedi_insn *insn,
597 struct vmk80xx_private *devpriv = dev->private;
598 unsigned char *tx_buf;
603 down(&devpriv->limit_sem);
605 tx_buf = devpriv->usb_tx_buf;
607 reg[0] = VMK8061_PWM_REG1;
608 reg[1] = VMK8061_PWM_REG2;
610 cmd = VMK8061_CMD_OUT_PWM;
613 * The followin piece of code was translated from the inline
614 * assembler code in the DLL source code.
617 * mov eax, k ; k is the value (data[n])
618 * and al, 03h ; al are the lower 8 bits of eax
619 * mov lo, al ; lo is the low part (tx_buf[reg[0]])
621 * shr eax, 2 ; right shift eax register by 2
622 * mov hi, al ; hi is the high part (tx_buf[reg[1]])
625 for (n = 0; n < insn->n; n++) {
626 tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03);
627 tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff;
629 if (vmk80xx_write_packet(dev, cmd))
633 up(&devpriv->limit_sem);
638 static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
640 struct vmk80xx_private *devpriv = dev->private;
641 struct usb_interface *intf = comedi_to_usb_interface(dev);
642 struct usb_host_interface *iface_desc = intf->cur_altsetting;
643 struct usb_endpoint_descriptor *ep_desc;
646 if (iface_desc->desc.bNumEndpoints != 2)
649 for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
650 ep_desc = &iface_desc->endpoint[i].desc;
652 if (usb_endpoint_is_int_in(ep_desc) ||
653 usb_endpoint_is_bulk_in(ep_desc)) {
655 devpriv->ep_rx = ep_desc;
659 if (usb_endpoint_is_int_out(ep_desc) ||
660 usb_endpoint_is_bulk_out(ep_desc)) {
662 devpriv->ep_tx = ep_desc;
667 if (!devpriv->ep_rx || !devpriv->ep_tx)
670 if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx))
676 static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev)
678 struct vmk80xx_private *devpriv = dev->private;
681 size = usb_endpoint_maxp(devpriv->ep_rx);
682 devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
683 if (!devpriv->usb_rx_buf)
686 size = usb_endpoint_maxp(devpriv->ep_tx);
687 devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
688 if (!devpriv->usb_tx_buf)
694 static int vmk80xx_init_subdevices(struct comedi_device *dev)
696 const struct vmk80xx_board *board = dev->board_ptr;
697 struct vmk80xx_private *devpriv = dev->private;
698 struct comedi_subdevice *s;
702 down(&devpriv->limit_sem);
704 if (devpriv->model == VMK8055_MODEL)
708 ret = comedi_alloc_subdevices(dev, n_subd);
710 up(&devpriv->limit_sem);
714 /* Analog input subdevice */
715 s = &dev->subdevices[0];
716 s->type = COMEDI_SUBD_AI;
717 s->subdev_flags = SDF_READABLE | SDF_GROUND;
718 s->n_chan = board->ai_nchans;
719 s->maxdata = board->ai_maxdata;
720 s->range_table = board->range;
721 s->insn_read = vmk80xx_ai_insn_read;
723 /* Analog output subdevice */
724 s = &dev->subdevices[1];
725 s->type = COMEDI_SUBD_AO;
726 s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
727 s->n_chan = board->ao_nchans;
729 s->range_table = board->range;
730 s->insn_write = vmk80xx_ao_insn_write;
731 if (devpriv->model == VMK8061_MODEL) {
732 s->subdev_flags |= SDF_READABLE;
733 s->insn_read = vmk80xx_ao_insn_read;
736 /* Digital input subdevice */
737 s = &dev->subdevices[2];
738 s->type = COMEDI_SUBD_DI;
739 s->subdev_flags = SDF_READABLE;
740 s->n_chan = board->di_nchans;
742 s->range_table = &range_digital;
743 s->insn_bits = vmk80xx_di_insn_bits;
745 /* Digital output subdevice */
746 s = &dev->subdevices[3];
747 s->type = COMEDI_SUBD_DO;
748 s->subdev_flags = SDF_WRITABLE;
751 s->range_table = &range_digital;
752 s->insn_bits = vmk80xx_do_insn_bits;
754 /* Counter subdevice */
755 s = &dev->subdevices[4];
756 s->type = COMEDI_SUBD_COUNTER;
757 s->subdev_flags = SDF_READABLE;
759 s->maxdata = board->cnt_maxdata;
760 s->insn_read = vmk80xx_cnt_insn_read;
761 s->insn_config = vmk80xx_cnt_insn_config;
762 if (devpriv->model == VMK8055_MODEL) {
763 s->subdev_flags |= SDF_WRITABLE;
764 s->insn_write = vmk80xx_cnt_insn_write;
768 if (devpriv->model == VMK8061_MODEL) {
769 s = &dev->subdevices[5];
770 s->type = COMEDI_SUBD_PWM;
771 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
772 s->n_chan = board->pwm_nchans;
773 s->maxdata = board->pwm_maxdata;
774 s->insn_read = vmk80xx_pwm_insn_read;
775 s->insn_write = vmk80xx_pwm_insn_write;
778 up(&devpriv->limit_sem);
783 static int vmk80xx_auto_attach(struct comedi_device *dev,
784 unsigned long context)
786 struct usb_interface *intf = comedi_to_usb_interface(dev);
787 const struct vmk80xx_board *board = NULL;
788 struct vmk80xx_private *devpriv;
791 if (context < ARRAY_SIZE(vmk80xx_boardinfo))
792 board = &vmk80xx_boardinfo[context];
795 dev->board_ptr = board;
796 dev->board_name = board->name;
798 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
802 devpriv->model = board->model;
804 sema_init(&devpriv->limit_sem, 8);
806 ret = vmk80xx_find_usb_endpoints(dev);
810 ret = vmk80xx_alloc_usb_buffers(dev);
814 usb_set_intfdata(intf, devpriv);
816 if (devpriv->model == VMK8055_MODEL)
817 vmk80xx_reset_device(dev);
819 return vmk80xx_init_subdevices(dev);
822 static void vmk80xx_detach(struct comedi_device *dev)
824 struct usb_interface *intf = comedi_to_usb_interface(dev);
825 struct vmk80xx_private *devpriv = dev->private;
830 down(&devpriv->limit_sem);
832 usb_set_intfdata(intf, NULL);
834 kfree(devpriv->usb_rx_buf);
835 kfree(devpriv->usb_tx_buf);
837 up(&devpriv->limit_sem);
840 static struct comedi_driver vmk80xx_driver = {
841 .module = THIS_MODULE,
842 .driver_name = "vmk80xx",
843 .auto_attach = vmk80xx_auto_attach,
844 .detach = vmk80xx_detach,
847 static int vmk80xx_usb_probe(struct usb_interface *intf,
848 const struct usb_device_id *id)
850 return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info);
853 static const struct usb_device_id vmk80xx_usb_id_table[] = {
854 { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 },
855 { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 },
856 { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 },
857 { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 },
858 { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 },
859 { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 },
860 { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 },
861 { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 },
862 { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 },
863 { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 },
864 { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 },
865 { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 },
868 MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table);
870 static struct usb_driver vmk80xx_usb_driver = {
872 .id_table = vmk80xx_usb_id_table,
873 .probe = vmk80xx_usb_probe,
874 .disconnect = comedi_usb_auto_unconfig,
876 module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver);
878 MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
879 MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
880 MODULE_LICENSE("GPL");