Commit | Line | Data |
---|---|---|
3faad673 | 1 | /* |
0726f06d HS |
2 | * vmk80xx.c |
3 | * Velleman USB Board Low-Level Driver | |
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 | */ | |
3faad673 | 20 | |
3faad673 | 21 | /* |
b1ad9684 IA |
22 | * Driver: vmk80xx |
23 | * Description: Velleman USB Board Low-Level Driver | |
24 | * Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140), | |
25 | * VM110 (K8055/VM110), VM140 (K8061/VM140) | |
26 | * Author: Manuel Gebele <forensixs@gmx.de> | |
27 | * Updated: Sun, 10 May 2009 11:14:59 +0200 | |
28 | * Status: works | |
29 | * | |
30 | * Supports: | |
31 | * - analog input | |
32 | * - analog output | |
33 | * - digital input | |
34 | * - digital output | |
35 | * - counter | |
36 | * - pwm | |
37 | */ | |
3faad673 MG |
38 | |
39 | #include <linux/kernel.h> | |
3faad673 MG |
40 | #include <linux/module.h> |
41 | #include <linux/mutex.h> | |
42 | #include <linux/errno.h> | |
43 | #include <linux/input.h> | |
44 | #include <linux/slab.h> | |
45 | #include <linux/poll.h> | |
985cafcc MG |
46 | #include <linux/uaccess.h> |
47 | ||
cf7661c7 | 48 | #include "../comedi_usb.h" |
985cafcc | 49 | |
985cafcc MG |
50 | enum { |
51 | DEVICE_VMK8055, | |
52 | DEVICE_VMK8061 | |
53 | }; | |
54 | ||
c9b9cfe7 HS |
55 | #define VMK8055_DI_REG 0x00 |
56 | #define VMK8055_DO_REG 0x01 | |
57 | #define VMK8055_AO1_REG 0x02 | |
58 | #define VMK8055_AO2_REG 0x03 | |
59 | #define VMK8055_AI1_REG 0x02 | |
60 | #define VMK8055_AI2_REG 0x03 | |
61 | #define VMK8055_CNT1_REG 0x04 | |
62 | #define VMK8055_CNT2_REG 0x06 | |
985cafcc | 63 | |
c9b9cfe7 HS |
64 | #define VMK8061_CH_REG 0x01 |
65 | #define VMK8061_DI_REG 0x01 | |
66 | #define VMK8061_DO_REG 0x01 | |
67 | #define VMK8061_PWM_REG1 0x01 | |
68 | #define VMK8061_PWM_REG2 0x02 | |
69 | #define VMK8061_CNT_REG 0x02 | |
70 | #define VMK8061_AO_REG 0x02 | |
71 | #define VMK8061_AI_REG1 0x02 | |
72 | #define VMK8061_AI_REG2 0x03 | |
985cafcc | 73 | |
c9b9cfe7 HS |
74 | #define VMK8055_CMD_RST 0x00 |
75 | #define VMK8055_CMD_DEB1_TIME 0x01 | |
76 | #define VMK8055_CMD_DEB2_TIME 0x02 | |
77 | #define VMK8055_CMD_RST_CNT1 0x03 | |
78 | #define VMK8055_CMD_RST_CNT2 0x04 | |
79 | #define VMK8055_CMD_WRT_AD 0x05 | |
985cafcc | 80 | |
c9b9cfe7 HS |
81 | #define VMK8061_CMD_RD_AI 0x00 |
82 | #define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */ | |
83 | #define VMK8061_CMD_SET_AO 0x02 | |
84 | #define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */ | |
85 | #define VMK8061_CMD_OUT_PWM 0x04 | |
86 | #define VMK8061_CMD_RD_DI 0x05 | |
87 | #define VMK8061_CMD_DO 0x06 /* !non-active! */ | |
88 | #define VMK8061_CMD_CLR_DO 0x07 | |
89 | #define VMK8061_CMD_SET_DO 0x08 | |
90 | #define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */ | |
91 | #define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */ | |
92 | #define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */ | |
93 | #define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */ | |
94 | #define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */ | |
95 | #define VMK8061_CMD_RD_DO 0x0e | |
96 | #define VMK8061_CMD_RD_AO 0x0f | |
97 | #define VMK8061_CMD_RD_PWM 0x10 | |
985cafcc | 98 | |
c9b9cfe7 HS |
99 | #define IC3_VERSION BIT(0) |
100 | #define IC6_VERSION BIT(1) | |
985cafcc | 101 | |
985cafcc MG |
102 | enum vmk80xx_model { |
103 | VMK8055_MODEL, | |
104 | VMK8061_MODEL | |
105 | }; | |
3faad673 | 106 | |
985cafcc | 107 | static const struct comedi_lrange vmk8061_range = { |
f45787c6 HS |
108 | 2, { |
109 | UNI_RANGE(5), | |
110 | UNI_RANGE(10) | |
111 | } | |
985cafcc | 112 | }; |
3faad673 | 113 | |
985cafcc MG |
114 | struct vmk80xx_board { |
115 | const char *name; | |
116 | enum vmk80xx_model model; | |
117 | const struct comedi_lrange *range; | |
658cd3ac HS |
118 | int ai_nchans; |
119 | unsigned int ai_maxdata; | |
8b3ec9f1 | 120 | int ao_nchans; |
268e5148 | 121 | int di_nchans; |
75a45d92 | 122 | unsigned int cnt_maxdata; |
9a23a748 HS |
123 | int pwm_nchans; |
124 | unsigned int pwm_maxdata; | |
985cafcc | 125 | }; |
3faad673 | 126 | |
20d60077 HS |
127 | static const struct vmk80xx_board vmk80xx_boardinfo[] = { |
128 | [DEVICE_VMK8055] = { | |
129 | .name = "K8055 (VM110)", | |
130 | .model = VMK8055_MODEL, | |
f45787c6 | 131 | .range = &range_unipolar5, |
658cd3ac HS |
132 | .ai_nchans = 2, |
133 | .ai_maxdata = 0x00ff, | |
8b3ec9f1 | 134 | .ao_nchans = 2, |
268e5148 | 135 | .di_nchans = 6, |
75a45d92 | 136 | .cnt_maxdata = 0xffff, |
20d60077 HS |
137 | }, |
138 | [DEVICE_VMK8061] = { | |
139 | .name = "K8061 (VM140)", | |
140 | .model = VMK8061_MODEL, | |
141 | .range = &vmk8061_range, | |
658cd3ac HS |
142 | .ai_nchans = 8, |
143 | .ai_maxdata = 0x03ff, | |
8b3ec9f1 | 144 | .ao_nchans = 8, |
268e5148 | 145 | .di_nchans = 8, |
75a45d92 | 146 | .cnt_maxdata = 0, /* unknown, device is not writeable */ |
9a23a748 HS |
147 | .pwm_nchans = 1, |
148 | .pwm_maxdata = 0x03ff, | |
20d60077 HS |
149 | }, |
150 | }; | |
151 | ||
dc49cbfc | 152 | struct vmk80xx_private { |
985cafcc MG |
153 | struct usb_endpoint_descriptor *ep_rx; |
154 | struct usb_endpoint_descriptor *ep_tx; | |
985cafcc | 155 | struct semaphore limit_sem; |
985cafcc MG |
156 | unsigned char *usb_rx_buf; |
157 | unsigned char *usb_tx_buf; | |
52d895d3 | 158 | enum vmk80xx_model model; |
985cafcc MG |
159 | }; |
160 | ||
0703c955 | 161 | static void vmk80xx_do_bulk_msg(struct comedi_device *dev) |
985cafcc | 162 | { |
0703c955 | 163 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 164 | struct usb_device *usb = comedi_to_usb_dev(dev); |
3a229fd5 AH |
165 | __u8 tx_addr; |
166 | __u8 rx_addr; | |
167 | unsigned int tx_pipe; | |
168 | unsigned int rx_pipe; | |
985cafcc | 169 | size_t size; |
3faad673 | 170 | |
da7b18ee HS |
171 | tx_addr = devpriv->ep_tx->bEndpointAddress; |
172 | rx_addr = devpriv->ep_rx->bEndpointAddress; | |
173 | tx_pipe = usb_sndbulkpipe(usb, tx_addr); | |
174 | rx_pipe = usb_rcvbulkpipe(usb, rx_addr); | |
985cafcc | 175 | |
3a229fd5 AH |
176 | /* |
177 | * The max packet size attributes of the K8061 | |
178 | * input/output endpoints are identical | |
179 | */ | |
62190d49 | 180 | size = usb_endpoint_maxp(devpriv->ep_tx); |
985cafcc | 181 | |
da7b18ee HS |
182 | usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, |
183 | size, NULL, devpriv->ep_tx->bInterval); | |
184 | usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10); | |
3faad673 MG |
185 | } |
186 | ||
0703c955 | 187 | static int vmk80xx_read_packet(struct comedi_device *dev) |
3faad673 | 188 | { |
0703c955 | 189 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 190 | struct usb_device *usb = comedi_to_usb_dev(dev); |
951348b3 IA |
191 | struct usb_endpoint_descriptor *ep; |
192 | unsigned int pipe; | |
3faad673 | 193 | |
52d895d3 | 194 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 195 | vmk80xx_do_bulk_msg(dev); |
985cafcc | 196 | return 0; |
3faad673 MG |
197 | } |
198 | ||
951348b3 IA |
199 | ep = devpriv->ep_rx; |
200 | pipe = usb_rcvintpipe(usb, ep->bEndpointAddress); | |
201 | return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf, | |
62190d49 | 202 | usb_endpoint_maxp(ep), NULL, |
951348b3 | 203 | HZ * 10); |
3faad673 MG |
204 | } |
205 | ||
0703c955 | 206 | static int vmk80xx_write_packet(struct comedi_device *dev, int cmd) |
3faad673 | 207 | { |
0703c955 | 208 | struct vmk80xx_private *devpriv = dev->private; |
db4c3eb7 | 209 | struct usb_device *usb = comedi_to_usb_dev(dev); |
951348b3 IA |
210 | struct usb_endpoint_descriptor *ep; |
211 | unsigned int pipe; | |
3faad673 | 212 | |
951348b3 | 213 | devpriv->usb_tx_buf[0] = cmd; |
3faad673 | 214 | |
52d895d3 | 215 | if (devpriv->model == VMK8061_MODEL) { |
0703c955 | 216 | vmk80xx_do_bulk_msg(dev); |
985cafcc | 217 | return 0; |
3faad673 MG |
218 | } |
219 | ||
951348b3 IA |
220 | ep = devpriv->ep_tx; |
221 | pipe = usb_sndintpipe(usb, ep->bEndpointAddress); | |
222 | return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf, | |
62190d49 | 223 | usb_endpoint_maxp(ep), NULL, |
951348b3 | 224 | HZ * 10); |
3faad673 MG |
225 | } |
226 | ||
0703c955 | 227 | static int vmk80xx_reset_device(struct comedi_device *dev) |
e8f311a5 | 228 | { |
0703c955 | 229 | struct vmk80xx_private *devpriv = dev->private; |
e8f311a5 | 230 | size_t size; |
f06a23c9 | 231 | int retval; |
e8f311a5 | 232 | |
62190d49 | 233 | size = usb_endpoint_maxp(devpriv->ep_tx); |
e8f311a5 | 234 | memset(devpriv->usb_tx_buf, 0, size); |
0703c955 | 235 | retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST); |
f06a23c9 IA |
236 | if (retval) |
237 | return retval; | |
238 | /* set outputs to known state as we cannot read them */ | |
0703c955 | 239 | return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD); |
e8f311a5 IA |
240 | } |
241 | ||
658cd3ac HS |
242 | static int vmk80xx_ai_insn_read(struct comedi_device *dev, |
243 | struct comedi_subdevice *s, | |
244 | struct comedi_insn *insn, | |
245 | unsigned int *data) | |
3faad673 | 246 | { |
da7b18ee | 247 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
248 | int chan; |
249 | int reg[2]; | |
985cafcc | 250 | int n; |
3faad673 | 251 | |
da7b18ee | 252 | down(&devpriv->limit_sem); |
985cafcc | 253 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 254 | |
52d895d3 | 255 | switch (devpriv->model) { |
985cafcc MG |
256 | case VMK8055_MODEL: |
257 | if (!chan) | |
258 | reg[0] = VMK8055_AI1_REG; | |
259 | else | |
260 | reg[0] = VMK8055_AI2_REG; | |
261 | break; | |
262 | case VMK8061_MODEL: | |
13f7952f | 263 | default: |
985cafcc MG |
264 | reg[0] = VMK8061_AI_REG1; |
265 | reg[1] = VMK8061_AI_REG2; | |
da7b18ee HS |
266 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI; |
267 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; | |
985cafcc | 268 | break; |
3faad673 MG |
269 | } |
270 | ||
985cafcc | 271 | for (n = 0; n < insn->n; n++) { |
0703c955 | 272 | if (vmk80xx_read_packet(dev)) |
985cafcc | 273 | break; |
3faad673 | 274 | |
52d895d3 | 275 | if (devpriv->model == VMK8055_MODEL) { |
da7b18ee | 276 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
985cafcc MG |
277 | continue; |
278 | } | |
3faad673 | 279 | |
985cafcc | 280 | /* VMK8061_MODEL */ |
da7b18ee HS |
281 | data[n] = devpriv->usb_rx_buf[reg[0]] + 256 * |
282 | devpriv->usb_rx_buf[reg[1]]; | |
985cafcc | 283 | } |
3faad673 | 284 | |
da7b18ee | 285 | up(&devpriv->limit_sem); |
3faad673 | 286 | |
985cafcc | 287 | return n; |
3faad673 MG |
288 | } |
289 | ||
8b3ec9f1 HS |
290 | static int vmk80xx_ao_insn_write(struct comedi_device *dev, |
291 | struct comedi_subdevice *s, | |
292 | struct comedi_insn *insn, | |
293 | unsigned int *data) | |
3faad673 | 294 | { |
da7b18ee | 295 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
296 | int chan; |
297 | int cmd; | |
298 | int reg; | |
985cafcc | 299 | int n; |
3faad673 | 300 | |
da7b18ee | 301 | down(&devpriv->limit_sem); |
985cafcc | 302 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 303 | |
52d895d3 | 304 | switch (devpriv->model) { |
985cafcc MG |
305 | case VMK8055_MODEL: |
306 | cmd = VMK8055_CMD_WRT_AD; | |
307 | if (!chan) | |
308 | reg = VMK8055_AO1_REG; | |
309 | else | |
310 | reg = VMK8055_AO2_REG; | |
311 | break; | |
0a85b6f0 | 312 | default: /* NOTE: avoid compiler warnings */ |
985cafcc MG |
313 | cmd = VMK8061_CMD_SET_AO; |
314 | reg = VMK8061_AO_REG; | |
da7b18ee | 315 | devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; |
985cafcc | 316 | break; |
3faad673 MG |
317 | } |
318 | ||
985cafcc | 319 | for (n = 0; n < insn->n; n++) { |
da7b18ee | 320 | devpriv->usb_tx_buf[reg] = data[n]; |
3faad673 | 321 | |
0703c955 | 322 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc | 323 | break; |
3faad673 MG |
324 | } |
325 | ||
da7b18ee | 326 | up(&devpriv->limit_sem); |
3faad673 | 327 | |
985cafcc | 328 | return n; |
3faad673 MG |
329 | } |
330 | ||
8b3ec9f1 HS |
331 | static int vmk80xx_ao_insn_read(struct comedi_device *dev, |
332 | struct comedi_subdevice *s, | |
333 | struct comedi_insn *insn, | |
334 | unsigned int *data) | |
3faad673 | 335 | { |
da7b18ee | 336 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
337 | int chan; |
338 | int reg; | |
985cafcc | 339 | int n; |
3faad673 | 340 | |
da7b18ee | 341 | down(&devpriv->limit_sem); |
985cafcc | 342 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 343 | |
985cafcc | 344 | reg = VMK8061_AO_REG - 1; |
3faad673 | 345 | |
da7b18ee | 346 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO; |
985cafcc MG |
347 | |
348 | for (n = 0; n < insn->n; n++) { | |
0703c955 | 349 | if (vmk80xx_read_packet(dev)) |
985cafcc MG |
350 | break; |
351 | ||
da7b18ee | 352 | data[n] = devpriv->usb_rx_buf[reg + chan]; |
3faad673 MG |
353 | } |
354 | ||
da7b18ee | 355 | up(&devpriv->limit_sem); |
3faad673 | 356 | |
985cafcc MG |
357 | return n; |
358 | } | |
3faad673 | 359 | |
268e5148 HS |
360 | static int vmk80xx_di_insn_bits(struct comedi_device *dev, |
361 | struct comedi_subdevice *s, | |
362 | struct comedi_insn *insn, | |
363 | unsigned int *data) | |
c647ed56 | 364 | { |
da7b18ee | 365 | struct vmk80xx_private *devpriv = dev->private; |
c647ed56 AH |
366 | unsigned char *rx_buf; |
367 | int reg; | |
368 | int retval; | |
369 | ||
da7b18ee | 370 | down(&devpriv->limit_sem); |
c647ed56 | 371 | |
da7b18ee | 372 | rx_buf = devpriv->usb_rx_buf; |
c647ed56 | 373 | |
52d895d3 | 374 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 | 375 | reg = VMK8061_DI_REG; |
da7b18ee | 376 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI; |
c647ed56 AH |
377 | } else { |
378 | reg = VMK8055_DI_REG; | |
379 | } | |
380 | ||
0703c955 | 381 | retval = vmk80xx_read_packet(dev); |
c647ed56 AH |
382 | |
383 | if (!retval) { | |
52d895d3 | 384 | if (devpriv->model == VMK8055_MODEL) |
c647ed56 AH |
385 | data[1] = (((rx_buf[reg] >> 4) & 0x03) | |
386 | ((rx_buf[reg] << 2) & 0x04) | | |
387 | ((rx_buf[reg] >> 3) & 0x18)); | |
388 | else | |
389 | data[1] = rx_buf[reg]; | |
390 | ||
391 | retval = 2; | |
392 | } | |
393 | ||
da7b18ee | 394 | up(&devpriv->limit_sem); |
c647ed56 AH |
395 | |
396 | return retval; | |
397 | } | |
398 | ||
b639e096 HS |
399 | static int vmk80xx_do_insn_bits(struct comedi_device *dev, |
400 | struct comedi_subdevice *s, | |
401 | struct comedi_insn *insn, | |
402 | unsigned int *data) | |
c647ed56 | 403 | { |
da7b18ee | 404 | struct vmk80xx_private *devpriv = dev->private; |
6f617e54 HS |
405 | unsigned char *rx_buf = devpriv->usb_rx_buf; |
406 | unsigned char *tx_buf = devpriv->usb_tx_buf; | |
951348b3 | 407 | int reg, cmd; |
c16975a0 | 408 | int ret = 0; |
c647ed56 | 409 | |
fc9ca48e | 410 | if (devpriv->model == VMK8061_MODEL) { |
fc9ca48e PH |
411 | reg = VMK8061_DO_REG; |
412 | cmd = VMK8061_CMD_DO; | |
413 | } else { /* VMK8055_MODEL */ | |
414 | reg = VMK8055_DO_REG; | |
415 | cmd = VMK8055_CMD_WRT_AD; | |
416 | } | |
c647ed56 | 417 | |
da7b18ee | 418 | down(&devpriv->limit_sem); |
c647ed56 | 419 | |
6f617e54 HS |
420 | if (comedi_dio_update_state(s, data)) { |
421 | tx_buf[reg] = s->state; | |
422 | ret = vmk80xx_write_packet(dev, cmd); | |
423 | if (ret) | |
c647ed56 AH |
424 | goto out; |
425 | } | |
426 | ||
52d895d3 | 427 | if (devpriv->model == VMK8061_MODEL) { |
c647ed56 | 428 | tx_buf[0] = VMK8061_CMD_RD_DO; |
6f617e54 HS |
429 | ret = vmk80xx_read_packet(dev); |
430 | if (ret) | |
431 | goto out; | |
432 | data[1] = rx_buf[reg]; | |
c647ed56 | 433 | } else { |
6f617e54 | 434 | data[1] = s->state; |
c647ed56 AH |
435 | } |
436 | ||
437 | out: | |
da7b18ee | 438 | up(&devpriv->limit_sem); |
c647ed56 | 439 | |
6f617e54 | 440 | return ret ? ret : insn->n; |
c647ed56 AH |
441 | } |
442 | ||
75a45d92 HS |
443 | static int vmk80xx_cnt_insn_read(struct comedi_device *dev, |
444 | struct comedi_subdevice *s, | |
445 | struct comedi_insn *insn, | |
446 | unsigned int *data) | |
985cafcc | 447 | { |
da7b18ee | 448 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
449 | int chan; |
450 | int reg[2]; | |
985cafcc MG |
451 | int n; |
452 | ||
da7b18ee | 453 | down(&devpriv->limit_sem); |
985cafcc | 454 | chan = CR_CHAN(insn->chanspec); |
3faad673 | 455 | |
52d895d3 | 456 | switch (devpriv->model) { |
985cafcc MG |
457 | case VMK8055_MODEL: |
458 | if (!chan) | |
459 | reg[0] = VMK8055_CNT1_REG; | |
460 | else | |
461 | reg[0] = VMK8055_CNT2_REG; | |
462 | break; | |
463 | case VMK8061_MODEL: | |
13f7952f | 464 | default: |
985cafcc MG |
465 | reg[0] = VMK8061_CNT_REG; |
466 | reg[1] = VMK8061_CNT_REG; | |
da7b18ee | 467 | devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; |
985cafcc | 468 | break; |
3faad673 MG |
469 | } |
470 | ||
985cafcc | 471 | for (n = 0; n < insn->n; n++) { |
0703c955 | 472 | if (vmk80xx_read_packet(dev)) |
985cafcc | 473 | break; |
3faad673 | 474 | |
52d895d3 | 475 | if (devpriv->model == VMK8055_MODEL) |
da7b18ee | 476 | data[n] = devpriv->usb_rx_buf[reg[0]]; |
3a229fd5 | 477 | else /* VMK8061_MODEL */ |
da7b18ee HS |
478 | data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1] |
479 | + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2]; | |
985cafcc MG |
480 | } |
481 | ||
da7b18ee | 482 | up(&devpriv->limit_sem); |
985cafcc MG |
483 | |
484 | return n; | |
3faad673 MG |
485 | } |
486 | ||
75a45d92 HS |
487 | static int vmk80xx_cnt_insn_config(struct comedi_device *dev, |
488 | struct comedi_subdevice *s, | |
489 | struct comedi_insn *insn, | |
490 | unsigned int *data) | |
3faad673 | 491 | { |
da7b18ee | 492 | struct vmk80xx_private *devpriv = dev->private; |
a4a75b21 | 493 | unsigned int chan = CR_CHAN(insn->chanspec); |
3a229fd5 AH |
494 | int cmd; |
495 | int reg; | |
a4a75b21 | 496 | int ret; |
3faad673 | 497 | |
da7b18ee | 498 | down(&devpriv->limit_sem); |
a4a75b21 HS |
499 | switch (data[0]) { |
500 | case INSN_CONFIG_RESET: | |
501 | if (devpriv->model == VMK8055_MODEL) { | |
502 | if (!chan) { | |
503 | cmd = VMK8055_CMD_RST_CNT1; | |
504 | reg = VMK8055_CNT1_REG; | |
505 | } else { | |
506 | cmd = VMK8055_CMD_RST_CNT2; | |
507 | reg = VMK8055_CNT2_REG; | |
508 | } | |
509 | devpriv->usb_tx_buf[reg] = 0x00; | |
985cafcc | 510 | } else { |
a4a75b21 | 511 | cmd = VMK8061_CMD_RST_CNT; |
985cafcc | 512 | } |
a4a75b21 HS |
513 | ret = vmk80xx_write_packet(dev, cmd); |
514 | break; | |
515 | default: | |
516 | ret = -EINVAL; | |
517 | break; | |
3a229fd5 | 518 | } |
da7b18ee | 519 | up(&devpriv->limit_sem); |
985cafcc | 520 | |
a4a75b21 | 521 | return ret ? ret : insn->n; |
985cafcc MG |
522 | } |
523 | ||
75a45d92 HS |
524 | static int vmk80xx_cnt_insn_write(struct comedi_device *dev, |
525 | struct comedi_subdevice *s, | |
526 | struct comedi_insn *insn, | |
527 | unsigned int *data) | |
985cafcc | 528 | { |
da7b18ee | 529 | struct vmk80xx_private *devpriv = dev->private; |
3a229fd5 AH |
530 | unsigned long debtime; |
531 | unsigned long val; | |
532 | int chan; | |
533 | int cmd; | |
985cafcc MG |
534 | int n; |
535 | ||
da7b18ee | 536 | down(&devpriv->limit_sem); |
985cafcc MG |
537 | chan = CR_CHAN(insn->chanspec); |
538 | ||
539 | if (!chan) | |
540 | cmd = VMK8055_CMD_DEB1_TIME; | |
541 | else | |
542 | cmd = VMK8055_CMD_DEB2_TIME; | |
543 | ||
544 | for (n = 0; n < insn->n; n++) { | |
545 | debtime = data[n]; | |
3faad673 MG |
546 | if (debtime == 0) |
547 | debtime = 1; | |
985cafcc MG |
548 | |
549 | /* TODO: Prevent overflows */ | |
550 | if (debtime > 7450) | |
551 | debtime = 7450; | |
552 | ||
3faad673 MG |
553 | val = int_sqrt(debtime * 1000 / 115); |
554 | if (((val + 1) * val) < debtime * 1000 / 115) | |
555 | val += 1; | |
556 | ||
da7b18ee | 557 | devpriv->usb_tx_buf[6 + chan] = val; |
3faad673 | 558 | |
0703c955 | 559 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc | 560 | break; |
3faad673 MG |
561 | } |
562 | ||
da7b18ee | 563 | up(&devpriv->limit_sem); |
3faad673 | 564 | |
985cafcc MG |
565 | return n; |
566 | } | |
3faad673 | 567 | |
9a23a748 HS |
568 | static int vmk80xx_pwm_insn_read(struct comedi_device *dev, |
569 | struct comedi_subdevice *s, | |
570 | struct comedi_insn *insn, | |
571 | unsigned int *data) | |
985cafcc | 572 | { |
da7b18ee HS |
573 | struct vmk80xx_private *devpriv = dev->private; |
574 | unsigned char *tx_buf; | |
575 | unsigned char *rx_buf; | |
985cafcc MG |
576 | int reg[2]; |
577 | int n; | |
578 | ||
da7b18ee HS |
579 | down(&devpriv->limit_sem); |
580 | ||
581 | tx_buf = devpriv->usb_tx_buf; | |
582 | rx_buf = devpriv->usb_rx_buf; | |
985cafcc MG |
583 | |
584 | reg[0] = VMK8061_PWM_REG1; | |
585 | reg[1] = VMK8061_PWM_REG2; | |
586 | ||
da7b18ee | 587 | tx_buf[0] = VMK8061_CMD_RD_PWM; |
985cafcc MG |
588 | |
589 | for (n = 0; n < insn->n; n++) { | |
0703c955 | 590 | if (vmk80xx_read_packet(dev)) |
985cafcc MG |
591 | break; |
592 | ||
da7b18ee | 593 | data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; |
985cafcc MG |
594 | } |
595 | ||
da7b18ee | 596 | up(&devpriv->limit_sem); |
985cafcc MG |
597 | |
598 | return n; | |
3faad673 MG |
599 | } |
600 | ||
9a23a748 HS |
601 | static int vmk80xx_pwm_insn_write(struct comedi_device *dev, |
602 | struct comedi_subdevice *s, | |
603 | struct comedi_insn *insn, | |
604 | unsigned int *data) | |
985cafcc | 605 | { |
da7b18ee | 606 | struct vmk80xx_private *devpriv = dev->private; |
985cafcc | 607 | unsigned char *tx_buf; |
3a229fd5 AH |
608 | int reg[2]; |
609 | int cmd; | |
985cafcc | 610 | int n; |
3faad673 | 611 | |
da7b18ee | 612 | down(&devpriv->limit_sem); |
985cafcc | 613 | |
da7b18ee | 614 | tx_buf = devpriv->usb_tx_buf; |
985cafcc MG |
615 | |
616 | reg[0] = VMK8061_PWM_REG1; | |
617 | reg[1] = VMK8061_PWM_REG2; | |
618 | ||
619 | cmd = VMK8061_CMD_OUT_PWM; | |
620 | ||
621 | /* | |
622 | * The followin piece of code was translated from the inline | |
623 | * assembler code in the DLL source code. | |
624 | * | |
625 | * asm | |
626 | * mov eax, k ; k is the value (data[n]) | |
627 | * and al, 03h ; al are the lower 8 bits of eax | |
628 | * mov lo, al ; lo is the low part (tx_buf[reg[0]]) | |
629 | * mov eax, k | |
630 | * shr eax, 2 ; right shift eax register by 2 | |
631 | * mov hi, al ; hi is the high part (tx_buf[reg[1]]) | |
632 | * end; | |
633 | */ | |
634 | for (n = 0; n < insn->n; n++) { | |
635 | tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); | |
636 | tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; | |
637 | ||
0703c955 | 638 | if (vmk80xx_write_packet(dev, cmd)) |
985cafcc MG |
639 | break; |
640 | } | |
3faad673 | 641 | |
da7b18ee | 642 | up(&devpriv->limit_sem); |
985cafcc MG |
643 | |
644 | return n; | |
645 | } | |
646 | ||
57cf09ae | 647 | static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) |
49253d54 | 648 | { |
57cf09ae | 649 | struct vmk80xx_private *devpriv = dev->private; |
e23322e4 | 650 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
49253d54 HS |
651 | struct usb_host_interface *iface_desc = intf->cur_altsetting; |
652 | struct usb_endpoint_descriptor *ep_desc; | |
653 | int i; | |
654 | ||
655 | if (iface_desc->desc.bNumEndpoints != 2) | |
656 | return -ENODEV; | |
657 | ||
658 | for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { | |
659 | ep_desc = &iface_desc->endpoint[i].desc; | |
660 | ||
661 | if (usb_endpoint_is_int_in(ep_desc) || | |
662 | usb_endpoint_is_bulk_in(ep_desc)) { | |
663 | if (!devpriv->ep_rx) | |
664 | devpriv->ep_rx = ep_desc; | |
665 | continue; | |
666 | } | |
667 | ||
668 | if (usb_endpoint_is_int_out(ep_desc) || | |
669 | usb_endpoint_is_bulk_out(ep_desc)) { | |
670 | if (!devpriv->ep_tx) | |
671 | devpriv->ep_tx = ep_desc; | |
672 | continue; | |
673 | } | |
674 | } | |
675 | ||
676 | if (!devpriv->ep_rx || !devpriv->ep_tx) | |
677 | return -ENODEV; | |
678 | ||
679 | return 0; | |
680 | } | |
681 | ||
57cf09ae | 682 | static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) |
78f8fa7f | 683 | { |
57cf09ae | 684 | struct vmk80xx_private *devpriv = dev->private; |
78f8fa7f HS |
685 | size_t size; |
686 | ||
62190d49 | 687 | size = usb_endpoint_maxp(devpriv->ep_rx); |
0cbfc826 | 688 | devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
689 | if (!devpriv->usb_rx_buf) |
690 | return -ENOMEM; | |
691 | ||
62190d49 | 692 | size = usb_endpoint_maxp(devpriv->ep_tx); |
0cbfc826 | 693 | devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); |
78f8fa7f HS |
694 | if (!devpriv->usb_tx_buf) { |
695 | kfree(devpriv->usb_rx_buf); | |
696 | return -ENOMEM; | |
697 | } | |
698 | ||
699 | return 0; | |
700 | } | |
701 | ||
66dbc7b1 | 702 | static int vmk80xx_init_subdevices(struct comedi_device *dev) |
3faad673 | 703 | { |
adda9ab0 | 704 | const struct vmk80xx_board *board = dev->board_ptr; |
57cf09ae | 705 | struct vmk80xx_private *devpriv = dev->private; |
b153d83e | 706 | struct comedi_subdevice *s; |
57cf09ae | 707 | int n_subd; |
8b6c5694 | 708 | int ret; |
3faad673 | 709 | |
da7b18ee | 710 | down(&devpriv->limit_sem); |
0dd772bf | 711 | |
52d895d3 | 712 | if (devpriv->model == VMK8055_MODEL) |
985cafcc MG |
713 | n_subd = 5; |
714 | else | |
715 | n_subd = 6; | |
da7b18ee | 716 | ret = comedi_alloc_subdevices(dev, n_subd); |
8b6c5694 | 717 | if (ret) { |
da7b18ee | 718 | up(&devpriv->limit_sem); |
8b6c5694 | 719 | return ret; |
3faad673 | 720 | } |
0dd772bf | 721 | |
985cafcc | 722 | /* Analog input subdevice */ |
da7b18ee | 723 | s = &dev->subdevices[0]; |
658cd3ac HS |
724 | s->type = COMEDI_SUBD_AI; |
725 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
adda9ab0 HS |
726 | s->n_chan = board->ai_nchans; |
727 | s->maxdata = board->ai_maxdata; | |
728 | s->range_table = board->range; | |
658cd3ac | 729 | s->insn_read = vmk80xx_ai_insn_read; |
0dd772bf | 730 | |
985cafcc | 731 | /* Analog output subdevice */ |
da7b18ee | 732 | s = &dev->subdevices[1]; |
8b3ec9f1 | 733 | s->type = COMEDI_SUBD_AO; |
ef49d832 | 734 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
adda9ab0 | 735 | s->n_chan = board->ao_nchans; |
8b3ec9f1 | 736 | s->maxdata = 0x00ff; |
adda9ab0 | 737 | s->range_table = board->range; |
8b3ec9f1 | 738 | s->insn_write = vmk80xx_ao_insn_write; |
52d895d3 | 739 | if (devpriv->model == VMK8061_MODEL) { |
8b3ec9f1 HS |
740 | s->subdev_flags |= SDF_READABLE; |
741 | s->insn_read = vmk80xx_ao_insn_read; | |
985cafcc | 742 | } |
0dd772bf | 743 | |
985cafcc | 744 | /* Digital input subdevice */ |
da7b18ee | 745 | s = &dev->subdevices[2]; |
268e5148 HS |
746 | s->type = COMEDI_SUBD_DI; |
747 | s->subdev_flags = SDF_READABLE; | |
adda9ab0 | 748 | s->n_chan = board->di_nchans; |
268e5148 HS |
749 | s->maxdata = 1; |
750 | s->range_table = &range_digital; | |
268e5148 | 751 | s->insn_bits = vmk80xx_di_insn_bits; |
0dd772bf | 752 | |
985cafcc | 753 | /* Digital output subdevice */ |
da7b18ee | 754 | s = &dev->subdevices[3]; |
b639e096 | 755 | s->type = COMEDI_SUBD_DO; |
ef49d832 | 756 | s->subdev_flags = SDF_WRITABLE; |
b639e096 HS |
757 | s->n_chan = 8; |
758 | s->maxdata = 1; | |
759 | s->range_table = &range_digital; | |
b639e096 | 760 | s->insn_bits = vmk80xx_do_insn_bits; |
0dd772bf | 761 | |
985cafcc | 762 | /* Counter subdevice */ |
da7b18ee | 763 | s = &dev->subdevices[4]; |
75a45d92 HS |
764 | s->type = COMEDI_SUBD_COUNTER; |
765 | s->subdev_flags = SDF_READABLE; | |
766 | s->n_chan = 2; | |
adda9ab0 | 767 | s->maxdata = board->cnt_maxdata; |
75a45d92 HS |
768 | s->insn_read = vmk80xx_cnt_insn_read; |
769 | s->insn_config = vmk80xx_cnt_insn_config; | |
52d895d3 | 770 | if (devpriv->model == VMK8055_MODEL) { |
ef49d832 | 771 | s->subdev_flags |= SDF_WRITABLE; |
75a45d92 | 772 | s->insn_write = vmk80xx_cnt_insn_write; |
985cafcc | 773 | } |
0dd772bf | 774 | |
985cafcc | 775 | /* PWM subdevice */ |
52d895d3 | 776 | if (devpriv->model == VMK8061_MODEL) { |
da7b18ee | 777 | s = &dev->subdevices[5]; |
9a23a748 | 778 | s->type = COMEDI_SUBD_PWM; |
ef49d832 | 779 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
adda9ab0 HS |
780 | s->n_chan = board->pwm_nchans; |
781 | s->maxdata = board->pwm_maxdata; | |
9a23a748 HS |
782 | s->insn_read = vmk80xx_pwm_insn_read; |
783 | s->insn_write = vmk80xx_pwm_insn_write; | |
985cafcc | 784 | } |
0dd772bf | 785 | |
da7b18ee | 786 | up(&devpriv->limit_sem); |
0dd772bf | 787 | |
f7d4d3bc IA |
788 | return 0; |
789 | } | |
3faad673 | 790 | |
da7b18ee | 791 | static int vmk80xx_auto_attach(struct comedi_device *dev, |
57cf09ae | 792 | unsigned long context) |
f7d4d3bc | 793 | { |
da7b18ee | 794 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
adda9ab0 | 795 | const struct vmk80xx_board *board = NULL; |
da7b18ee | 796 | struct vmk80xx_private *devpriv; |
49253d54 | 797 | int ret; |
3faad673 | 798 | |
e38576ce HS |
799 | if (context < ARRAY_SIZE(vmk80xx_boardinfo)) |
800 | board = &vmk80xx_boardinfo[context]; | |
801 | if (!board) | |
802 | return -ENODEV; | |
adda9ab0 HS |
803 | dev->board_ptr = board; |
804 | dev->board_name = board->name; | |
3faad673 | 805 | |
0bdab509 | 806 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
57cf09ae HS |
807 | if (!devpriv) |
808 | return -ENOMEM; | |
3faad673 | 809 | |
adda9ab0 | 810 | devpriv->model = board->model; |
985cafcc | 811 | |
57cf09ae | 812 | ret = vmk80xx_find_usb_endpoints(dev); |
db7dabf7 | 813 | if (ret) |
57cf09ae | 814 | return ret; |
3faad673 | 815 | |
57cf09ae | 816 | ret = vmk80xx_alloc_usb_buffers(dev); |
db7dabf7 | 817 | if (ret) |
57cf09ae | 818 | return ret; |
985cafcc | 819 | |
da7b18ee | 820 | sema_init(&devpriv->limit_sem, 8); |
985cafcc | 821 | |
da7b18ee | 822 | usb_set_intfdata(intf, devpriv); |
985cafcc | 823 | |
52d895d3 | 824 | if (devpriv->model == VMK8055_MODEL) |
0703c955 | 825 | vmk80xx_reset_device(dev); |
3faad673 | 826 | |
66dbc7b1 | 827 | return vmk80xx_init_subdevices(dev); |
57cf09ae | 828 | } |
3faad673 | 829 | |
57cf09ae HS |
830 | static void vmk80xx_detach(struct comedi_device *dev) |
831 | { | |
e23322e4 | 832 | struct usb_interface *intf = comedi_to_usb_interface(dev); |
57cf09ae | 833 | struct vmk80xx_private *devpriv = dev->private; |
8ba69ce4 | 834 | |
57cf09ae HS |
835 | if (!devpriv) |
836 | return; | |
db7dabf7 | 837 | |
57cf09ae HS |
838 | down(&devpriv->limit_sem); |
839 | ||
e23322e4 | 840 | usb_set_intfdata(intf, NULL); |
57cf09ae | 841 | |
57cf09ae HS |
842 | kfree(devpriv->usb_rx_buf); |
843 | kfree(devpriv->usb_tx_buf); | |
844 | ||
845 | up(&devpriv->limit_sem); | |
846 | } | |
847 | ||
848 | static struct comedi_driver vmk80xx_driver = { | |
849 | .module = THIS_MODULE, | |
850 | .driver_name = "vmk80xx", | |
851 | .auto_attach = vmk80xx_auto_attach, | |
852 | .detach = vmk80xx_detach, | |
853 | }; | |
854 | ||
855 | static int vmk80xx_usb_probe(struct usb_interface *intf, | |
856 | const struct usb_device_id *id) | |
857 | { | |
858 | return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info); | |
3faad673 MG |
859 | } |
860 | ||
007ff2af HS |
861 | static const struct usb_device_id vmk80xx_usb_id_table[] = { |
862 | { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 }, | |
863 | { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 }, | |
864 | { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 }, | |
865 | { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 }, | |
866 | { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 }, | |
867 | { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 }, | |
868 | { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 }, | |
869 | { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 }, | |
870 | { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 }, | |
871 | { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 }, | |
872 | { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 }, | |
873 | { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 }, | |
874 | { } | |
875 | }; | |
876 | MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table); | |
877 | ||
d6cc3ec8 HS |
878 | static struct usb_driver vmk80xx_usb_driver = { |
879 | .name = "vmk80xx", | |
007ff2af | 880 | .id_table = vmk80xx_usb_id_table, |
ce874227 HS |
881 | .probe = vmk80xx_usb_probe, |
882 | .disconnect = comedi_usb_auto_unconfig, | |
3faad673 | 883 | }; |
d6cc3ec8 | 884 | module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); |
007ff2af HS |
885 | |
886 | MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); | |
887 | MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); | |
888 | MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); | |
007ff2af | 889 | MODULE_LICENSE("GPL"); |