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