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 | /* | |
b1ad9684 IA |
21 | * Driver: vmk80xx |
22 | * Description: Velleman USB Board Low-Level Driver | |
23 | * Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140), | |
24 | * VM110 (K8055/VM110), VM140 (K8061/VM140) | |
25 | * Author: Manuel Gebele <forensixs@gmx.de> | |
26 | * Updated: Sun, 10 May 2009 11:14:59 +0200 | |
27 | * Status: works | |
28 | * | |
29 | * Supports: | |
30 | * - analog input | |
31 | * - analog output | |
32 | * - digital input | |
33 | * - digital output | |
34 | * - counter | |
35 | * - pwm | |
36 | */ | |
3faad673 MG |
37 | |
38 | #include <linux/kernel.h> | |
3faad673 MG |
39 | #include <linux/module.h> |
40 | #include <linux/mutex.h> | |
41 | #include <linux/errno.h> | |
42 | #include <linux/input.h> | |
43 | #include <linux/slab.h> | |
44 | #include <linux/poll.h> | |
985cafcc MG |
45 | #include <linux/uaccess.h> |
46 | ||
cf7661c7 | 47 | #include "../comedi_usb.h" |
985cafcc | 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; |
a4a75b21 | 553 | unsigned int chan = CR_CHAN(insn->chanspec); |
3a229fd5 AH |
554 | int cmd; |
555 | int reg; | |
a4a75b21 | 556 | int ret; |
3faad673 | 557 | |
da7b18ee | 558 | down(&devpriv->limit_sem); |
a4a75b21 HS |
559 | switch (data[0]) { |
560 | case INSN_CONFIG_RESET: | |
561 | if (devpriv->model == VMK8055_MODEL) { | |
562 | if (!chan) { | |
563 | cmd = VMK8055_CMD_RST_CNT1; | |
564 | reg = VMK8055_CNT1_REG; | |
565 | } else { | |
566 | cmd = VMK8055_CMD_RST_CNT2; | |
567 | reg = VMK8055_CNT2_REG; | |
568 | } | |
569 | devpriv->usb_tx_buf[reg] = 0x00; | |
985cafcc | 570 | } else { |
a4a75b21 | 571 | cmd = VMK8061_CMD_RST_CNT; |
985cafcc | 572 | } |
a4a75b21 HS |
573 | ret = vmk80xx_write_packet(dev, cmd); |
574 | break; | |
575 | default: | |
576 | ret = -EINVAL; | |
577 | break; | |
3a229fd5 | 578 | } |
da7b18ee | 579 | up(&devpriv->limit_sem); |
985cafcc | 580 | |
a4a75b21 | 581 | return ret ? ret : insn->n; |
985cafcc MG |
582 | } |
583 | ||
75a45d92 HS |
584 | static int vmk80xx_cnt_insn_write(struct comedi_device *dev, |
585 | struct comedi_subdevice *s, | |
586 | struct comedi_insn *insn, | |
587 | unsigned int *data) | |
985cafcc | 588 | { |
da7b18ee | 589 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
590 | unsigned long debtime; |
591 | unsigned long val; | |
592 | int chan; | |
593 | int cmd; | |
985cafcc MG |
594 | int n; |
595 | ||
da7b18ee | 596 | down(&devpriv->limit_sem); |
985cafcc MG |
597 | chan = CR_CHAN(insn->chanspec); |
598 | ||
599 | if (!chan) | |
600 | cmd = VMK8055_CMD_DEB1_TIME; | |
601 | else | |
602 | cmd = VMK8055_CMD_DEB2_TIME; | |
603 | ||
604 | for (n = 0; n < insn->n; n++) { | |
605 | debtime = data[n]; | |
3faad673 MG |
606 | if (debtime == 0) |
607 | debtime = 1; | |
985cafcc MG |
608 | |
609 | /* TODO: Prevent overflows */ | |
610 | if (debtime > 7450) | |
611 | debtime = 7450; | |
612 | ||
3faad673 MG |
613 | val = int_sqrt(debtime * 1000 / 115); |
614 | if (((val + 1) * val) < debtime * 1000 / 115) | |
615 | val += 1; | |
616 | ||
da7b18ee | 617 | devpriv->usb_tx_buf[6 + chan] = val; |
3faad673 | 618 | |
0703c955 | 619 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc | 620 | break; |
3faad673 MG |
621 | } |
622 | ||
da7b18ee | 623 | up(&devpriv->limit_sem); |
3faad673 | 624 | |
985cafcc MG |
625 | return n; |
626 | } | |
3faad673 | 627 | |
9a23a748 HS |
628 | static int vmk80xx_pwm_insn_read(struct comedi_device *dev, |
629 | struct comedi_subdevice *s, | |
630 | struct comedi_insn *insn, | |
631 | unsigned int *data) | |
985cafcc | 632 | { |
da7b18ee HS |
633 | struct vmk80xx_private *devpriv = dev->private; |
634 | unsigned char *tx_buf; | |
635 | unsigned char *rx_buf; | |
985cafcc MG |
636 | int reg[2]; |
637 | int n; | |
638 | ||
da7b18ee HS |
639 | down(&devpriv->limit_sem); |
640 | ||
641 | tx_buf = devpriv->usb_tx_buf; | |
642 | rx_buf = devpriv->usb_rx_buf; | |
985cafcc MG |
643 | |
644 | reg[0] = VMK8061_PWM_REG1; | |
645 | reg[1] = VMK8061_PWM_REG2; | |
646 | ||
da7b18ee | 647 | tx_buf[0] = VMK8061_CMD_RD_PWM; |
985cafcc MG |
648 | |
649 | for (n = 0; n < insn->n; n++) { | |
0703c955 | 650 | if (vmk80xx_read_packet(dev)) |
985cafcc MG |
651 | break; |
652 | ||
da7b18ee | 653 | data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; |
985cafcc MG |
654 | } |
655 | ||
da7b18ee | 656 | up(&devpriv->limit_sem); |
985cafcc MG |
657 | |
658 | return n; | |
3faad673 MG |
659 | } |
660 | ||
9a23a748 HS |
661 | static int vmk80xx_pwm_insn_write(struct comedi_device *dev, |
662 | struct comedi_subdevice *s, | |
663 | struct comedi_insn *insn, | |
664 | unsigned int *data) | |
985cafcc | 665 | { |
da7b18ee | 666 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 667 | unsigned char *tx_buf; |
3a229fd5 AH |
668 | int reg[2]; |
669 | int cmd; | |
985cafcc | 670 | int n; |
3faad673 | 671 | |
da7b18ee | 672 | down(&devpriv->limit_sem); |
985cafcc | 673 | |
da7b18ee | 674 | tx_buf = devpriv->usb_tx_buf; |
985cafcc MG |
675 | |
676 | reg[0] = VMK8061_PWM_REG1; | |
677 | reg[1] = VMK8061_PWM_REG2; | |
678 | ||
679 | cmd = VMK8061_CMD_OUT_PWM; | |
680 | ||
681 | /* | |
682 | * The followin piece of code was translated from the inline | |
683 | * assembler code in the DLL source code. | |
684 | * | |
685 | * asm | |
686 | * mov eax, k ; k is the value (data[n]) | |
687 | * and al, 03h ; al are the lower 8 bits of eax | |
688 | * mov lo, al ; lo is the low part (tx_buf[reg[0]]) | |
689 | * mov eax, k | |
690 | * shr eax, 2 ; right shift eax register by 2 | |
691 | * mov hi, al ; hi is the high part (tx_buf[reg[1]]) | |
692 | * end; | |
693 | */ | |
694 | for (n = 0; n < insn->n; n++) { | |
695 | tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); | |
696 | tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; | |
697 | ||
0703c955 | 698 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc MG |
699 | break; |
700 | } | |
3faad673 | 701 | |
da7b18ee | 702 | up(&devpriv->limit_sem); |
985cafcc MG |
703 | |
704 | return n; | |
705 | } | |
706 | ||
57cf09ae | 707 | static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) |
49253d54 | 708 | { |
57cf09ae | 709 | struct vmk80xx_private *devpriv = dev->private; |
e23322e4 | 710 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
49253d54 HS |
711 | struct usb_host_interface *iface_desc = intf->cur_altsetting; |
712 | struct usb_endpoint_descriptor *ep_desc; | |
713 | int i; | |
714 | ||
715 | if (iface_desc->desc.bNumEndpoints != 2) | |
716 | return -ENODEV; | |
717 | ||
718 | for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { | |
719 | ep_desc = &iface_desc->endpoint[i].desc; | |
720 | ||
721 | if (usb_endpoint_is_int_in(ep_desc) || | |
722 | usb_endpoint_is_bulk_in(ep_desc)) { | |
723 | if (!devpriv->ep_rx) | |
724 | devpriv->ep_rx = ep_desc; | |
725 | continue; | |
726 | } | |
727 | ||
728 | if (usb_endpoint_is_int_out(ep_desc) || | |
729 | usb_endpoint_is_bulk_out(ep_desc)) { | |
730 | if (!devpriv->ep_tx) | |
731 | devpriv->ep_tx = ep_desc; | |
732 | continue; | |
733 | } | |
734 | } | |
735 | ||
736 | if (!devpriv->ep_rx || !devpriv->ep_tx) | |
737 | return -ENODEV; | |
738 | ||
739 | return 0; | |
740 | } | |
741 | ||
57cf09ae | 742 | static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) |
78f8fa7f | 743 | { |
57cf09ae | 744 | struct vmk80xx_private *devpriv = dev->private; |
78f8fa7f HS |
745 | size_t size; |
746 | ||
747 | size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize); | |
0cbfc826 | 748 | devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
749 | if (!devpriv->usb_rx_buf) |
750 | return -ENOMEM; | |
751 | ||
752 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
0cbfc826 | 753 | devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
754 | if (!devpriv->usb_tx_buf) { |
755 | kfree(devpriv->usb_rx_buf); | |
756 | return -ENOMEM; | |
757 | } | |
758 | ||
759 | return 0; | |
760 | } | |
761 | ||
66dbc7b1 | 762 | static int vmk80xx_init_subdevices(struct comedi_device *dev) |
3faad673 | 763 | { |
957b9f8b | 764 | const struct vmk80xx_board *boardinfo = dev->board_ptr; |
57cf09ae | 765 | struct vmk80xx_private *devpriv = dev->private; |
b153d83e | 766 | struct comedi_subdevice *s; |
57cf09ae | 767 | int n_subd; |
8b6c5694 | 768 | int ret; |
3faad673 | 769 | |
da7b18ee | 770 | down(&devpriv->limit_sem); |
0dd772bf | 771 | |
52d895d3 | 772 | if (devpriv->model == VMK8055_MODEL) |
985cafcc MG |
773 | n_subd = 5; |
774 | else | |
775 | n_subd = 6; | |
da7b18ee | 776 | ret = comedi_alloc_subdevices(dev, n_subd); |
8b6c5694 | 777 | if (ret) { |
da7b18ee | 778 | up(&devpriv->limit_sem); |
8b6c5694 | 779 | return ret; |
3faad673 | 780 | } |
0dd772bf | 781 | |
985cafcc | 782 | /* Analog input subdevice */ |
da7b18ee | 783 | s = &dev->subdevices[0]; |
658cd3ac HS |
784 | s->type = COMEDI_SUBD_AI; |
785 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
786 | s->n_chan = boardinfo->ai_nchans; | |
787 | s->maxdata = boardinfo->ai_maxdata; | |
788 | s->range_table = boardinfo->range; | |
789 | s->insn_read = vmk80xx_ai_insn_read; | |
0dd772bf | 790 | |
985cafcc | 791 | /* Analog output subdevice */ |
da7b18ee | 792 | s = &dev->subdevices[1]; |
8b3ec9f1 | 793 | s->type = COMEDI_SUBD_AO; |
ef49d832 | 794 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
8b3ec9f1 HS |
795 | s->n_chan = boardinfo->ao_nchans; |
796 | s->maxdata = 0x00ff; | |
797 | s->range_table = boardinfo->range; | |
798 | s->insn_write = vmk80xx_ao_insn_write; | |
52d895d3 | 799 | if (devpriv->model == VMK8061_MODEL) { |
8b3ec9f1 HS |
800 | s->subdev_flags |= SDF_READABLE; |
801 | s->insn_read = vmk80xx_ao_insn_read; | |
985cafcc | 802 | } |
0dd772bf | 803 | |
985cafcc | 804 | /* Digital input subdevice */ |
da7b18ee | 805 | s = &dev->subdevices[2]; |
268e5148 HS |
806 | s->type = COMEDI_SUBD_DI; |
807 | s->subdev_flags = SDF_READABLE; | |
808 | s->n_chan = boardinfo->di_nchans; | |
809 | s->maxdata = 1; | |
810 | s->range_table = &range_digital; | |
268e5148 | 811 | s->insn_bits = vmk80xx_di_insn_bits; |
0dd772bf | 812 | |
985cafcc | 813 | /* Digital output subdevice */ |
da7b18ee | 814 | s = &dev->subdevices[3]; |
b639e096 | 815 | s->type = COMEDI_SUBD_DO; |
ef49d832 | 816 | s->subdev_flags = SDF_WRITABLE; |
b639e096 HS |
817 | s->n_chan = 8; |
818 | s->maxdata = 1; | |
819 | s->range_table = &range_digital; | |
b639e096 | 820 | s->insn_bits = vmk80xx_do_insn_bits; |
0dd772bf | 821 | |
985cafcc | 822 | /* Counter subdevice */ |
da7b18ee | 823 | s = &dev->subdevices[4]; |
75a45d92 HS |
824 | s->type = COMEDI_SUBD_COUNTER; |
825 | s->subdev_flags = SDF_READABLE; | |
826 | s->n_chan = 2; | |
827 | s->maxdata = boardinfo->cnt_maxdata; | |
828 | s->insn_read = vmk80xx_cnt_insn_read; | |
829 | s->insn_config = vmk80xx_cnt_insn_config; | |
52d895d3 | 830 | if (devpriv->model == VMK8055_MODEL) { |
ef49d832 | 831 | s->subdev_flags |= SDF_WRITABLE; |
75a45d92 | 832 | s->insn_write = vmk80xx_cnt_insn_write; |
985cafcc | 833 | } |
0dd772bf | 834 | |
985cafcc | 835 | /* PWM subdevice */ |
52d895d3 | 836 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee | 837 | s = &dev->subdevices[5]; |
9a23a748 | 838 | s->type = COMEDI_SUBD_PWM; |
ef49d832 | 839 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
9a23a748 HS |
840 | s->n_chan = boardinfo->pwm_nchans; |
841 | s->maxdata = boardinfo->pwm_maxdata; | |
842 | s->insn_read = vmk80xx_pwm_insn_read; | |
843 | s->insn_write = vmk80xx_pwm_insn_write; | |
985cafcc | 844 | } |
0dd772bf | 845 | |
da7b18ee | 846 | up(&devpriv->limit_sem); |
0dd772bf | 847 | |
f7d4d3bc IA |
848 | return 0; |
849 | } | |
3faad673 | 850 | |
da7b18ee | 851 | static int vmk80xx_auto_attach(struct comedi_device *dev, |
57cf09ae | 852 | unsigned long context) |
f7d4d3bc | 853 | { |
da7b18ee | 854 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
0dd772bf | 855 | const struct vmk80xx_board *boardinfo; |
da7b18ee | 856 | struct vmk80xx_private *devpriv; |
49253d54 | 857 | int ret; |
3faad673 | 858 | |
57cf09ae HS |
859 | boardinfo = &vmk80xx_boardinfo[context]; |
860 | dev->board_ptr = boardinfo; | |
861 | dev->board_name = boardinfo->name; | |
3faad673 | 862 | |
0bdab509 | 863 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
57cf09ae HS |
864 | if (!devpriv) |
865 | return -ENOMEM; | |
3faad673 | 866 | |
52d895d3 | 867 | devpriv->model = boardinfo->model; |
985cafcc | 868 | |
57cf09ae | 869 | ret = vmk80xx_find_usb_endpoints(dev); |
db7dabf7 | 870 | if (ret) |
57cf09ae | 871 | return ret; |
3faad673 | 872 | |
57cf09ae | 873 | ret = vmk80xx_alloc_usb_buffers(dev); |
db7dabf7 | 874 | if (ret) |
57cf09ae | 875 | return ret; |
985cafcc | 876 | |
da7b18ee | 877 | sema_init(&devpriv->limit_sem, 8); |
985cafcc | 878 | |
da7b18ee | 879 | usb_set_intfdata(intf, devpriv); |
985cafcc | 880 | |
52d895d3 | 881 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 882 | vmk80xx_read_eeprom(dev, IC3_VERSION); |
da7b18ee | 883 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic3_vers); |
985cafcc | 884 | |
0703c955 HS |
885 | if (vmk80xx_check_data_link(dev)) { |
886 | vmk80xx_read_eeprom(dev, IC6_VERSION); | |
da7b18ee | 887 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic6_vers); |
3a229fd5 | 888 | } |
3faad673 MG |
889 | } |
890 | ||
52d895d3 | 891 | if (devpriv->model == VMK8055_MODEL) |
0703c955 | 892 | vmk80xx_reset_device(dev); |
3faad673 | 893 | |
66dbc7b1 | 894 | return vmk80xx_init_subdevices(dev); |
57cf09ae | 895 | } |
3faad673 | 896 | |
57cf09ae HS |
897 | static void vmk80xx_detach(struct comedi_device *dev) |
898 | { | |
e23322e4 | 899 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
57cf09ae | 900 | struct vmk80xx_private *devpriv = dev->private; |
8ba69ce4 | 901 | |
57cf09ae HS |
902 | if (!devpriv) |
903 | return; | |
db7dabf7 | 904 | |
57cf09ae HS |
905 | down(&devpriv->limit_sem); |
906 | ||
e23322e4 | 907 | usb_set_intfdata(intf, NULL); |
57cf09ae | 908 | |
57cf09ae HS |
909 | kfree(devpriv->usb_rx_buf); |
910 | kfree(devpriv->usb_tx_buf); | |
911 | ||
912 | up(&devpriv->limit_sem); | |
913 | } | |
914 | ||
915 | static struct comedi_driver vmk80xx_driver = { | |
916 | .module = THIS_MODULE, | |
917 | .driver_name = "vmk80xx", | |
918 | .auto_attach = vmk80xx_auto_attach, | |
919 | .detach = vmk80xx_detach, | |
920 | }; | |
921 | ||
922 | static int vmk80xx_usb_probe(struct usb_interface *intf, | |
923 | const struct usb_device_id *id) | |
924 | { | |
925 | return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info); | |
3faad673 MG |
926 | } |
927 | ||
007ff2af HS |
928 | static const struct usb_device_id vmk80xx_usb_id_table[] = { |
929 | { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 }, | |
930 | { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 }, | |
931 | { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 }, | |
932 | { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 }, | |
933 | { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 }, | |
934 | { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 }, | |
935 | { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 }, | |
936 | { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 }, | |
937 | { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 }, | |
938 | { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 }, | |
939 | { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 }, | |
940 | { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 }, | |
941 | { } | |
942 | }; | |
943 | MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table); | |
944 | ||
d6cc3ec8 HS |
945 | static struct usb_driver vmk80xx_usb_driver = { |
946 | .name = "vmk80xx", | |
007ff2af | 947 | .id_table = vmk80xx_usb_id_table, |
ce874227 HS |
948 | .probe = vmk80xx_usb_probe, |
949 | .disconnect = comedi_usb_auto_unconfig, | |
3faad673 | 950 | }; |
d6cc3ec8 | 951 | module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); |
007ff2af HS |
952 | |
953 | MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); | |
954 | MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); | |
955 | MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); | |
007ff2af | 956 | MODULE_LICENSE("GPL"); |