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