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 | ||
985cafcc MG |
116 | #define TRANS_OUT_BUSY 1 |
117 | #define TRANS_IN_BUSY 2 | |
118 | #define TRANS_IN_RUNNING 3 | |
119 | ||
120 | #define IC3_VERSION (1 << 0) | |
121 | #define IC6_VERSION (1 << 1) | |
122 | ||
123 | #define URB_RCV_FLAG (1 << 0) | |
124 | #define URB_SND_FLAG (1 << 1) | |
3faad673 | 125 | |
985cafcc MG |
126 | enum vmk80xx_model { |
127 | VMK8055_MODEL, | |
128 | VMK8061_MODEL | |
129 | }; | |
3faad673 | 130 | |
985cafcc | 131 | struct firmware_version { |
0a85b6f0 MT |
132 | unsigned char ic3_vers[32]; /* USB-Controller */ |
133 | unsigned char ic6_vers[32]; /* CPU */ | |
3faad673 MG |
134 | }; |
135 | ||
985cafcc | 136 | static const struct comedi_lrange vmk8055_range = { |
0a85b6f0 | 137 | 1, {UNI_RANGE(5)} |
985cafcc | 138 | }; |
3faad673 | 139 | |
985cafcc | 140 | static const struct comedi_lrange vmk8061_range = { |
0a85b6f0 | 141 | 2, {UNI_RANGE(5), UNI_RANGE(10)} |
985cafcc | 142 | }; |
3faad673 | 143 | |
985cafcc MG |
144 | struct vmk80xx_board { |
145 | const char *name; | |
146 | enum vmk80xx_model model; | |
147 | const struct comedi_lrange *range; | |
658cd3ac HS |
148 | int ai_nchans; |
149 | unsigned int ai_maxdata; | |
8b3ec9f1 | 150 | int ao_nchans; |
268e5148 | 151 | int di_nchans; |
75a45d92 | 152 | unsigned int cnt_maxdata; |
9a23a748 HS |
153 | int pwm_nchans; |
154 | unsigned int pwm_maxdata; | |
985cafcc | 155 | }; |
3faad673 | 156 | |
20d60077 HS |
157 | static const struct vmk80xx_board vmk80xx_boardinfo[] = { |
158 | [DEVICE_VMK8055] = { | |
159 | .name = "K8055 (VM110)", | |
160 | .model = VMK8055_MODEL, | |
161 | .range = &vmk8055_range, | |
658cd3ac HS |
162 | .ai_nchans = 2, |
163 | .ai_maxdata = 0x00ff, | |
8b3ec9f1 | 164 | .ao_nchans = 2, |
268e5148 | 165 | .di_nchans = 6, |
75a45d92 | 166 | .cnt_maxdata = 0xffff, |
20d60077 HS |
167 | }, |
168 | [DEVICE_VMK8061] = { | |
169 | .name = "K8061 (VM140)", | |
170 | .model = VMK8061_MODEL, | |
171 | .range = &vmk8061_range, | |
658cd3ac HS |
172 | .ai_nchans = 8, |
173 | .ai_maxdata = 0x03ff, | |
8b3ec9f1 | 174 | .ao_nchans = 8, |
268e5148 | 175 | .di_nchans = 8, |
75a45d92 | 176 | .cnt_maxdata = 0, /* unknown, device is not writeable */ |
9a23a748 HS |
177 | .pwm_nchans = 1, |
178 | .pwm_maxdata = 0x03ff, | |
20d60077 HS |
179 | }, |
180 | }; | |
181 | ||
dc49cbfc | 182 | struct vmk80xx_private { |
da7b18ee | 183 | struct usb_device *usb; |
985cafcc MG |
184 | struct usb_interface *intf; |
185 | struct usb_endpoint_descriptor *ep_rx; | |
186 | struct usb_endpoint_descriptor *ep_tx; | |
187 | struct usb_anchor rx_anchor; | |
188 | struct usb_anchor tx_anchor; | |
985cafcc MG |
189 | struct firmware_version fw; |
190 | struct semaphore limit_sem; | |
191 | wait_queue_head_t read_wait; | |
192 | wait_queue_head_t write_wait; | |
193 | unsigned char *usb_rx_buf; | |
194 | unsigned char *usb_tx_buf; | |
195 | unsigned long flags; | |
52d895d3 | 196 | enum vmk80xx_model model; |
985cafcc MG |
197 | }; |
198 | ||
985cafcc | 199 | static void vmk80xx_tx_callback(struct urb *urb) |
3faad673 | 200 | { |
da7b18ee HS |
201 | struct vmk80xx_private *devpriv = urb->context; |
202 | unsigned long *flags = &devpriv->flags; | |
3faad673 | 203 | |
da7b18ee | 204 | if (!test_bit(TRANS_OUT_BUSY, flags)) |
985cafcc | 205 | return; |
3faad673 | 206 | |
da7b18ee | 207 | clear_bit(TRANS_OUT_BUSY, flags); |
3faad673 | 208 | |
da7b18ee | 209 | wake_up_interruptible(&devpriv->write_wait); |
3faad673 MG |
210 | } |
211 | ||
985cafcc | 212 | static void vmk80xx_rx_callback(struct urb *urb) |
3faad673 | 213 | { |
da7b18ee HS |
214 | struct vmk80xx_private *devpriv = urb->context; |
215 | unsigned long *flags = &devpriv->flags; | |
985cafcc | 216 | int stat = urb->status; |
3faad673 | 217 | |
985cafcc MG |
218 | switch (stat) { |
219 | case 0: | |
3faad673 MG |
220 | break; |
221 | case -ENOENT: | |
222 | case -ECONNRESET: | |
223 | case -ESHUTDOWN: | |
224 | break; | |
225 | default: | |
4c56a2b6 HS |
226 | /* Try to resubmit the urb */ |
227 | if (test_bit(TRANS_IN_RUNNING, flags) && devpriv->intf) { | |
228 | usb_anchor_urb(urb, &devpriv->rx_anchor); | |
985cafcc | 229 | |
4c56a2b6 HS |
230 | if (usb_submit_urb(urb, GFP_KERNEL)) |
231 | usb_unanchor_urb(urb); | |
232 | } | |
233 | break; | |
3faad673 | 234 | } |
4c56a2b6 | 235 | |
da7b18ee | 236 | clear_bit(TRANS_IN_BUSY, flags); |
3faad673 | 237 | |
da7b18ee | 238 | wake_up_interruptible(&devpriv->read_wait); |
3faad673 MG |
239 | } |
240 | ||
da7b18ee | 241 | static int vmk80xx_check_data_link(struct vmk80xx_private *devpriv) |
3faad673 | 242 | { |
da7b18ee | 243 | struct usb_device *usb = devpriv->usb; |
3a229fd5 AH |
244 | unsigned int tx_pipe; |
245 | unsigned int rx_pipe; | |
246 | unsigned char tx[1]; | |
247 | unsigned char rx[2]; | |
985cafcc | 248 | |
da7b18ee HS |
249 | tx_pipe = usb_sndbulkpipe(usb, 0x01); |
250 | rx_pipe = usb_rcvbulkpipe(usb, 0x81); | |
3faad673 | 251 | |
985cafcc | 252 | tx[0] = VMK8061_CMD_RD_PWR_STAT; |
3faad673 | 253 | |
3a229fd5 AH |
254 | /* |
255 | * Check that IC6 (PIC16F871) is powered and | |
985cafcc | 256 | * running and the data link between IC3 and |
3a229fd5 AH |
257 | * IC6 is working properly |
258 | */ | |
da7b18ee HS |
259 | usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); |
260 | usb_bulk_msg(usb, rx_pipe, rx, 2, NULL, HZ * 10); | |
3faad673 | 261 | |
985cafcc | 262 | return (int)rx[1]; |
3faad673 MG |
263 | } |
264 | ||
da7b18ee | 265 | static void vmk80xx_read_eeprom(struct vmk80xx_private *devpriv, int flag) |
3faad673 | 266 | { |
da7b18ee | 267 | struct usb_device *usb = devpriv->usb; |
3a229fd5 AH |
268 | unsigned int tx_pipe; |
269 | unsigned int rx_pipe; | |
270 | unsigned char tx[1]; | |
271 | unsigned char rx[64]; | |
985cafcc | 272 | int cnt; |
3faad673 | 273 | |
da7b18ee HS |
274 | tx_pipe = usb_sndbulkpipe(usb, 0x01); |
275 | rx_pipe = usb_rcvbulkpipe(usb, 0x81); | |
3faad673 | 276 | |
985cafcc MG |
277 | tx[0] = VMK8061_CMD_RD_VERSION; |
278 | ||
3a229fd5 AH |
279 | /* |
280 | * Read the firmware version info of IC3 and | |
281 | * IC6 from the internal EEPROM of the IC | |
282 | */ | |
da7b18ee HS |
283 | usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); |
284 | usb_bulk_msg(usb, rx_pipe, rx, 64, &cnt, HZ * 10); | |
985cafcc MG |
285 | |
286 | rx[cnt] = '\0'; | |
287 | ||
288 | if (flag & IC3_VERSION) | |
da7b18ee | 289 | strncpy(devpriv->fw.ic3_vers, rx + 1, 24); |
0a85b6f0 | 290 | else /* IC6_VERSION */ |
da7b18ee | 291 | strncpy(devpriv->fw.ic6_vers, rx + 25, 24); |
985cafcc MG |
292 | } |
293 | ||
da7b18ee | 294 | static int vmk80xx_reset_device(struct vmk80xx_private *devpriv) |
985cafcc | 295 | { |
da7b18ee HS |
296 | struct usb_device *usb = devpriv->usb; |
297 | unsigned char *tx_buf = devpriv->usb_tx_buf; | |
985cafcc MG |
298 | struct urb *urb; |
299 | unsigned int tx_pipe; | |
300 | int ival; | |
301 | size_t size; | |
302 | ||
985cafcc MG |
303 | urb = usb_alloc_urb(0, GFP_KERNEL); |
304 | if (!urb) | |
305 | return -ENOMEM; | |
306 | ||
da7b18ee | 307 | tx_pipe = usb_sndintpipe(usb, 0x01); |
985cafcc | 308 | |
da7b18ee HS |
309 | ival = devpriv->ep_tx->bInterval; |
310 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
985cafcc | 311 | |
da7b18ee HS |
312 | tx_buf[0] = VMK8055_CMD_RST; |
313 | tx_buf[1] = 0x00; | |
314 | tx_buf[2] = 0x00; | |
315 | tx_buf[3] = 0x00; | |
316 | tx_buf[4] = 0x00; | |
317 | tx_buf[5] = 0x00; | |
318 | tx_buf[6] = 0x00; | |
319 | tx_buf[7] = 0x00; | |
985cafcc | 320 | |
da7b18ee HS |
321 | usb_fill_int_urb(urb, usb, tx_pipe, tx_buf, size, |
322 | vmk80xx_tx_callback, devpriv, ival); | |
985cafcc | 323 | |
da7b18ee | 324 | usb_anchor_urb(urb, &devpriv->tx_anchor); |
985cafcc MG |
325 | |
326 | return usb_submit_urb(urb, GFP_KERNEL); | |
327 | } | |
328 | ||
329 | static void vmk80xx_build_int_urb(struct urb *urb, int flag) | |
330 | { | |
da7b18ee HS |
331 | struct vmk80xx_private *devpriv = urb->context; |
332 | struct usb_device *usb = devpriv->usb; | |
3a229fd5 AH |
333 | __u8 rx_addr; |
334 | __u8 tx_addr; | |
985cafcc MG |
335 | unsigned int pipe; |
336 | unsigned char *buf; | |
337 | size_t size; | |
0a85b6f0 | 338 | void (*callback) (struct urb *); |
985cafcc MG |
339 | int ival; |
340 | ||
985cafcc | 341 | if (flag & URB_RCV_FLAG) { |
da7b18ee HS |
342 | rx_addr = devpriv->ep_rx->bEndpointAddress; |
343 | pipe = usb_rcvintpipe(usb, rx_addr); | |
344 | buf = devpriv->usb_rx_buf; | |
345 | size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize); | |
985cafcc | 346 | callback = vmk80xx_rx_callback; |
da7b18ee | 347 | ival = devpriv->ep_rx->bInterval; |
0a85b6f0 | 348 | } else { /* URB_SND_FLAG */ |
da7b18ee HS |
349 | tx_addr = devpriv->ep_tx->bEndpointAddress; |
350 | pipe = usb_sndintpipe(usb, tx_addr); | |
351 | buf = devpriv->usb_tx_buf; | |
352 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
985cafcc | 353 | callback = vmk80xx_tx_callback; |
da7b18ee | 354 | ival = devpriv->ep_tx->bInterval; |
3faad673 MG |
355 | } |
356 | ||
da7b18ee | 357 | usb_fill_int_urb(urb, usb, pipe, buf, size, callback, devpriv, ival); |
985cafcc | 358 | } |
3faad673 | 359 | |
da7b18ee | 360 | static void vmk80xx_do_bulk_msg(struct vmk80xx_private *devpriv) |
985cafcc | 361 | { |
da7b18ee HS |
362 | struct usb_device *usb = devpriv->usb; |
363 | unsigned long *flags = &devpriv->flags; | |
3a229fd5 AH |
364 | __u8 tx_addr; |
365 | __u8 rx_addr; | |
366 | unsigned int tx_pipe; | |
367 | unsigned int rx_pipe; | |
985cafcc | 368 | size_t size; |
3faad673 | 369 | |
da7b18ee HS |
370 | set_bit(TRANS_IN_BUSY, flags); |
371 | set_bit(TRANS_OUT_BUSY, flags); | |
3faad673 | 372 | |
da7b18ee HS |
373 | tx_addr = devpriv->ep_tx->bEndpointAddress; |
374 | rx_addr = devpriv->ep_rx->bEndpointAddress; | |
375 | tx_pipe = usb_sndbulkpipe(usb, tx_addr); | |
376 | rx_pipe = usb_rcvbulkpipe(usb, rx_addr); | |
985cafcc | 377 | |
3a229fd5 AH |
378 | /* |
379 | * The max packet size attributes of the K8061 | |
380 | * input/output endpoints are identical | |
381 | */ | |
da7b18ee | 382 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); |
985cafcc | 383 | |
da7b18ee HS |
384 | usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, |
385 | size, NULL, devpriv->ep_tx->bInterval); | |
386 | usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10); | |
985cafcc | 387 | |
da7b18ee HS |
388 | clear_bit(TRANS_OUT_BUSY, flags); |
389 | clear_bit(TRANS_IN_BUSY, flags); | |
3faad673 MG |
390 | } |
391 | ||
da7b18ee | 392 | static int vmk80xx_read_packet(struct vmk80xx_private *devpriv) |
3faad673 | 393 | { |
da7b18ee | 394 | unsigned long *flags = &devpriv->flags; |
985cafcc MG |
395 | struct urb *urb; |
396 | int retval; | |
3faad673 | 397 | |
da7b18ee | 398 | if (!devpriv->intf) |
985cafcc | 399 | return -ENODEV; |
3faad673 | 400 | |
985cafcc | 401 | /* Only useful for interrupt transfers */ |
da7b18ee HS |
402 | if (test_bit(TRANS_IN_BUSY, flags)) |
403 | if (wait_event_interruptible(devpriv->read_wait, | |
404 | !test_bit(TRANS_IN_BUSY, flags))) | |
985cafcc MG |
405 | return -ERESTART; |
406 | ||
52d895d3 | 407 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee | 408 | vmk80xx_do_bulk_msg(devpriv); |
985cafcc MG |
409 | |
410 | return 0; | |
3faad673 MG |
411 | } |
412 | ||
985cafcc MG |
413 | urb = usb_alloc_urb(0, GFP_KERNEL); |
414 | if (!urb) | |
415 | return -ENOMEM; | |
3faad673 | 416 | |
da7b18ee | 417 | urb->context = devpriv; |
985cafcc | 418 | vmk80xx_build_int_urb(urb, URB_RCV_FLAG); |
3faad673 | 419 | |
da7b18ee HS |
420 | set_bit(TRANS_IN_RUNNING, flags); |
421 | set_bit(TRANS_IN_BUSY, flags); | |
3faad673 | 422 | |
da7b18ee | 423 | usb_anchor_urb(urb, &devpriv->rx_anchor); |
3faad673 | 424 | |
985cafcc MG |
425 | retval = usb_submit_urb(urb, GFP_KERNEL); |
426 | if (!retval) | |
427 | goto exit; | |
3faad673 | 428 | |
da7b18ee | 429 | clear_bit(TRANS_IN_RUNNING, flags); |
985cafcc | 430 | usb_unanchor_urb(urb); |
3faad673 MG |
431 | |
432 | exit: | |
985cafcc MG |
433 | usb_free_urb(urb); |
434 | ||
3faad673 MG |
435 | return retval; |
436 | } | |
437 | ||
da7b18ee | 438 | static int vmk80xx_write_packet(struct vmk80xx_private *devpriv, int cmd) |
3faad673 | 439 | { |
da7b18ee | 440 | unsigned long *flags = &devpriv->flags; |
985cafcc MG |
441 | struct urb *urb; |
442 | int retval; | |
3faad673 | 443 | |
da7b18ee | 444 | if (!devpriv->intf) |
985cafcc | 445 | return -ENODEV; |
3faad673 | 446 | |
da7b18ee HS |
447 | if (test_bit(TRANS_OUT_BUSY, flags)) |
448 | if (wait_event_interruptible(devpriv->write_wait, | |
449 | !test_bit(TRANS_OUT_BUSY, flags))) | |
985cafcc | 450 | return -ERESTART; |
3faad673 | 451 | |
52d895d3 | 452 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee HS |
453 | devpriv->usb_tx_buf[0] = cmd; |
454 | vmk80xx_do_bulk_msg(devpriv); | |
3faad673 | 455 | |
985cafcc | 456 | return 0; |
3faad673 MG |
457 | } |
458 | ||
985cafcc MG |
459 | urb = usb_alloc_urb(0, GFP_KERNEL); |
460 | if (!urb) | |
461 | return -ENOMEM; | |
462 | ||
da7b18ee | 463 | urb->context = devpriv; |
985cafcc | 464 | vmk80xx_build_int_urb(urb, URB_SND_FLAG); |
3faad673 | 465 | |
da7b18ee | 466 | set_bit(TRANS_OUT_BUSY, flags); |
3faad673 | 467 | |
da7b18ee | 468 | usb_anchor_urb(urb, &devpriv->tx_anchor); |
3faad673 | 469 | |
da7b18ee | 470 | devpriv->usb_tx_buf[0] = cmd; |
3faad673 | 471 | |
985cafcc MG |
472 | retval = usb_submit_urb(urb, GFP_KERNEL); |
473 | if (!retval) | |
474 | goto exit; | |
475 | ||
da7b18ee | 476 | clear_bit(TRANS_OUT_BUSY, flags); |
985cafcc MG |
477 | usb_unanchor_urb(urb); |
478 | ||
479 | exit: | |
480 | usb_free_urb(urb); | |
3faad673 MG |
481 | |
482 | return retval; | |
483 | } | |
484 | ||
985cafcc MG |
485 | #define DIR_IN 1 |
486 | #define DIR_OUT 2 | |
487 | ||
da7b18ee | 488 | static int rudimentary_check(struct vmk80xx_private *devpriv, int dir) |
587e500c | 489 | { |
da7b18ee | 490 | if (!devpriv) |
587e500c | 491 | return -EFAULT; |
587e500c | 492 | if (dir & DIR_IN) { |
da7b18ee | 493 | if (test_bit(TRANS_IN_BUSY, &devpriv->flags)) |
587e500c | 494 | return -EBUSY; |
510b9be3 AH |
495 | } |
496 | if (dir & DIR_OUT) { | |
da7b18ee | 497 | if (test_bit(TRANS_OUT_BUSY, &devpriv->flags)) |
587e500c AH |
498 | return -EBUSY; |
499 | } | |
500 | ||
501 | return 0; | |
502 | } | |
985cafcc | 503 | |
658cd3ac HS |
504 | static int vmk80xx_ai_insn_read(struct comedi_device *dev, |
505 | struct comedi_subdevice *s, | |
506 | struct comedi_insn *insn, | |
507 | unsigned int *data) | |
3faad673 | 508 | { |
da7b18ee | 509 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
510 | int chan; |
511 | int reg[2]; | |
985cafcc | 512 | int n; |
3faad673 | 513 | |
da7b18ee | 514 | n = rudimentary_check(devpriv, DIR_IN); |
587e500c AH |
515 | if (n) |
516 | return n; | |
3faad673 | 517 | |
da7b18ee | 518 | down(&devpriv->limit_sem); |
985cafcc | 519 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 520 | |
52d895d3 | 521 | switch (devpriv->model) { |
985cafcc MG |
522 | case VMK8055_MODEL: |
523 | if (!chan) | |
524 | reg[0] = VMK8055_AI1_REG; | |
525 | else | |
526 | reg[0] = VMK8055_AI2_REG; | |
527 | break; | |
528 | case VMK8061_MODEL: | |
13f7952f | 529 | default: |
985cafcc MG |
530 | reg[0] = VMK8061_AI_REG1; |
531 | reg[1] = VMK8061_AI_REG2; | |
da7b18ee HS |
532 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI; |
533 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; | |
985cafcc | 534 | break; |
3faad673 MG |
535 | } |
536 | ||
985cafcc | 537 | for (n = 0; n < insn->n; n++) { |
da7b18ee | 538 | if (vmk80xx_read_packet(devpriv)) |
985cafcc | 539 | break; |
3faad673 | 540 | |
52d895d3 | 541 | if (devpriv->model == VMK8055_MODEL) { |
da7b18ee | 542 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
985cafcc MG |
543 | continue; |
544 | } | |
3faad673 | 545 | |
985cafcc | 546 | /* VMK8061_MODEL */ |
da7b18ee HS |
547 | data[n] = devpriv->usb_rx_buf[reg[0]] + 256 * |
548 | devpriv->usb_rx_buf[reg[1]]; | |
985cafcc | 549 | } |
3faad673 | 550 | |
da7b18ee | 551 | up(&devpriv->limit_sem); |
3faad673 | 552 | |
985cafcc | 553 | return n; |
3faad673 MG |
554 | } |
555 | ||
8b3ec9f1 HS |
556 | static int vmk80xx_ao_insn_write(struct comedi_device *dev, |
557 | struct comedi_subdevice *s, | |
558 | struct comedi_insn *insn, | |
559 | unsigned int *data) | |
3faad673 | 560 | { |
da7b18ee | 561 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
562 | int chan; |
563 | int cmd; | |
564 | int reg; | |
985cafcc | 565 | int n; |
3faad673 | 566 | |
da7b18ee | 567 | n = rudimentary_check(devpriv, DIR_OUT); |
587e500c AH |
568 | if (n) |
569 | return n; | |
3faad673 | 570 | |
da7b18ee | 571 | down(&devpriv->limit_sem); |
985cafcc | 572 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 573 | |
52d895d3 | 574 | switch (devpriv->model) { |
985cafcc MG |
575 | case VMK8055_MODEL: |
576 | cmd = VMK8055_CMD_WRT_AD; | |
577 | if (!chan) | |
578 | reg = VMK8055_AO1_REG; | |
579 | else | |
580 | reg = VMK8055_AO2_REG; | |
581 | break; | |
0a85b6f0 | 582 | default: /* NOTE: avoid compiler warnings */ |
985cafcc MG |
583 | cmd = VMK8061_CMD_SET_AO; |
584 | reg = VMK8061_AO_REG; | |
da7b18ee | 585 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; |
985cafcc | 586 | break; |
3faad673 MG |
587 | } |
588 | ||
985cafcc | 589 | for (n = 0; n < insn->n; n++) { |
da7b18ee | 590 | devpriv->usb_tx_buf[reg] = data[n]; |
3faad673 | 591 | |
da7b18ee | 592 | if (vmk80xx_write_packet(devpriv, cmd)) |
985cafcc | 593 | break; |
3faad673 MG |
594 | } |
595 | ||
da7b18ee | 596 | up(&devpriv->limit_sem); |
3faad673 | 597 | |
985cafcc | 598 | return n; |
3faad673 MG |
599 | } |
600 | ||
8b3ec9f1 HS |
601 | static int vmk80xx_ao_insn_read(struct comedi_device *dev, |
602 | struct comedi_subdevice *s, | |
603 | struct comedi_insn *insn, | |
604 | unsigned int *data) | |
3faad673 | 605 | { |
da7b18ee | 606 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
607 | int chan; |
608 | int reg; | |
985cafcc | 609 | int n; |
3faad673 | 610 | |
da7b18ee | 611 | n = rudimentary_check(devpriv, DIR_IN); |
587e500c AH |
612 | if (n) |
613 | return n; | |
3faad673 | 614 | |
da7b18ee | 615 | down(&devpriv->limit_sem); |
985cafcc | 616 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 617 | |
985cafcc | 618 | reg = VMK8061_AO_REG - 1; |
3faad673 | 619 | |
da7b18ee | 620 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO; |
985cafcc MG |
621 | |
622 | for (n = 0; n < insn->n; n++) { | |
da7b18ee | 623 | if (vmk80xx_read_packet(devpriv)) |
985cafcc MG |
624 | break; |
625 | ||
da7b18ee | 626 | data[n] = devpriv->usb_rx_buf[reg + chan]; |
3faad673 MG |
627 | } |
628 | ||
da7b18ee | 629 | up(&devpriv->limit_sem); |
3faad673 | 630 | |
985cafcc MG |
631 | return n; |
632 | } | |
3faad673 | 633 | |
268e5148 HS |
634 | static int vmk80xx_di_insn_bits(struct comedi_device *dev, |
635 | struct comedi_subdevice *s, | |
636 | struct comedi_insn *insn, | |
637 | unsigned int *data) | |
c647ed56 | 638 | { |
da7b18ee | 639 | struct vmk80xx_private *devpriv = dev->private; |
c647ed56 AH |
640 | unsigned char *rx_buf; |
641 | int reg; | |
642 | int retval; | |
643 | ||
da7b18ee | 644 | retval = rudimentary_check(devpriv, DIR_IN); |
c647ed56 AH |
645 | if (retval) |
646 | return retval; | |
647 | ||
da7b18ee | 648 | down(&devpriv->limit_sem); |
c647ed56 | 649 | |
da7b18ee | 650 | rx_buf = devpriv->usb_rx_buf; |
c647ed56 | 651 | |
52d895d3 | 652 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 | 653 | reg = VMK8061_DI_REG; |
da7b18ee | 654 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI; |
c647ed56 AH |
655 | } else { |
656 | reg = VMK8055_DI_REG; | |
657 | } | |
658 | ||
da7b18ee | 659 | retval = vmk80xx_read_packet(devpriv); |
c647ed56 AH |
660 | |
661 | if (!retval) { | |
52d895d3 | 662 | if (devpriv->model == VMK8055_MODEL) |
c647ed56 AH |
663 | data[1] = (((rx_buf[reg] >> 4) & 0x03) | |
664 | ((rx_buf[reg] << 2) & 0x04) | | |
665 | ((rx_buf[reg] >> 3) & 0x18)); | |
666 | else | |
667 | data[1] = rx_buf[reg]; | |
668 | ||
669 | retval = 2; | |
670 | } | |
671 | ||
da7b18ee | 672 | up(&devpriv->limit_sem); |
c647ed56 AH |
673 | |
674 | return retval; | |
675 | } | |
676 | ||
b639e096 HS |
677 | static int vmk80xx_do_insn_bits(struct comedi_device *dev, |
678 | struct comedi_subdevice *s, | |
679 | struct comedi_insn *insn, | |
680 | unsigned int *data) | |
c647ed56 | 681 | { |
da7b18ee | 682 | struct vmk80xx_private *devpriv = dev->private; |
c647ed56 AH |
683 | unsigned char *rx_buf, *tx_buf; |
684 | int dir, reg, cmd; | |
685 | int retval; | |
686 | ||
c647ed56 AH |
687 | dir = 0; |
688 | ||
689 | if (data[0]) | |
690 | dir |= DIR_OUT; | |
691 | ||
52d895d3 | 692 | if (devpriv->model == VMK8061_MODEL) |
c647ed56 AH |
693 | dir |= DIR_IN; |
694 | ||
da7b18ee | 695 | retval = rudimentary_check(devpriv, dir); |
c647ed56 AH |
696 | if (retval) |
697 | return retval; | |
698 | ||
da7b18ee | 699 | down(&devpriv->limit_sem); |
c647ed56 | 700 | |
da7b18ee HS |
701 | rx_buf = devpriv->usb_rx_buf; |
702 | tx_buf = devpriv->usb_tx_buf; | |
c647ed56 AH |
703 | |
704 | if (data[0]) { | |
52d895d3 | 705 | if (devpriv->model == VMK8055_MODEL) { |
c647ed56 AH |
706 | reg = VMK8055_DO_REG; |
707 | cmd = VMK8055_CMD_WRT_AD; | |
708 | } else { /* VMK8061_MODEL */ | |
709 | reg = VMK8061_DO_REG; | |
710 | cmd = VMK8061_CMD_DO; | |
711 | } | |
712 | ||
713 | tx_buf[reg] &= ~data[0]; | |
714 | tx_buf[reg] |= (data[0] & data[1]); | |
715 | ||
da7b18ee | 716 | retval = vmk80xx_write_packet(devpriv, cmd); |
c647ed56 AH |
717 | |
718 | if (retval) | |
719 | goto out; | |
720 | } | |
721 | ||
52d895d3 | 722 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 AH |
723 | reg = VMK8061_DO_REG; |
724 | tx_buf[0] = VMK8061_CMD_RD_DO; | |
725 | ||
da7b18ee | 726 | retval = vmk80xx_read_packet(devpriv); |
c647ed56 AH |
727 | |
728 | if (!retval) { | |
729 | data[1] = rx_buf[reg]; | |
730 | retval = 2; | |
731 | } | |
732 | } else { | |
733 | data[1] = tx_buf[reg]; | |
734 | retval = 2; | |
735 | } | |
736 | ||
737 | out: | |
da7b18ee | 738 | up(&devpriv->limit_sem); |
c647ed56 AH |
739 | |
740 | return retval; | |
741 | } | |
742 | ||
75a45d92 HS |
743 | static int vmk80xx_cnt_insn_read(struct comedi_device *dev, |
744 | struct comedi_subdevice *s, | |
745 | struct comedi_insn *insn, | |
746 | unsigned int *data) | |
985cafcc | 747 | { |
da7b18ee | 748 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
749 | int chan; |
750 | int reg[2]; | |
985cafcc MG |
751 | int n; |
752 | ||
da7b18ee | 753 | n = rudimentary_check(devpriv, DIR_IN); |
587e500c AH |
754 | if (n) |
755 | return n; | |
3faad673 | 756 | |
da7b18ee | 757 | down(&devpriv->limit_sem); |
985cafcc | 758 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 759 | |
52d895d3 | 760 | switch (devpriv->model) { |
985cafcc MG |
761 | case VMK8055_MODEL: |
762 | if (!chan) | |
763 | reg[0] = VMK8055_CNT1_REG; | |
764 | else | |
765 | reg[0] = VMK8055_CNT2_REG; | |
766 | break; | |
767 | case VMK8061_MODEL: | |
13f7952f | 768 | default: |
985cafcc MG |
769 | reg[0] = VMK8061_CNT_REG; |
770 | reg[1] = VMK8061_CNT_REG; | |
da7b18ee | 771 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; |
985cafcc | 772 | break; |
3faad673 MG |
773 | } |
774 | ||
985cafcc | 775 | for (n = 0; n < insn->n; n++) { |
da7b18ee | 776 | if (vmk80xx_read_packet(devpriv)) |
985cafcc | 777 | break; |
3faad673 | 778 | |
52d895d3 | 779 | if (devpriv->model == VMK8055_MODEL) |
da7b18ee | 780 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
3a229fd5 | 781 | else /* VMK8061_MODEL */ |
da7b18ee HS |
782 | data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1] |
783 | + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2]; | |
985cafcc MG |
784 | } |
785 | ||
da7b18ee | 786 | up(&devpriv->limit_sem); |
985cafcc MG |
787 | |
788 | return n; | |
3faad673 MG |
789 | } |
790 | ||
75a45d92 HS |
791 | static int vmk80xx_cnt_insn_config(struct comedi_device *dev, |
792 | struct comedi_subdevice *s, | |
793 | struct comedi_insn *insn, | |
794 | unsigned int *data) | |
3faad673 | 795 | { |
da7b18ee | 796 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 797 | unsigned int insn_cmd; |
3a229fd5 AH |
798 | int chan; |
799 | int cmd; | |
800 | int reg; | |
985cafcc | 801 | int n; |
3faad673 | 802 | |
da7b18ee | 803 | n = rudimentary_check(devpriv, DIR_OUT); |
587e500c AH |
804 | if (n) |
805 | return n; | |
3faad673 | 806 | |
985cafcc MG |
807 | insn_cmd = data[0]; |
808 | if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET) | |
809 | return -EINVAL; | |
3faad673 | 810 | |
da7b18ee | 811 | down(&devpriv->limit_sem); |
8f9064a8 | 812 | |
985cafcc | 813 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 814 | |
52d895d3 | 815 | if (devpriv->model == VMK8055_MODEL) { |
985cafcc MG |
816 | if (!chan) { |
817 | cmd = VMK8055_CMD_RST_CNT1; | |
818 | reg = VMK8055_CNT1_REG; | |
819 | } else { | |
820 | cmd = VMK8055_CMD_RST_CNT2; | |
821 | reg = VMK8055_CNT2_REG; | |
822 | } | |
823 | ||
da7b18ee | 824 | devpriv->usb_tx_buf[reg] = 0x00; |
3a229fd5 | 825 | } else { |
985cafcc | 826 | cmd = VMK8061_CMD_RST_CNT; |
3a229fd5 | 827 | } |
985cafcc MG |
828 | |
829 | for (n = 0; n < insn->n; n++) | |
da7b18ee | 830 | if (vmk80xx_write_packet(devpriv, cmd)) |
985cafcc MG |
831 | break; |
832 | ||
da7b18ee | 833 | up(&devpriv->limit_sem); |
985cafcc MG |
834 | |
835 | return n; | |
836 | } | |
837 | ||
75a45d92 HS |
838 | static int vmk80xx_cnt_insn_write(struct comedi_device *dev, |
839 | struct comedi_subdevice *s, | |
840 | struct comedi_insn *insn, | |
841 | unsigned int *data) | |
985cafcc | 842 | { |
da7b18ee | 843 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
844 | unsigned long debtime; |
845 | unsigned long val; | |
846 | int chan; | |
847 | int cmd; | |
985cafcc MG |
848 | int n; |
849 | ||
da7b18ee | 850 | n = rudimentary_check(devpriv, DIR_OUT); |
587e500c AH |
851 | if (n) |
852 | return n; | |
985cafcc | 853 | |
da7b18ee | 854 | down(&devpriv->limit_sem); |
985cafcc MG |
855 | chan = CR_CHAN(insn->chanspec); |
856 | ||
857 | if (!chan) | |
858 | cmd = VMK8055_CMD_DEB1_TIME; | |
859 | else | |
860 | cmd = VMK8055_CMD_DEB2_TIME; | |
861 | ||
862 | for (n = 0; n < insn->n; n++) { | |
863 | debtime = data[n]; | |
3faad673 MG |
864 | if (debtime == 0) |
865 | debtime = 1; | |
985cafcc MG |
866 | |
867 | /* TODO: Prevent overflows */ | |
868 | if (debtime > 7450) | |
869 | debtime = 7450; | |
870 | ||
3faad673 MG |
871 | val = int_sqrt(debtime * 1000 / 115); |
872 | if (((val + 1) * val) < debtime * 1000 / 115) | |
873 | val += 1; | |
874 | ||
da7b18ee | 875 | devpriv->usb_tx_buf[6 + chan] = val; |
3faad673 | 876 | |
da7b18ee | 877 | if (vmk80xx_write_packet(devpriv, cmd)) |
985cafcc | 878 | break; |
3faad673 MG |
879 | } |
880 | ||
da7b18ee | 881 | up(&devpriv->limit_sem); |
3faad673 | 882 | |
985cafcc MG |
883 | return n; |
884 | } | |
3faad673 | 885 | |
9a23a748 HS |
886 | static int vmk80xx_pwm_insn_read(struct comedi_device *dev, |
887 | struct comedi_subdevice *s, | |
888 | struct comedi_insn *insn, | |
889 | unsigned int *data) | |
985cafcc | 890 | { |
da7b18ee HS |
891 | struct vmk80xx_private *devpriv = dev->private; |
892 | unsigned char *tx_buf; | |
893 | unsigned char *rx_buf; | |
985cafcc MG |
894 | int reg[2]; |
895 | int n; | |
896 | ||
da7b18ee | 897 | n = rudimentary_check(devpriv, DIR_IN); |
587e500c AH |
898 | if (n) |
899 | return n; | |
985cafcc | 900 | |
da7b18ee HS |
901 | down(&devpriv->limit_sem); |
902 | ||
903 | tx_buf = devpriv->usb_tx_buf; | |
904 | rx_buf = devpriv->usb_rx_buf; | |
985cafcc MG |
905 | |
906 | reg[0] = VMK8061_PWM_REG1; | |
907 | reg[1] = VMK8061_PWM_REG2; | |
908 | ||
da7b18ee | 909 | tx_buf[0] = VMK8061_CMD_RD_PWM; |
985cafcc MG |
910 | |
911 | for (n = 0; n < insn->n; n++) { | |
da7b18ee | 912 | if (vmk80xx_read_packet(devpriv)) |
985cafcc MG |
913 | break; |
914 | ||
da7b18ee | 915 | data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; |
985cafcc MG |
916 | } |
917 | ||
da7b18ee | 918 | up(&devpriv->limit_sem); |
985cafcc MG |
919 | |
920 | return n; | |
3faad673 MG |
921 | } |
922 | ||
9a23a748 HS |
923 | static int vmk80xx_pwm_insn_write(struct comedi_device *dev, |
924 | struct comedi_subdevice *s, | |
925 | struct comedi_insn *insn, | |
926 | unsigned int *data) | |
985cafcc | 927 | { |
da7b18ee | 928 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 929 | unsigned char *tx_buf; |
3a229fd5 AH |
930 | int reg[2]; |
931 | int cmd; | |
985cafcc | 932 | int n; |
3faad673 | 933 | |
da7b18ee | 934 | n = rudimentary_check(devpriv, DIR_OUT); |
587e500c AH |
935 | if (n) |
936 | return n; | |
985cafcc | 937 | |
da7b18ee | 938 | down(&devpriv->limit_sem); |
985cafcc | 939 | |
da7b18ee | 940 | tx_buf = devpriv->usb_tx_buf; |
985cafcc MG |
941 | |
942 | reg[0] = VMK8061_PWM_REG1; | |
943 | reg[1] = VMK8061_PWM_REG2; | |
944 | ||
945 | cmd = VMK8061_CMD_OUT_PWM; | |
946 | ||
947 | /* | |
948 | * The followin piece of code was translated from the inline | |
949 | * assembler code in the DLL source code. | |
950 | * | |
951 | * asm | |
952 | * mov eax, k ; k is the value (data[n]) | |
953 | * and al, 03h ; al are the lower 8 bits of eax | |
954 | * mov lo, al ; lo is the low part (tx_buf[reg[0]]) | |
955 | * mov eax, k | |
956 | * shr eax, 2 ; right shift eax register by 2 | |
957 | * mov hi, al ; hi is the high part (tx_buf[reg[1]]) | |
958 | * end; | |
959 | */ | |
960 | for (n = 0; n < insn->n; n++) { | |
961 | tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); | |
962 | tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; | |
963 | ||
da7b18ee | 964 | if (vmk80xx_write_packet(devpriv, cmd)) |
985cafcc MG |
965 | break; |
966 | } | |
3faad673 | 967 | |
da7b18ee | 968 | up(&devpriv->limit_sem); |
985cafcc MG |
969 | |
970 | return n; | |
971 | } | |
972 | ||
57cf09ae | 973 | static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) |
49253d54 | 974 | { |
57cf09ae HS |
975 | struct vmk80xx_private *devpriv = dev->private; |
976 | struct usb_interface *intf = devpriv->intf; | |
49253d54 HS |
977 | struct usb_host_interface *iface_desc = intf->cur_altsetting; |
978 | struct usb_endpoint_descriptor *ep_desc; | |
979 | int i; | |
980 | ||
981 | if (iface_desc->desc.bNumEndpoints != 2) | |
982 | return -ENODEV; | |
983 | ||
984 | for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { | |
985 | ep_desc = &iface_desc->endpoint[i].desc; | |
986 | ||
987 | if (usb_endpoint_is_int_in(ep_desc) || | |
988 | usb_endpoint_is_bulk_in(ep_desc)) { | |
989 | if (!devpriv->ep_rx) | |
990 | devpriv->ep_rx = ep_desc; | |
991 | continue; | |
992 | } | |
993 | ||
994 | if (usb_endpoint_is_int_out(ep_desc) || | |
995 | usb_endpoint_is_bulk_out(ep_desc)) { | |
996 | if (!devpriv->ep_tx) | |
997 | devpriv->ep_tx = ep_desc; | |
998 | continue; | |
999 | } | |
1000 | } | |
1001 | ||
1002 | if (!devpriv->ep_rx || !devpriv->ep_tx) | |
1003 | return -ENODEV; | |
1004 | ||
1005 | return 0; | |
1006 | } | |
1007 | ||
57cf09ae | 1008 | static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) |
78f8fa7f | 1009 | { |
57cf09ae | 1010 | struct vmk80xx_private *devpriv = dev->private; |
78f8fa7f HS |
1011 | size_t size; |
1012 | ||
1013 | size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize); | |
1014 | devpriv->usb_rx_buf = kmalloc(size, GFP_KERNEL); | |
1015 | if (!devpriv->usb_rx_buf) | |
1016 | return -ENOMEM; | |
1017 | ||
1018 | size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize); | |
1019 | devpriv->usb_tx_buf = kmalloc(size, GFP_KERNEL); | |
1020 | if (!devpriv->usb_tx_buf) { | |
1021 | kfree(devpriv->usb_rx_buf); | |
1022 | return -ENOMEM; | |
1023 | } | |
1024 | ||
1025 | return 0; | |
1026 | } | |
1027 | ||
57cf09ae | 1028 | static int vmk80xx_attach_common(struct comedi_device *dev) |
3faad673 | 1029 | { |
57cf09ae HS |
1030 | const struct vmk80xx_board *boardinfo = comedi_board(dev); |
1031 | struct vmk80xx_private *devpriv = dev->private; | |
b153d83e | 1032 | struct comedi_subdevice *s; |
57cf09ae | 1033 | int n_subd; |
8b6c5694 | 1034 | int ret; |
3faad673 | 1035 | |
da7b18ee | 1036 | down(&devpriv->limit_sem); |
0dd772bf | 1037 | |
52d895d3 | 1038 | if (devpriv->model == VMK8055_MODEL) |
985cafcc MG |
1039 | n_subd = 5; |
1040 | else | |
1041 | n_subd = 6; | |
da7b18ee | 1042 | ret = comedi_alloc_subdevices(dev, n_subd); |
8b6c5694 | 1043 | if (ret) { |
da7b18ee | 1044 | up(&devpriv->limit_sem); |
8b6c5694 | 1045 | return ret; |
3faad673 | 1046 | } |
0dd772bf | 1047 | |
985cafcc | 1048 | /* Analog input subdevice */ |
da7b18ee | 1049 | s = &dev->subdevices[0]; |
658cd3ac HS |
1050 | s->type = COMEDI_SUBD_AI; |
1051 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
1052 | s->n_chan = boardinfo->ai_nchans; | |
1053 | s->maxdata = boardinfo->ai_maxdata; | |
1054 | s->range_table = boardinfo->range; | |
1055 | s->insn_read = vmk80xx_ai_insn_read; | |
0dd772bf | 1056 | |
985cafcc | 1057 | /* Analog output subdevice */ |
da7b18ee | 1058 | s = &dev->subdevices[1]; |
8b3ec9f1 HS |
1059 | s->type = COMEDI_SUBD_AO; |
1060 | s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; | |
1061 | s->n_chan = boardinfo->ao_nchans; | |
1062 | s->maxdata = 0x00ff; | |
1063 | s->range_table = boardinfo->range; | |
1064 | s->insn_write = vmk80xx_ao_insn_write; | |
52d895d3 | 1065 | if (devpriv->model == VMK8061_MODEL) { |
8b3ec9f1 HS |
1066 | s->subdev_flags |= SDF_READABLE; |
1067 | s->insn_read = vmk80xx_ao_insn_read; | |
985cafcc | 1068 | } |
0dd772bf | 1069 | |
985cafcc | 1070 | /* Digital input subdevice */ |
da7b18ee | 1071 | s = &dev->subdevices[2]; |
268e5148 HS |
1072 | s->type = COMEDI_SUBD_DI; |
1073 | s->subdev_flags = SDF_READABLE; | |
1074 | s->n_chan = boardinfo->di_nchans; | |
1075 | s->maxdata = 1; | |
1076 | s->range_table = &range_digital; | |
268e5148 | 1077 | s->insn_bits = vmk80xx_di_insn_bits; |
0dd772bf | 1078 | |
985cafcc | 1079 | /* Digital output subdevice */ |
da7b18ee | 1080 | s = &dev->subdevices[3]; |
b639e096 HS |
1081 | s->type = COMEDI_SUBD_DO; |
1082 | s->subdev_flags = SDF_WRITEABLE; | |
1083 | s->n_chan = 8; | |
1084 | s->maxdata = 1; | |
1085 | s->range_table = &range_digital; | |
b639e096 | 1086 | s->insn_bits = vmk80xx_do_insn_bits; |
0dd772bf | 1087 | |
985cafcc | 1088 | /* Counter subdevice */ |
da7b18ee | 1089 | s = &dev->subdevices[4]; |
75a45d92 HS |
1090 | s->type = COMEDI_SUBD_COUNTER; |
1091 | s->subdev_flags = SDF_READABLE; | |
1092 | s->n_chan = 2; | |
1093 | s->maxdata = boardinfo->cnt_maxdata; | |
1094 | s->insn_read = vmk80xx_cnt_insn_read; | |
1095 | s->insn_config = vmk80xx_cnt_insn_config; | |
52d895d3 | 1096 | if (devpriv->model == VMK8055_MODEL) { |
75a45d92 HS |
1097 | s->subdev_flags |= SDF_WRITEABLE; |
1098 | s->insn_write = vmk80xx_cnt_insn_write; | |
985cafcc | 1099 | } |
0dd772bf | 1100 | |
985cafcc | 1101 | /* PWM subdevice */ |
52d895d3 | 1102 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee | 1103 | s = &dev->subdevices[5]; |
9a23a748 HS |
1104 | s->type = COMEDI_SUBD_PWM; |
1105 | s->subdev_flags = SDF_READABLE | SDF_WRITEABLE; | |
1106 | s->n_chan = boardinfo->pwm_nchans; | |
1107 | s->maxdata = boardinfo->pwm_maxdata; | |
1108 | s->insn_read = vmk80xx_pwm_insn_read; | |
1109 | s->insn_write = vmk80xx_pwm_insn_write; | |
985cafcc | 1110 | } |
0dd772bf | 1111 | |
da7b18ee | 1112 | up(&devpriv->limit_sem); |
0dd772bf | 1113 | |
f7d4d3bc IA |
1114 | return 0; |
1115 | } | |
3faad673 | 1116 | |
da7b18ee | 1117 | static int vmk80xx_auto_attach(struct comedi_device *dev, |
57cf09ae | 1118 | unsigned long context) |
f7d4d3bc | 1119 | { |
da7b18ee | 1120 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
0dd772bf | 1121 | const struct vmk80xx_board *boardinfo; |
da7b18ee | 1122 | struct vmk80xx_private *devpriv; |
49253d54 | 1123 | int ret; |
3faad673 | 1124 | |
57cf09ae HS |
1125 | boardinfo = &vmk80xx_boardinfo[context]; |
1126 | dev->board_ptr = boardinfo; | |
1127 | dev->board_name = boardinfo->name; | |
3faad673 | 1128 | |
57cf09ae HS |
1129 | devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); |
1130 | if (!devpriv) | |
1131 | return -ENOMEM; | |
1132 | dev->private = devpriv; | |
3faad673 | 1133 | |
57cf09ae HS |
1134 | devpriv->usb = interface_to_usbdev(intf); |
1135 | devpriv->intf = intf; | |
52d895d3 | 1136 | devpriv->model = boardinfo->model; |
985cafcc | 1137 | |
57cf09ae | 1138 | ret = vmk80xx_find_usb_endpoints(dev); |
db7dabf7 | 1139 | if (ret) |
57cf09ae | 1140 | return ret; |
3faad673 | 1141 | |
57cf09ae | 1142 | ret = vmk80xx_alloc_usb_buffers(dev); |
db7dabf7 | 1143 | if (ret) |
57cf09ae | 1144 | return ret; |
985cafcc | 1145 | |
da7b18ee HS |
1146 | sema_init(&devpriv->limit_sem, 8); |
1147 | init_waitqueue_head(&devpriv->read_wait); | |
1148 | init_waitqueue_head(&devpriv->write_wait); | |
985cafcc | 1149 | |
da7b18ee HS |
1150 | init_usb_anchor(&devpriv->rx_anchor); |
1151 | init_usb_anchor(&devpriv->tx_anchor); | |
985cafcc | 1152 | |
da7b18ee | 1153 | usb_set_intfdata(intf, devpriv); |
985cafcc | 1154 | |
52d895d3 | 1155 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee HS |
1156 | vmk80xx_read_eeprom(devpriv, IC3_VERSION); |
1157 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic3_vers); | |
985cafcc | 1158 | |
da7b18ee HS |
1159 | if (vmk80xx_check_data_link(devpriv)) { |
1160 | vmk80xx_read_eeprom(devpriv, IC6_VERSION); | |
1161 | dev_info(&intf->dev, "%s\n", devpriv->fw.ic6_vers); | |
3a229fd5 | 1162 | } |
3faad673 MG |
1163 | } |
1164 | ||
52d895d3 | 1165 | if (devpriv->model == VMK8055_MODEL) |
da7b18ee | 1166 | vmk80xx_reset_device(devpriv); |
3faad673 | 1167 | |
57cf09ae HS |
1168 | return vmk80xx_attach_common(dev); |
1169 | } | |
3faad673 | 1170 | |
57cf09ae HS |
1171 | static void vmk80xx_detach(struct comedi_device *dev) |
1172 | { | |
1173 | struct vmk80xx_private *devpriv = dev->private; | |
8ba69ce4 | 1174 | |
57cf09ae HS |
1175 | if (!devpriv) |
1176 | return; | |
db7dabf7 | 1177 | |
57cf09ae HS |
1178 | down(&devpriv->limit_sem); |
1179 | ||
1180 | usb_set_intfdata(devpriv->intf, NULL); | |
1181 | ||
1182 | usb_kill_anchored_urbs(&devpriv->rx_anchor); | |
1183 | usb_kill_anchored_urbs(&devpriv->tx_anchor); | |
1184 | ||
1185 | kfree(devpriv->usb_rx_buf); | |
1186 | kfree(devpriv->usb_tx_buf); | |
1187 | ||
1188 | up(&devpriv->limit_sem); | |
1189 | } | |
1190 | ||
1191 | static struct comedi_driver vmk80xx_driver = { | |
1192 | .module = THIS_MODULE, | |
1193 | .driver_name = "vmk80xx", | |
1194 | .auto_attach = vmk80xx_auto_attach, | |
1195 | .detach = vmk80xx_detach, | |
1196 | }; | |
1197 | ||
1198 | static int vmk80xx_usb_probe(struct usb_interface *intf, | |
1199 | const struct usb_device_id *id) | |
1200 | { | |
1201 | return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info); | |
3faad673 MG |
1202 | } |
1203 | ||
007ff2af HS |
1204 | static const struct usb_device_id vmk80xx_usb_id_table[] = { |
1205 | { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 }, | |
1206 | { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 }, | |
1207 | { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 }, | |
1208 | { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 }, | |
1209 | { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 }, | |
1210 | { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 }, | |
1211 | { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 }, | |
1212 | { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 }, | |
1213 | { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 }, | |
1214 | { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 }, | |
1215 | { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 }, | |
1216 | { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 }, | |
1217 | { } | |
1218 | }; | |
1219 | MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table); | |
1220 | ||
d6cc3ec8 HS |
1221 | static struct usb_driver vmk80xx_usb_driver = { |
1222 | .name = "vmk80xx", | |
007ff2af | 1223 | .id_table = vmk80xx_usb_id_table, |
ce874227 HS |
1224 | .probe = vmk80xx_usb_probe, |
1225 | .disconnect = comedi_usb_auto_unconfig, | |
3faad673 | 1226 | }; |
d6cc3ec8 | 1227 | module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); |
007ff2af HS |
1228 | |
1229 | MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); | |
1230 | MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); | |
1231 | MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); | |
1232 | MODULE_VERSION("0.8.01"); | |
1233 | MODULE_LICENSE("GPL"); |