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