Commit | Line | Data |
---|---|---|
3faad673 MG |
1 | /* |
2 | comedi/drivers/vmk80xx.c | |
985cafcc | 3 | Velleman USB Board Low-Level Driver |
3faad673 MG |
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. | |
3faad673 MG |
19 | */ |
20 | /* | |
21 | Driver: vmk80xx | |
985cafcc MG |
22 | Description: Velleman USB Board Low-Level Driver |
23 | Devices: K8055/K8061 aka VM110/VM140 | |
3faad673 | 24 | Author: Manuel Gebele <forensixs@gmx.de> |
985cafcc | 25 | Updated: Sun, 10 May 2009 11:14:59 +0200 |
3faad673 | 26 | Status: works |
985cafcc MG |
27 | |
28 | Supports: | |
29 | - analog input | |
30 | - analog output | |
31 | - digital input | |
32 | - digital output | |
33 | - counter | |
34 | - pwm | |
35 | */ | |
3faad673 MG |
36 | |
37 | #include <linux/kernel.h> | |
3faad673 MG |
38 | #include <linux/module.h> |
39 | #include <linux/mutex.h> | |
40 | #include <linux/errno.h> | |
41 | #include <linux/input.h> | |
42 | #include <linux/slab.h> | |
43 | #include <linux/poll.h> | |
44 | #include <linux/usb.h> | |
985cafcc MG |
45 | #include <linux/uaccess.h> |
46 | ||
47 | #include "../comedidev.h" | |
48 | ||
985cafcc MG |
49 | enum { |
50 | DEVICE_VMK8055, | |
51 | DEVICE_VMK8061 | |
52 | }; | |
53 | ||
985cafcc MG |
54 | #define VMK8055_DI_REG 0x00 |
55 | #define VMK8055_DO_REG 0x01 | |
56 | #define VMK8055_AO1_REG 0x02 | |
57 | #define VMK8055_AO2_REG 0x03 | |
58 | #define VMK8055_AI1_REG 0x02 | |
59 | #define VMK8055_AI2_REG 0x03 | |
60 | #define VMK8055_CNT1_REG 0x04 | |
61 | #define VMK8055_CNT2_REG 0x06 | |
62 | ||
63 | #define VMK8061_CH_REG 0x01 | |
64 | #define VMK8061_DI_REG 0x01 | |
65 | #define VMK8061_DO_REG 0x01 | |
66 | #define VMK8061_PWM_REG1 0x01 | |
67 | #define VMK8061_PWM_REG2 0x02 | |
68 | #define VMK8061_CNT_REG 0x02 | |
69 | #define VMK8061_AO_REG 0x02 | |
70 | #define VMK8061_AI_REG1 0x02 | |
71 | #define VMK8061_AI_REG2 0x03 | |
72 | ||
73 | #define VMK8055_CMD_RST 0x00 | |
74 | #define VMK8055_CMD_DEB1_TIME 0x01 | |
75 | #define VMK8055_CMD_DEB2_TIME 0x02 | |
76 | #define VMK8055_CMD_RST_CNT1 0x03 | |
77 | #define VMK8055_CMD_RST_CNT2 0x04 | |
78 | #define VMK8055_CMD_WRT_AD 0x05 | |
79 | ||
80 | #define VMK8061_CMD_RD_AI 0x00 | |
0a85b6f0 | 81 | #define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */ |
985cafcc | 82 | #define VMK8061_CMD_SET_AO 0x02 |
0a85b6f0 | 83 | #define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */ |
985cafcc MG |
84 | #define VMK8061_CMD_OUT_PWM 0x04 |
85 | #define VMK8061_CMD_RD_DI 0x05 | |
0a85b6f0 | 86 | #define VMK8061_CMD_DO 0x06 /* !non-active! */ |
985cafcc MG |
87 | #define VMK8061_CMD_CLR_DO 0x07 |
88 | #define VMK8061_CMD_SET_DO 0x08 | |
0a85b6f0 MT |
89 | #define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */ |
90 | #define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */ | |
91 | #define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */ | |
92 | #define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */ | |
93 | #define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */ | |
985cafcc MG |
94 | #define VMK8061_CMD_RD_DO 0x0e |
95 | #define VMK8061_CMD_RD_AO 0x0f | |
96 | #define VMK8061_CMD_RD_PWM 0x10 | |
97 | ||
985cafcc MG |
98 | #define IC3_VERSION (1 << 0) |
99 | #define IC6_VERSION (1 << 1) | |
100 | ||
985cafcc MG |
101 | enum vmk80xx_model { |
102 | VMK8055_MODEL, | |
103 | VMK8061_MODEL | |
104 | }; | |
3faad673 | 105 | |
985cafcc | 106 | struct firmware_version { |
0a85b6f0 MT |
107 | unsigned char ic3_vers[32]; /* USB-Controller */ |
108 | unsigned char ic6_vers[32]; /* CPU */ | |
3faad673 MG |
109 | }; |
110 | ||
985cafcc | 111 | static const struct comedi_lrange vmk8061_range = { |
f45787c6 HS |
112 | 2, { |
113 | UNI_RANGE(5), | |
114 | UNI_RANGE(10) | |
115 | } | |
985cafcc | 116 | }; |
3faad673 | 117 | |
985cafcc MG |
118 | struct vmk80xx_board { |
119 | const char *name; | |
120 | enum vmk80xx_model model; | |
121 | const struct comedi_lrange *range; | |
658cd3ac HS |
122 | int ai_nchans; |
123 | unsigned int ai_maxdata; | |
8b3ec9f1 | 124 | int ao_nchans; |
268e5148 | 125 | int di_nchans; |
75a45d92 | 126 | unsigned int cnt_maxdata; |
9a23a748 HS |
127 | int pwm_nchans; |
128 | unsigned int pwm_maxdata; | |
985cafcc | 129 | }; |
3faad673 | 130 | |
20d60077 HS |
131 | static const struct vmk80xx_board vmk80xx_boardinfo[] = { |
132 | [DEVICE_VMK8055] = { | |
133 | .name = "K8055 (VM110)", | |
134 | .model = VMK8055_MODEL, | |
f45787c6 | 135 | .range = &range_unipolar5, |
658cd3ac HS |
136 | .ai_nchans = 2, |
137 | .ai_maxdata = 0x00ff, | |
8b3ec9f1 | 138 | .ao_nchans = 2, |
268e5148 | 139 | .di_nchans = 6, |
75a45d92 | 140 | .cnt_maxdata = 0xffff, |
20d60077 HS |
141 | }, |
142 | [DEVICE_VMK8061] = { | |
143 | .name = "K8061 (VM140)", | |
144 | .model = VMK8061_MODEL, | |
145 | .range = &vmk8061_range, | |
658cd3ac HS |
146 | .ai_nchans = 8, |
147 | .ai_maxdata = 0x03ff, | |
8b3ec9f1 | 148 | .ao_nchans = 8, |
268e5148 | 149 | .di_nchans = 8, |
75a45d92 | 150 | .cnt_maxdata = 0, /* unknown, device is not writeable */ |
9a23a748 HS |
151 | .pwm_nchans = 1, |
152 | .pwm_maxdata = 0x03ff, | |
20d60077 HS |
153 | }, |
154 | }; | |
155 | ||
dc49cbfc | 156 | struct vmk80xx_private { |
985cafcc MG |
157 | struct usb_endpoint_descriptor *ep_rx; |
158 | struct usb_endpoint_descriptor *ep_tx; | |
985cafcc MG |
159 | struct firmware_version fw; |
160 | struct semaphore limit_sem; | |
985cafcc MG |
161 | unsigned char *usb_rx_buf; |
162 | unsigned char *usb_tx_buf; | |
52d895d3 | 163 | enum vmk80xx_model model; |
985cafcc MG |
164 | }; |
165 | ||
0703c955 | 166 | static int vmk80xx_check_data_link(struct comedi_device *dev) |
3faad673 | 167 | { |
0703c955 | 168 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 169 | struct usb_device *usb = comedi_to_usb_dev(dev); |
3a229fd5 AH |
170 | unsigned int tx_pipe; |
171 | unsigned int rx_pipe; | |
172 | unsigned char tx[1]; | |
173 | unsigned char rx[2]; | |
985cafcc | 174 | |
da7b18ee HS |
175 | tx_pipe = usb_sndbulkpipe(usb, 0x01); |
176 | rx_pipe = usb_rcvbulkpipe(usb, 0x81); | |
3faad673 | 177 | |
985cafcc | 178 | tx[0] = VMK8061_CMD_RD_PWR_STAT; |
3faad673 | 179 | |
3a229fd5 AH |
180 | /* |
181 | * Check that IC6 (PIC16F871) is powered and | |
985cafcc | 182 | * running and the data link between IC3 and |
3a229fd5 AH |
183 | * IC6 is working properly |
184 | */ | |
da7b18ee HS |
185 | usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); |
186 | usb_bulk_msg(usb, rx_pipe, rx, 2, NULL, HZ * 10); | |
3faad673 | 187 | |
985cafcc | 188 | return (int)rx[1]; |
3faad673 MG |
189 | } |
190 | ||
0703c955 | 191 | static void vmk80xx_read_eeprom(struct comedi_device *dev, int flag) |
3faad673 | 192 | { |
0703c955 | 193 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 194 | struct usb_device *usb = comedi_to_usb_dev(dev); |
3a229fd5 AH |
195 | unsigned int tx_pipe; |
196 | unsigned int rx_pipe; | |
197 | unsigned char tx[1]; | |
198 | unsigned char rx[64]; | |
985cafcc | 199 | int cnt; |
3faad673 | 200 | |
da7b18ee HS |
201 | tx_pipe = usb_sndbulkpipe(usb, 0x01); |
202 | rx_pipe = usb_rcvbulkpipe(usb, 0x81); | |
3faad673 | 203 | |
985cafcc MG |
204 | tx[0] = VMK8061_CMD_RD_VERSION; |
205 | ||
3a229fd5 AH |
206 | /* |
207 | * Read the firmware version info of IC3 and | |
208 | * IC6 from the internal EEPROM of the IC | |
209 | */ | |
da7b18ee HS |
210 | usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); |
211 | usb_bulk_msg(usb, rx_pipe, rx, 64, &cnt, HZ * 10); | |
985cafcc MG |
212 | |
213 | rx[cnt] = '\0'; | |
214 | ||
215 | if (flag & IC3_VERSION) | |
da7b18ee | 216 | strncpy(devpriv->fw.ic3_vers, rx + 1, 24); |
0a85b6f0 | 217 | else /* IC6_VERSION */ |
da7b18ee | 218 | strncpy(devpriv->fw.ic6_vers, rx + 25, 24); |
985cafcc MG |
219 | } |
220 | ||
0703c955 | 221 | static void vmk80xx_do_bulk_msg(struct comedi_device *dev) |
985cafcc | 222 | { |
0703c955 | 223 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 224 | struct usb_device *usb = comedi_to_usb_dev(dev); |
3a229fd5 AH |
225 | __u8 tx_addr; |
226 | __u8 rx_addr; | |
227 | unsigned int tx_pipe; | |
228 | unsigned int rx_pipe; | |
985cafcc | 229 | size_t size; |
3faad673 | 230 | |
da7b18ee HS |
231 | tx_addr = devpriv->ep_tx->bEndpointAddress; |
232 | rx_addr = devpriv->ep_rx->bEndpointAddress; | |
233 | tx_pipe = usb_sndbulkpipe(usb, tx_addr); | |
234 | rx_pipe = usb_rcvbulkpipe(usb, rx_addr); | |
985cafcc | 235 | |
3a229fd5 AH |
236 | /* |
237 | * The max packet size attributes of the K8061 | |
238 | * input/output endpoints are identical | |
239 | */ | |
da7b18ee | 240 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); |
985cafcc | 241 | |
da7b18ee HS |
242 | usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, |
243 | size, NULL, devpriv->ep_tx->bInterval); | |
244 | usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10); | |
3faad673 MG |
245 | } |
246 | ||
0703c955 | 247 | static int vmk80xx_read_packet(struct comedi_device *dev) |
3faad673 | 248 | { |
0703c955 | 249 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 250 | struct usb_device *usb = comedi_to_usb_dev(dev); |
951348b3 IA |
251 | struct usb_endpoint_descriptor *ep; |
252 | unsigned int pipe; | |
3faad673 | 253 | |
52d895d3 | 254 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 255 | vmk80xx_do_bulk_msg(dev); |
985cafcc | 256 | return 0; |
3faad673 MG |
257 | } |
258 | ||
951348b3 IA |
259 | ep = devpriv->ep_rx; |
260 | pipe = usb_rcvintpipe(usb, ep->bEndpointAddress); | |
261 | return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf, | |
262 | le16_to_cpu(ep->wMaxPacketSize), NULL, | |
263 | HZ * 10); | |
3faad673 MG |
264 | } |
265 | ||
0703c955 | 266 | static int vmk80xx_write_packet(struct comedi_device *dev, int cmd) |
3faad673 | 267 | { |
0703c955 | 268 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 269 | struct usb_device *usb = comedi_to_usb_dev(dev); |
951348b3 IA |
270 | struct usb_endpoint_descriptor *ep; |
271 | unsigned int pipe; | |
3faad673 | 272 | |
951348b3 | 273 | devpriv->usb_tx_buf[0] = cmd; |
3faad673 | 274 | |
52d895d3 | 275 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 276 | vmk80xx_do_bulk_msg(dev); |
985cafcc | 277 | return 0; |
3faad673 MG |
278 | } |
279 | ||
951348b3 IA |
280 | ep = devpriv->ep_tx; |
281 | pipe = usb_sndintpipe(usb, ep->bEndpointAddress); | |
282 | return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf, | |
283 | le16_to_cpu(ep->wMaxPacketSize), NULL, | |
284 | HZ * 10); | |
3faad673 MG |
285 | } |
286 | ||
0703c955 | 287 | static int vmk80xx_reset_device(struct comedi_device *dev) |
e8f311a5 | 288 | { |
0703c955 | 289 | struct vmk80xx_private *devpriv = dev->private; |
e8f311a5 | 290 | size_t size; |
f06a23c9 | 291 | int retval; |
e8f311a5 IA |
292 | |
293 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
294 | memset(devpriv->usb_tx_buf, 0, size); | |
0703c955 | 295 | retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST); |
f06a23c9 IA |
296 | if (retval) |
297 | return retval; | |
298 | /* set outputs to known state as we cannot read them */ | |
0703c955 | 299 | return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD); |
e8f311a5 IA |
300 | } |
301 | ||
658cd3ac HS |
302 | static int vmk80xx_ai_insn_read(struct comedi_device *dev, |
303 | struct comedi_subdevice *s, | |
304 | struct comedi_insn *insn, | |
305 | unsigned int *data) | |
3faad673 | 306 | { |
da7b18ee | 307 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
308 | int chan; |
309 | int reg[2]; | |
985cafcc | 310 | int n; |
3faad673 | 311 | |
da7b18ee | 312 | down(&devpriv->limit_sem); |
985cafcc | 313 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 314 | |
52d895d3 | 315 | switch (devpriv->model) { |
985cafcc MG |
316 | case VMK8055_MODEL: |
317 | if (!chan) | |
318 | reg[0] = VMK8055_AI1_REG; | |
319 | else | |
320 | reg[0] = VMK8055_AI2_REG; | |
321 | break; | |
322 | case VMK8061_MODEL: | |
13f7952f | 323 | default: |
985cafcc MG |
324 | reg[0] = VMK8061_AI_REG1; |
325 | reg[1] = VMK8061_AI_REG2; | |
da7b18ee HS |
326 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI; |
327 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; | |
985cafcc | 328 | break; |
3faad673 MG |
329 | } |
330 | ||
985cafcc | 331 | for (n = 0; n < insn->n; n++) { |
0703c955 | 332 | if (vmk80xx_read_packet(dev)) |
985cafcc | 333 | break; |
3faad673 | 334 | |
52d895d3 | 335 | if (devpriv->model == VMK8055_MODEL) { |
da7b18ee | 336 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
985cafcc MG |
337 | continue; |
338 | } | |
3faad673 | 339 | |
985cafcc | 340 | /* VMK8061_MODEL */ |
da7b18ee HS |
341 | data[n] = devpriv->usb_rx_buf[reg[0]] + 256 * |
342 | devpriv->usb_rx_buf[reg[1]]; | |
985cafcc | 343 | } |
3faad673 | 344 | |
da7b18ee | 345 | up(&devpriv->limit_sem); |
3faad673 | 346 | |
985cafcc | 347 | return n; |
3faad673 MG |
348 | } |
349 | ||
8b3ec9f1 HS |
350 | static int vmk80xx_ao_insn_write(struct comedi_device *dev, |
351 | struct comedi_subdevice *s, | |
352 | struct comedi_insn *insn, | |
353 | unsigned int *data) | |
3faad673 | 354 | { |
da7b18ee | 355 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
356 | int chan; |
357 | int cmd; | |
358 | int reg; | |
985cafcc | 359 | int n; |
3faad673 | 360 | |
da7b18ee | 361 | down(&devpriv->limit_sem); |
985cafcc | 362 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 363 | |
52d895d3 | 364 | switch (devpriv->model) { |
985cafcc MG |
365 | case VMK8055_MODEL: |
366 | cmd = VMK8055_CMD_WRT_AD; | |
367 | if (!chan) | |
368 | reg = VMK8055_AO1_REG; | |
369 | else | |
370 | reg = VMK8055_AO2_REG; | |
371 | break; | |
0a85b6f0 | 372 | default: /* NOTE: avoid compiler warnings */ |
985cafcc MG |
373 | cmd = VMK8061_CMD_SET_AO; |
374 | reg = VMK8061_AO_REG; | |
da7b18ee | 375 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; |
985cafcc | 376 | break; |
3faad673 MG |
377 | } |
378 | ||
985cafcc | 379 | for (n = 0; n < insn->n; n++) { |
da7b18ee | 380 | devpriv->usb_tx_buf[reg] = data[n]; |
3faad673 | 381 | |
0703c955 | 382 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc | 383 | break; |
3faad673 MG |
384 | } |
385 | ||
da7b18ee | 386 | up(&devpriv->limit_sem); |
3faad673 | 387 | |
985cafcc | 388 | return n; |
3faad673 MG |
389 | } |
390 | ||
8b3ec9f1 HS |
391 | static int vmk80xx_ao_insn_read(struct comedi_device *dev, |
392 | struct comedi_subdevice *s, | |
393 | struct comedi_insn *insn, | |
394 | unsigned int *data) | |
3faad673 | 395 | { |
da7b18ee | 396 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
397 | int chan; |
398 | int reg; | |
985cafcc | 399 | int n; |
3faad673 | 400 | |
da7b18ee | 401 | down(&devpriv->limit_sem); |
985cafcc | 402 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 403 | |
985cafcc | 404 | reg = VMK8061_AO_REG - 1; |
3faad673 | 405 | |
da7b18ee | 406 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO; |
985cafcc MG |
407 | |
408 | for (n = 0; n < insn->n; n++) { | |
0703c955 | 409 | if (vmk80xx_read_packet(dev)) |
985cafcc MG |
410 | break; |
411 | ||
da7b18ee | 412 | data[n] = devpriv->usb_rx_buf[reg + chan]; |
3faad673 MG |
413 | } |
414 | ||
da7b18ee | 415 | up(&devpriv->limit_sem); |
3faad673 | 416 | |
985cafcc MG |
417 | return n; |
418 | } | |
3faad673 | 419 | |
268e5148 HS |
420 | static int vmk80xx_di_insn_bits(struct comedi_device *dev, |
421 | struct comedi_subdevice *s, | |
422 | struct comedi_insn *insn, | |
423 | unsigned int *data) | |
c647ed56 | 424 | { |
da7b18ee | 425 | struct vmk80xx_private *devpriv = dev->private; |
c647ed56 AH |
426 | unsigned char *rx_buf; |
427 | int reg; | |
428 | int retval; | |
429 | ||
da7b18ee | 430 | down(&devpriv->limit_sem); |
c647ed56 | 431 | |
da7b18ee | 432 | rx_buf = devpriv->usb_rx_buf; |
c647ed56 | 433 | |
52d895d3 | 434 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 | 435 | reg = VMK8061_DI_REG; |
da7b18ee | 436 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI; |
c647ed56 AH |
437 | } else { |
438 | reg = VMK8055_DI_REG; | |
439 | } | |
440 | ||
0703c955 | 441 | retval = vmk80xx_read_packet(dev); |
c647ed56 AH |
442 | |
443 | if (!retval) { | |
52d895d3 | 444 | if (devpriv->model == VMK8055_MODEL) |
c647ed56 AH |
445 | data[1] = (((rx_buf[reg] >> 4) & 0x03) | |
446 | ((rx_buf[reg] << 2) & 0x04) | | |
447 | ((rx_buf[reg] >> 3) & 0x18)); | |
448 | else | |
449 | data[1] = rx_buf[reg]; | |
450 | ||
451 | retval = 2; | |
452 | } | |
453 | ||
da7b18ee | 454 | up(&devpriv->limit_sem); |
c647ed56 AH |
455 | |
456 | return retval; | |
457 | } | |
458 | ||
b639e096 HS |
459 | static int vmk80xx_do_insn_bits(struct comedi_device *dev, |
460 | struct comedi_subdevice *s, | |
461 | struct comedi_insn *insn, | |
462 | unsigned int *data) | |
c647ed56 | 463 | { |
da7b18ee | 464 | struct vmk80xx_private *devpriv = dev->private; |
6f617e54 HS |
465 | unsigned char *rx_buf = devpriv->usb_rx_buf; |
466 | unsigned char *tx_buf = devpriv->usb_tx_buf; | |
951348b3 | 467 | int reg, cmd; |
c16975a0 | 468 | int ret = 0; |
c647ed56 | 469 | |
fc9ca48e | 470 | if (devpriv->model == VMK8061_MODEL) { |
fc9ca48e PH |
471 | reg = VMK8061_DO_REG; |
472 | cmd = VMK8061_CMD_DO; | |
473 | } else { /* VMK8055_MODEL */ | |
474 | reg = VMK8055_DO_REG; | |
475 | cmd = VMK8055_CMD_WRT_AD; | |
476 | } | |
c647ed56 | 477 | |
da7b18ee | 478 | down(&devpriv->limit_sem); |
c647ed56 | 479 | |
6f617e54 HS |
480 | if (comedi_dio_update_state(s, data)) { |
481 | tx_buf[reg] = s->state; | |
482 | ret = vmk80xx_write_packet(dev, cmd); | |
483 | if (ret) | |
c647ed56 AH |
484 | goto out; |
485 | } | |
486 | ||
52d895d3 | 487 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 | 488 | tx_buf[0] = VMK8061_CMD_RD_DO; |
6f617e54 HS |
489 | ret = vmk80xx_read_packet(dev); |
490 | if (ret) | |
491 | goto out; | |
492 | data[1] = rx_buf[reg]; | |
c647ed56 | 493 | } else { |
6f617e54 | 494 | data[1] = s->state; |
c647ed56 AH |
495 | } |
496 | ||
497 | out: | |
da7b18ee | 498 | up(&devpriv->limit_sem); |
c647ed56 | 499 | |
6f617e54 | 500 | return ret ? ret : insn->n; |
c647ed56 AH |
501 | } |
502 | ||
75a45d92 HS |
503 | static int vmk80xx_cnt_insn_read(struct comedi_device *dev, |
504 | struct comedi_subdevice *s, | |
505 | struct comedi_insn *insn, | |
506 | unsigned int *data) | |
985cafcc | 507 | { |
da7b18ee | 508 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
509 | int chan; |
510 | int reg[2]; | |
985cafcc MG |
511 | int n; |
512 | ||
da7b18ee | 513 | down(&devpriv->limit_sem); |
985cafcc | 514 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 515 | |
52d895d3 | 516 | switch (devpriv->model) { |
985cafcc MG |
517 | case VMK8055_MODEL: |
518 | if (!chan) | |
519 | reg[0] = VMK8055_CNT1_REG; | |
520 | else | |
521 | reg[0] = VMK8055_CNT2_REG; | |
522 | break; | |
523 | case VMK8061_MODEL: | |
13f7952f | 524 | default: |
985cafcc MG |
525 | reg[0] = VMK8061_CNT_REG; |
526 | reg[1] = VMK8061_CNT_REG; | |
da7b18ee | 527 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; |
985cafcc | 528 | break; |
3faad673 MG |
529 | } |
530 | ||
985cafcc | 531 | for (n = 0; n < insn->n; n++) { |
0703c955 | 532 | if (vmk80xx_read_packet(dev)) |
985cafcc | 533 | break; |
3faad673 | 534 | |
52d895d3 | 535 | if (devpriv->model == VMK8055_MODEL) |
da7b18ee | 536 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
3a229fd5 | 537 | else /* VMK8061_MODEL */ |
da7b18ee HS |
538 | data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1] |
539 | + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2]; | |
985cafcc MG |
540 | } |
541 | ||
da7b18ee | 542 | up(&devpriv->limit_sem); |
985cafcc MG |
543 | |
544 | return n; | |
3faad673 MG |
545 | } |
546 | ||
75a45d92 HS |
547 | static int vmk80xx_cnt_insn_config(struct comedi_device *dev, |
548 | struct comedi_subdevice *s, | |
549 | struct comedi_insn *insn, | |
550 | unsigned int *data) | |
3faad673 | 551 | { |
da7b18ee | 552 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 553 | unsigned int insn_cmd; |
3a229fd5 AH |
554 | int chan; |
555 | int cmd; | |
556 | int reg; | |
985cafcc | 557 | int n; |
3faad673 | 558 | |
985cafcc MG |
559 | insn_cmd = data[0]; |
560 | if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET) | |
561 | return -EINVAL; | |
3faad673 | 562 | |
da7b18ee | 563 | down(&devpriv->limit_sem); |
8f9064a8 | 564 | |
985cafcc | 565 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 566 | |
52d895d3 | 567 | if (devpriv->model == VMK8055_MODEL) { |
985cafcc MG |
568 | if (!chan) { |
569 | cmd = VMK8055_CMD_RST_CNT1; | |
570 | reg = VMK8055_CNT1_REG; | |
571 | } else { | |
572 | cmd = VMK8055_CMD_RST_CNT2; | |
573 | reg = VMK8055_CNT2_REG; | |
574 | } | |
575 | ||
da7b18ee | 576 | devpriv->usb_tx_buf[reg] = 0x00; |
3a229fd5 | 577 | } else { |
985cafcc | 578 | cmd = VMK8061_CMD_RST_CNT; |
3a229fd5 | 579 | } |
985cafcc MG |
580 | |
581 | for (n = 0; n < insn->n; n++) | |
0703c955 | 582 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc MG |
583 | break; |
584 | ||
da7b18ee | 585 | up(&devpriv->limit_sem); |
985cafcc MG |
586 | |
587 | return n; | |
588 | } | |
589 | ||
75a45d92 HS |
590 | static int vmk80xx_cnt_insn_write(struct comedi_device *dev, |
591 | struct comedi_subdevice *s, | |
592 | struct comedi_insn *insn, | |
593 | unsigned int *data) | |
985cafcc | 594 | { |
da7b18ee | 595 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
596 | unsigned long debtime; |
597 | unsigned long val; | |
598 | int chan; | |
599 | int cmd; | |
985cafcc MG |
600 | int n; |
601 | ||
da7b18ee | 602 | down(&devpriv->limit_sem); |
985cafcc MG |
603 | chan = CR_CHAN(insn->chanspec); |
604 | ||
605 | if (!chan) | |
606 | cmd = VMK8055_CMD_DEB1_TIME; | |
607 | else | |
608 | cmd = VMK8055_CMD_DEB2_TIME; | |
609 | ||
610 | for (n = 0; n < insn->n; n++) { | |
611 | debtime = data[n]; | |
3faad673 MG |
612 | if (debtime == 0) |
613 | debtime = 1; | |
985cafcc MG |
614 | |
615 | /* TODO: Prevent overflows */ | |
616 | if (debtime > 7450) | |
617 | debtime = 7450; | |
618 | ||
3faad673 MG |
619 | val = int_sqrt(debtime * 1000 / 115); |
620 | if (((val + 1) * val) < debtime * 1000 / 115) | |
621 | val += 1; | |
622 | ||
da7b18ee | 623 | devpriv->usb_tx_buf[6 + chan] = val; |
3faad673 | 624 | |
0703c955 | 625 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc | 626 | break; |
3faad673 MG |
627 | } |
628 | ||
da7b18ee | 629 | up(&devpriv->limit_sem); |
3faad673 | 630 | |
985cafcc MG |
631 | return n; |
632 | } | |
3faad673 | 633 | |
9a23a748 HS |
634 | static int vmk80xx_pwm_insn_read(struct comedi_device *dev, |
635 | struct comedi_subdevice *s, | |
636 | struct comedi_insn *insn, | |
637 | unsigned int *data) | |
985cafcc | 638 | { |
da7b18ee HS |
639 | struct vmk80xx_private *devpriv = dev->private; |
640 | unsigned char *tx_buf; | |
641 | unsigned char *rx_buf; | |
985cafcc MG |
642 | int reg[2]; |
643 | int n; | |
644 | ||
da7b18ee HS |
645 | down(&devpriv->limit_sem); |
646 | ||
647 | tx_buf = devpriv->usb_tx_buf; | |
648 | rx_buf = devpriv->usb_rx_buf; | |
985cafcc MG |
649 | |
650 | reg[0] = VMK8061_PWM_REG1; | |
651 | reg[1] = VMK8061_PWM_REG2; | |
652 | ||
da7b18ee | 653 | tx_buf[0] = VMK8061_CMD_RD_PWM; |
985cafcc MG |
654 | |
655 | for (n = 0; n < insn->n; n++) { | |
0703c955 | 656 | if (vmk80xx_read_packet(dev)) |
985cafcc MG |
657 | break; |
658 | ||
da7b18ee | 659 | data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; |
985cafcc MG |
660 | } |
661 | ||
da7b18ee | 662 | up(&devpriv->limit_sem); |
985cafcc MG |
663 | |
664 | return n; | |
3faad673 MG |
665 | } |
666 | ||
9a23a748 HS |
667 | static int vmk80xx_pwm_insn_write(struct comedi_device *dev, |
668 | struct comedi_subdevice *s, | |
669 | struct comedi_insn *insn, | |
670 | unsigned int *data) | |
985cafcc | 671 | { |
da7b18ee | 672 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 673 | unsigned char *tx_buf; |
3a229fd5 AH |
674 | int reg[2]; |
675 | int cmd; | |
985cafcc | 676 | int n; |
3faad673 | 677 | |
da7b18ee | 678 | down(&devpriv->limit_sem); |
985cafcc | 679 | |
da7b18ee | 680 | tx_buf = devpriv->usb_tx_buf; |
985cafcc MG |
681 | |
682 | reg[0] = VMK8061_PWM_REG1; | |
683 | reg[1] = VMK8061_PWM_REG2; | |
684 | ||
685 | cmd = VMK8061_CMD_OUT_PWM; | |
686 | ||
687 | /* | |
688 | * The followin piece of code was translated from the inline | |
689 | * assembler code in the DLL source code. | |
690 | * | |
691 | * asm | |
692 | * mov eax, k ; k is the value (data[n]) | |
693 | * and al, 03h ; al are the lower 8 bits of eax | |
694 | * mov lo, al ; lo is the low part (tx_buf[reg[0]]) | |
695 | * mov eax, k | |
696 | * shr eax, 2 ; right shift eax register by 2 | |
697 | * mov hi, al ; hi is the high part (tx_buf[reg[1]]) | |
698 | * end; | |
699 | */ | |
700 | for (n = 0; n < insn->n; n++) { | |
701 | tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); | |
702 | tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; | |
703 | ||
0703c955 | 704 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc MG |
705 | break; |
706 | } | |
3faad673 | 707 | |
da7b18ee | 708 | up(&devpriv->limit_sem); |
985cafcc MG |
709 | |
710 | return n; | |
711 | } | |
712 | ||
57cf09ae | 713 | static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) |
49253d54 | 714 | { |
57cf09ae | 715 | struct vmk80xx_private *devpriv = dev->private; |
e23322e4 | 716 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
49253d54 HS |
717 | struct usb_host_interface *iface_desc = intf->cur_altsetting; |
718 | struct usb_endpoint_descriptor *ep_desc; | |
719 | int i; | |
720 | ||
721 | if (iface_desc->desc.bNumEndpoints != 2) | |
722 | return -ENODEV; | |
723 | ||
724 | for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { | |
725 | ep_desc = &iface_desc->endpoint[i].desc; | |
726 | ||
727 | if (usb_endpoint_is_int_in(ep_desc) || | |
728 | usb_endpoint_is_bulk_in(ep_desc)) { | |
729 | if (!devpriv->ep_rx) | |
730 | devpriv->ep_rx = ep_desc; | |
731 | continue; | |
732 | } | |
733 | ||
734 | if (usb_endpoint_is_int_out(ep_desc) || | |
735 | usb_endpoint_is_bulk_out(ep_desc)) { | |
736 | if (!devpriv->ep_tx) | |
737 | devpriv->ep_tx = ep_desc; | |
738 | continue; | |
739 | } | |
740 | } | |
741 | ||
742 | if (!devpriv->ep_rx || !devpriv->ep_tx) | |
743 | return -ENODEV; | |
744 | ||
745 | return 0; | |
746 | } | |
747 | ||
57cf09ae | 748 | static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) |
78f8fa7f | 749 | { |
57cf09ae | 750 | struct vmk80xx_private *devpriv = dev->private; |
78f8fa7f HS |
751 | size_t size; |
752 | ||
753 | size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize); | |
0cbfc826 | 754 | devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
755 | if (!devpriv->usb_rx_buf) |
756 | return -ENOMEM; | |
757 | ||
758 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
0cbfc826 | 759 | devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
760 | if (!devpriv->usb_tx_buf) { |
761 | kfree(devpriv->usb_rx_buf); | |
762 | return -ENOMEM; | |
763 | } | |
764 | ||
765 | return 0; | |
766 | } | |
767 | ||
66dbc7b1 | 768 | static int vmk80xx_init_subdevices(struct comedi_device *dev) |
3faad673 | 769 | { |
57cf09ae HS |
770 | const struct vmk80xx_board *boardinfo = comedi_board(dev); |
771 | struct vmk80xx_private *devpriv = dev->private; | |
b153d83e | 772 | struct comedi_subdevice *s; |
57cf09ae | 773 | int n_subd; |
8b6c5694 | 774 | int ret; |
3faad673 | 775 | |
da7b18ee | 776 | down(&devpriv->limit_sem); |
0dd772bf | 777 | |
52d895d3 | 778 | if (devpriv->model == VMK8055_MODEL) |
985cafcc MG |
779 | n_subd = 5; |
780 | else | |
781 | n_subd = 6; | |
da7b18ee | 782 | ret = comedi_alloc_subdevices(dev, n_subd); |
8b6c5694 | 783 | if (ret) { |
da7b18ee | 784 | up(&devpriv->limit_sem); |
8b6c5694 | 785 | return ret; |
3faad673 | 786 | } |
0dd772bf | 787 | |
985cafcc | 788 | /* Analog input subdevice */ |
da7b18ee | 789 | s = &dev->subdevices[0]; |
658cd3ac HS |
790 | s->type = COMEDI_SUBD_AI; |
791 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
792 | s->n_chan = boardinfo->ai_nchans; | |
793 | s->maxdata = boardinfo->ai_maxdata; | |
794 | s->range_table = boardinfo->range; | |
795 | s->insn_read = vmk80xx_ai_insn_read; | |
0dd772bf | 796 | |
985cafcc | 797 | /* Analog output subdevice */ |
da7b18ee | 798 | s = &dev->subdevices[1]; |
8b3ec9f1 HS |
799 | s->type = COMEDI_SUBD_AO; |
800 | s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; | |
801 | s->n_chan = boardinfo->ao_nchans; | |
802 | s->maxdata = 0x00ff; | |
803 | s->range_table = boardinfo->range; | |
804 | s->insn_write = vmk80xx_ao_insn_write; | |
52d895d3 | 805 | if (devpriv->model == VMK8061_MODEL) { |
8b3ec9f1 HS |
806 | s->subdev_flags |= SDF_READABLE; |
807 | s->insn_read = vmk80xx_ao_insn_read; | |
985cafcc | 808 | } |
0dd772bf | 809 | |
985cafcc | 810 | /* Digital input subdevice */ |
da7b18ee | 811 | s = &dev->subdevices[2]; |
268e5148 HS |
812 | s->type = COMEDI_SUBD_DI; |
813 | s->subdev_flags = SDF_READABLE; | |
814 | s->n_chan = boardinfo->di_nchans; | |
815 | s->maxdata = 1; | |
816 | s->range_table = &range_digital; | |
268e5148 | 817 | s->insn_bits = vmk80xx_di_insn_bits; |
0dd772bf | 818 | |
985cafcc | 819 | /* Digital output subdevice */ |
da7b18ee | 820 | s = &dev->subdevices[3]; |
b639e096 HS |
821 | s->type = COMEDI_SUBD_DO; |
822 | s->subdev_flags = SDF_WRITEABLE; | |
823 | s->n_chan = 8; | |
824 | s->maxdata = 1; | |
825 | s->range_table = &range_digital; | |
b639e096 | 826 | s->insn_bits = vmk80xx_do_insn_bits; |
0dd772bf | 827 | |
985cafcc | 828 | /* Counter subdevice */ |
da7b18ee | 829 | s = &dev->subdevices[4]; |
75a45d92 HS |
830 | s->type = COMEDI_SUBD_COUNTER; |
831 | s->subdev_flags = SDF_READABLE; | |
832 | s->n_chan = 2; | |
833 | s->maxdata = boardinfo->cnt_maxdata; | |
834 | s->insn_read = vmk80xx_cnt_insn_read; | |
835 | s->insn_config = vmk80xx_cnt_insn_config; | |
52d895d3 | 836 | if (devpriv->model == VMK8055_MODEL) { |
75a45d92 HS |
837 | s->subdev_flags |= SDF_WRITEABLE; |
838 | s->insn_write = vmk80xx_cnt_insn_write; | |
985cafcc | 839 | } |
0dd772bf | 840 | |
985cafcc | 841 | /* PWM subdevice */ |
52d895d3 | 842 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee | 843 | s = &dev->subdevices[5]; |
9a23a748 HS |
844 | s->type = COMEDI_SUBD_PWM; |
845 | s->subdev_flags = SDF_READABLE | SDF_WRITEABLE; | |
846 | s->n_chan = boardinfo->pwm_nchans; | |
847 | s->maxdata = boardinfo->pwm_maxdata; | |
848 | s->insn_read = vmk80xx_pwm_insn_read; | |
849 | s->insn_write = vmk80xx_pwm_insn_write; | |
985cafcc | 850 | } |
0dd772bf | 851 | |
da7b18ee | 852 | up(&devpriv->limit_sem); |
0dd772bf | 853 | |
f7d4d3bc IA |
854 | return 0; |
855 | } | |
3faad673 | 856 | |
da7b18ee | 857 | static int vmk80xx_auto_attach(struct comedi_device *dev, |
57cf09ae | 858 | unsigned long context) |
f7d4d3bc | 859 | { |
da7b18ee | 860 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
0dd772bf | 861 | const struct vmk80xx_board *boardinfo; |
da7b18ee | 862 | struct vmk80xx_private *devpriv; |
49253d54 | 863 | int ret; |
3faad673 | 864 | |
57cf09ae HS |
865 | boardinfo = &vmk80xx_boardinfo[context]; |
866 | dev->board_ptr = boardinfo; | |
867 | dev->board_name = boardinfo->name; | |
3faad673 | 868 | |
0bdab509 | 869 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
57cf09ae HS |
870 | if (!devpriv) |
871 | return -ENOMEM; | |
3faad673 | 872 | |
52d895d3 | 873 | devpriv->model = boardinfo->model; |
985cafcc | 874 | |
57cf09ae | 875 | ret = vmk80xx_find_usb_endpoints(dev); |
db7dabf7 | 876 | if (ret) |
57cf09ae | 877 | return ret; |
3faad673 | 878 | |
57cf09ae | 879 | ret = vmk80xx_alloc_usb_buffers(dev); |
db7dabf7 | 880 | if (ret) |
57cf09ae | 881 | return ret; |
985cafcc | 882 | |
da7b18ee | 883 | sema_init(&devpriv->limit_sem, 8); |
985cafcc | 884 | |
da7b18ee | 885 | usb_set_intfdata(intf, devpriv); |
985cafcc | 886 | |
52d895d3 | 887 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 888 | vmk80xx_read_eeprom(dev, IC3_VERSION); |
da7b18ee | 889 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic3_vers); |
985cafcc | 890 | |
0703c955 HS |
891 | if (vmk80xx_check_data_link(dev)) { |
892 | vmk80xx_read_eeprom(dev, IC6_VERSION); | |
da7b18ee | 893 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic6_vers); |
3a229fd5 | 894 | } |
3faad673 MG |
895 | } |
896 | ||
52d895d3 | 897 | if (devpriv->model == VMK8055_MODEL) |
0703c955 | 898 | vmk80xx_reset_device(dev); |
3faad673 | 899 | |
66dbc7b1 | 900 | return vmk80xx_init_subdevices(dev); |
57cf09ae | 901 | } |
3faad673 | 902 | |
57cf09ae HS |
903 | static void vmk80xx_detach(struct comedi_device *dev) |
904 | { | |
e23322e4 | 905 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
57cf09ae | 906 | struct vmk80xx_private *devpriv = dev->private; |
8ba69ce4 | 907 | |
57cf09ae HS |
908 | if (!devpriv) |
909 | return; | |
db7dabf7 | 910 | |
57cf09ae HS |
911 | down(&devpriv->limit_sem); |
912 | ||
e23322e4 | 913 | usb_set_intfdata(intf, NULL); |
57cf09ae | 914 | |
57cf09ae HS |
915 | kfree(devpriv->usb_rx_buf); |
916 | kfree(devpriv->usb_tx_buf); | |
917 | ||
918 | up(&devpriv->limit_sem); | |
919 | } | |
920 | ||
921 | static struct comedi_driver vmk80xx_driver = { | |
922 | .module = THIS_MODULE, | |
923 | .driver_name = "vmk80xx", | |
924 | .auto_attach = vmk80xx_auto_attach, | |
925 | .detach = vmk80xx_detach, | |
926 | }; | |
927 | ||
928 | static int vmk80xx_usb_probe(struct usb_interface *intf, | |
929 | const struct usb_device_id *id) | |
930 | { | |
931 | return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info); | |
3faad673 MG |
932 | } |
933 | ||
007ff2af HS |
934 | static const struct usb_device_id vmk80xx_usb_id_table[] = { |
935 | { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 }, | |
936 | { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 }, | |
937 | { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 }, | |
938 | { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 }, | |
939 | { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 }, | |
940 | { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 }, | |
941 | { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 }, | |
942 | { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 }, | |
943 | { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 }, | |
944 | { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 }, | |
945 | { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 }, | |
946 | { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 }, | |
947 | { } | |
948 | }; | |
949 | MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table); | |
950 | ||
d6cc3ec8 HS |
951 | static struct usb_driver vmk80xx_usb_driver = { |
952 | .name = "vmk80xx", | |
007ff2af | 953 | .id_table = vmk80xx_usb_id_table, |
ce874227 HS |
954 | .probe = vmk80xx_usb_probe, |
955 | .disconnect = comedi_usb_auto_unconfig, | |
3faad673 | 956 | }; |
d6cc3ec8 | 957 | module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); |
007ff2af HS |
958 | |
959 | MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); | |
960 | MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); | |
961 | MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); | |
962 | MODULE_VERSION("0.8.01"); | |
963 | MODULE_LICENSE("GPL"); |