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