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