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