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