Commit | Line | Data |
---|---|---|
f47c697d | 1 | /* |
4e8ad0dc MK |
2 | * Copyright (C) 2004 Bernd Porr, Bernd.Porr@f2s.com |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
17 | */ | |
f47c697d BP |
18 | |
19 | /* | |
20 | * I must give credit here to Chris Baugher who | |
21 | * wrote the driver for AT-MIO-16d. I used some parts of this | |
22 | * driver. I also must give credits to David Brownell | |
23 | * who supported me with the USB development. | |
24 | * | |
25 | * Bernd Porr | |
26 | * | |
27 | * | |
28 | * Revision history: | |
29 | * 0.9: Dropping the first data packet which seems to be from the last transfer. | |
30 | * Buffer overflows in the FX2 are handed over to comedi. | |
31 | * 0.92: Dropping now 4 packets. The quad buffer has to be emptied. | |
4e8ad0dc MK |
32 | * Added insn command basically for testing. Sample rate is |
33 | * 1MHz/16ch=62.5kHz | |
f47c697d BP |
34 | * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks! |
35 | * 0.99a: added external trigger. | |
6742c0af BP |
36 | * 1.00: added firmware kernel request to the driver which fixed |
37 | * udev coldplug problem | |
f47c697d BP |
38 | */ |
39 | ||
40 | #include <linux/kernel.h> | |
6742c0af | 41 | #include <linux/firmware.h> |
f47c697d BP |
42 | #include <linux/module.h> |
43 | #include <linux/init.h> | |
44 | #include <linux/slab.h> | |
45 | #include <linux/input.h> | |
46 | #include <linux/usb.h> | |
f47c697d BP |
47 | #include <linux/fcntl.h> |
48 | #include <linux/compiler.h> | |
49 | #include "comedi_fc.h" | |
50 | #include "../comedidev.h" | |
f47c697d | 51 | |
6742c0af | 52 | #define DRIVER_VERSION "v1.0" |
4e8ad0dc MK |
53 | #define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" |
54 | #define DRIVER_DESC "USB-DUXfast, BerndPorr@f2s.com" | |
f47c697d BP |
55 | #define BOARDNAME "usbduxfast" |
56 | ||
4e8ad0dc MK |
57 | /* |
58 | * timeout for the USB-transfer | |
59 | */ | |
60 | #define EZTIMEOUT 30 | |
f47c697d | 61 | |
4e8ad0dc MK |
62 | /* |
63 | * constants for "firmware" upload and download | |
64 | */ | |
65 | #define USBDUXFASTSUB_FIRMWARE 0xA0 | |
66 | #define VENDOR_DIR_IN 0xC0 | |
67 | #define VENDOR_DIR_OUT 0x40 | |
f47c697d | 68 | |
4e8ad0dc | 69 | /* |
f69b0d64 | 70 | * internal addresses of the 8051 processor |
4e8ad0dc MK |
71 | */ |
72 | #define USBDUXFASTSUB_CPUCS 0xE600 | |
f47c697d | 73 | |
4e8ad0dc MK |
74 | /* |
75 | * max lenghth of the transfer-buffer for software upload | |
76 | */ | |
77 | #define TB_LEN 0x2000 | |
f47c697d | 78 | |
4e8ad0dc MK |
79 | /* |
80 | * input endpoint number | |
81 | */ | |
82 | #define BULKINEP 6 | |
f47c697d | 83 | |
4e8ad0dc MK |
84 | /* |
85 | * endpoint for the A/D channellist: bulk OUT | |
86 | */ | |
87 | #define CHANNELLISTEP 4 | |
f47c697d | 88 | |
4e8ad0dc MK |
89 | /* |
90 | * number of channels | |
91 | */ | |
92 | #define NUMCHANNELS 32 | |
f47c697d | 93 | |
4e8ad0dc MK |
94 | /* |
95 | * size of the waveform descriptor | |
96 | */ | |
97 | #define WAVESIZE 0x20 | |
f47c697d | 98 | |
4e8ad0dc MK |
99 | /* |
100 | * size of one A/D value | |
101 | */ | |
102 | #define SIZEADIN (sizeof(int16_t)) | |
f47c697d | 103 | |
4e8ad0dc MK |
104 | /* |
105 | * size of the input-buffer IN BYTES | |
106 | */ | |
107 | #define SIZEINBUF 512 | |
f47c697d | 108 | |
4e8ad0dc MK |
109 | /* |
110 | * 16 bytes | |
111 | */ | |
112 | #define SIZEINSNBUF 512 | |
f47c697d | 113 | |
4e8ad0dc MK |
114 | /* |
115 | * size of the buffer for the dux commands in bytes | |
116 | */ | |
117 | #define SIZEOFDUXBUFFER 256 | |
f47c697d | 118 | |
4e8ad0dc MK |
119 | /* |
120 | * number of in-URBs which receive the data: min=5 | |
121 | */ | |
122 | #define NUMOFINBUFFERSHIGH 10 | |
f47c697d | 123 | |
4e8ad0dc MK |
124 | /* |
125 | * total number of usbduxfast devices | |
126 | */ | |
127 | #define NUMUSBDUXFAST 16 | |
f47c697d | 128 | |
4e8ad0dc MK |
129 | /* |
130 | * number of subdevices | |
131 | */ | |
132 | #define N_SUBDEVICES 1 | |
f47c697d | 133 | |
4e8ad0dc MK |
134 | /* |
135 | * analogue in subdevice | |
136 | */ | |
137 | #define SUBDEV_AD 0 | |
f47c697d | 138 | |
4e8ad0dc MK |
139 | /* |
140 | * min delay steps for more than one channel | |
141 | * basically when the mux gives up ;-) | |
142 | * | |
143 | * steps at 30MHz in the FX2 | |
144 | */ | |
145 | #define MIN_SAMPLING_PERIOD 9 | |
f47c697d | 146 | |
4e8ad0dc MK |
147 | /* |
148 | * max number of 1/30MHz delay steps | |
149 | */ | |
150 | #define MAX_SAMPLING_PERIOD 500 | |
f47c697d | 151 | |
4e8ad0dc MK |
152 | /* |
153 | * number of received packets to ignore before we start handing data | |
154 | * over to comedi, it's quad buffering and we have to ignore 4 packets | |
155 | */ | |
156 | #define PACKETS_TO_IGNORE 4 | |
f47c697d | 157 | |
4e8ad0dc MK |
158 | /* |
159 | * comedi constants | |
160 | */ | |
9ced1de6 | 161 | static const struct comedi_lrange range_usbduxfast_ai_range = { |
0a85b6f0 | 162 | 2, {BIP_RANGE(0.75), BIP_RANGE(0.5)} |
f47c697d BP |
163 | }; |
164 | ||
165 | /* | |
166 | * private structure of one subdevice | |
4e8ad0dc MK |
167 | * |
168 | * this is the structure which holds all the data of this driver | |
169 | * one sub device just now: A/D | |
f47c697d | 170 | */ |
4e8ad0dc | 171 | struct usbduxfastsub_s { |
0a85b6f0 MT |
172 | int attached; /* is attached? */ |
173 | int probed; /* is it associated with a subdevice? */ | |
4e8ad0dc | 174 | struct usb_device *usbdev; /* pointer to the usb-device */ |
0a85b6f0 | 175 | struct urb *urbIn; /* BULK-transfer handling: urb */ |
f47c697d | 176 | int8_t *transfer_buffer; |
0a85b6f0 MT |
177 | int16_t *insnBuffer; /* input buffer for single insn */ |
178 | int ifnum; /* interface number */ | |
4e8ad0dc | 179 | struct usb_interface *interface; /* interface structure */ |
de55a7a5 SR |
180 | /* comedi device for the interrupt context */ |
181 | struct comedi_device *comedidev; | |
4e8ad0dc | 182 | short int ai_cmd_running; /* asynchronous command is running */ |
25985edc | 183 | short int ai_continous; /* continous acquisition */ |
9d220c6b | 184 | long int ai_sample_count; /* number of samples to acquire */ |
0a85b6f0 MT |
185 | uint8_t *dux_commands; /* commands */ |
186 | int ignore; /* counter which ignores the first | |
187 | buffers */ | |
f47c697d | 188 | struct semaphore sem; |
4e8ad0dc | 189 | }; |
f47c697d | 190 | |
4e8ad0dc MK |
191 | /* |
192 | * The pointer to the private usb-data of the driver | |
193 | * is also the private data for the comedi-device. | |
194 | * This has to be global as the usb subsystem needs | |
195 | * global variables. The other reason is that this | |
196 | * structure must be there _before_ any comedi | |
197 | * command is issued. The usb subsystem must be | |
198 | * initialised before comedi can access it. | |
199 | */ | |
200 | static struct usbduxfastsub_s usbduxfastsub[NUMUSBDUXFAST]; | |
f47c697d | 201 | |
45f4d024 | 202 | static DEFINE_SEMAPHORE(start_stop_sem); |
f47c697d | 203 | |
4e8ad0dc MK |
204 | /* |
205 | * bulk transfers to usbduxfast | |
206 | */ | |
f47c697d BP |
207 | #define SENDADCOMMANDS 0 |
208 | #define SENDINITEP6 1 | |
209 | ||
4e8ad0dc | 210 | static int send_dux_commands(struct usbduxfastsub_s *udfs, int cmd_type) |
f47c697d | 211 | { |
0a3b8b64 MK |
212 | int tmp, nsent; |
213 | ||
4e8ad0dc | 214 | udfs->dux_commands[0] = cmd_type; |
0a3b8b64 | 215 | |
f47c697d | 216 | #ifdef CONFIG_COMEDI_DEBUG |
0a3b8b64 | 217 | printk(KERN_DEBUG "comedi%d: usbduxfast: dux_commands: ", |
0a85b6f0 | 218 | udfs->comedidev->minor); |
0a3b8b64 | 219 | for (tmp = 0; tmp < SIZEOFDUXBUFFER; tmp++) |
4e8ad0dc | 220 | printk(" %02x", udfs->dux_commands[tmp]); |
f47c697d BP |
221 | printk("\n"); |
222 | #endif | |
0a3b8b64 | 223 | |
4e8ad0dc MK |
224 | tmp = usb_bulk_msg(udfs->usbdev, |
225 | usb_sndbulkpipe(udfs->usbdev, CHANNELLISTEP), | |
226 | udfs->dux_commands, SIZEOFDUXBUFFER, &nsent, 10000); | |
0a3b8b64 MK |
227 | if (tmp < 0) |
228 | printk(KERN_ERR "comedi%d: could not transmit dux_commands to" | |
0a85b6f0 | 229 | "the usb-device, err=%d\n", udfs->comedidev->minor, tmp); |
0a3b8b64 | 230 | return tmp; |
f47c697d BP |
231 | } |
232 | ||
4e8ad0dc MK |
233 | /* |
234 | * Stops the data acquision. | |
235 | * It should be safe to call this function from any context. | |
236 | */ | |
237 | static int usbduxfastsub_unlink_InURBs(struct usbduxfastsub_s *udfs) | |
f47c697d BP |
238 | { |
239 | int j = 0; | |
240 | int err = 0; | |
241 | ||
4e8ad0dc MK |
242 | if (udfs && udfs->urbIn) { |
243 | udfs->ai_cmd_running = 0; | |
244 | /* waits until a running transfer is over */ | |
245 | usb_kill_urb(udfs->urbIn); | |
f47c697d | 246 | j = 0; |
f47c697d BP |
247 | } |
248 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc | 249 | printk(KERN_DEBUG "comedi: usbduxfast: unlinked InURB: res=%d\n", j); |
f47c697d BP |
250 | #endif |
251 | return err; | |
252 | } | |
253 | ||
4e8ad0dc MK |
254 | /* |
255 | * This will stop a running acquisition operation. | |
256 | * Is called from within this driver from both the | |
257 | * interrupt context and from comedi. | |
258 | */ | |
0a85b6f0 | 259 | static int usbduxfast_ai_stop(struct usbduxfastsub_s *udfs, int do_unlink) |
f47c697d BP |
260 | { |
261 | int ret = 0; | |
262 | ||
4e8ad0dc MK |
263 | if (!udfs) { |
264 | printk(KERN_ERR "comedi?: usbduxfast_ai_stop: udfs=NULL!\n"); | |
f47c697d BP |
265 | return -EFAULT; |
266 | } | |
267 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc | 268 | printk(KERN_DEBUG "comedi: usbduxfast_ai_stop\n"); |
f47c697d BP |
269 | #endif |
270 | ||
4e8ad0dc | 271 | udfs->ai_cmd_running = 0; |
f47c697d | 272 | |
4e8ad0dc | 273 | if (do_unlink) |
de55a7a5 SR |
274 | /* stop aquistion */ |
275 | ret = usbduxfastsub_unlink_InURBs(udfs); | |
f47c697d BP |
276 | |
277 | return ret; | |
278 | } | |
279 | ||
4e8ad0dc MK |
280 | /* |
281 | * This will cancel a running acquisition operation. | |
282 | * This is called by comedi but never from inside the driver. | |
283 | */ | |
0a85b6f0 MT |
284 | static int usbduxfast_ai_cancel(struct comedi_device *dev, |
285 | struct comedi_subdevice *s) | |
f47c697d | 286 | { |
4e8ad0dc MK |
287 | struct usbduxfastsub_s *udfs; |
288 | int ret; | |
f47c697d | 289 | |
4e8ad0dc | 290 | /* force unlink of all urbs */ |
f47c697d | 291 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc | 292 | printk(KERN_DEBUG "comedi: usbduxfast_ai_cancel\n"); |
f47c697d | 293 | #endif |
4e8ad0dc MK |
294 | udfs = dev->private; |
295 | if (!udfs) { | |
296 | printk(KERN_ERR "comedi: usbduxfast_ai_cancel: udfs=NULL\n"); | |
f47c697d BP |
297 | return -EFAULT; |
298 | } | |
4e8ad0dc MK |
299 | down(&udfs->sem); |
300 | if (!udfs->probed) { | |
301 | up(&udfs->sem); | |
f47c697d BP |
302 | return -ENODEV; |
303 | } | |
4e8ad0dc MK |
304 | /* unlink */ |
305 | ret = usbduxfast_ai_stop(udfs, 1); | |
306 | up(&udfs->sem); | |
f47c697d | 307 | |
4e8ad0dc | 308 | return ret; |
f47c697d BP |
309 | } |
310 | ||
4e8ad0dc MK |
311 | /* |
312 | * analogue IN | |
313 | * interrupt service routine | |
314 | */ | |
70265d24 | 315 | static void usbduxfastsub_ai_Irq(struct urb *urb) |
f47c697d BP |
316 | { |
317 | int n, err; | |
4e8ad0dc | 318 | struct usbduxfastsub_s *udfs; |
71b5f4f1 | 319 | struct comedi_device *this_comedidev; |
34c43922 | 320 | struct comedi_subdevice *s; |
f47c697d BP |
321 | uint16_t *p; |
322 | ||
4e8ad0dc | 323 | /* sanity checks - is the urb there? */ |
f47c697d | 324 | if (!urb) { |
4e8ad0dc MK |
325 | printk(KERN_ERR "comedi_: usbduxfast_: ao int-handler called " |
326 | "with urb=NULL!\n"); | |
f47c697d BP |
327 | return; |
328 | } | |
4e8ad0dc | 329 | /* the context variable points to the subdevice */ |
f47c697d BP |
330 | this_comedidev = urb->context; |
331 | if (!this_comedidev) { | |
4e8ad0dc MK |
332 | printk(KERN_ERR "comedi_: usbduxfast_: urb context is a NULL " |
333 | "pointer!\n"); | |
f47c697d BP |
334 | return; |
335 | } | |
4e8ad0dc MK |
336 | /* the private structure of the subdevice is usbduxfastsub_s */ |
337 | udfs = this_comedidev->private; | |
338 | if (!udfs) { | |
339 | printk(KERN_ERR "comedi_: usbduxfast_: private of comedi " | |
340 | "subdev is a NULL pointer!\n"); | |
f47c697d BP |
341 | return; |
342 | } | |
4e8ad0dc MK |
343 | /* are we running a command? */ |
344 | if (unlikely(!udfs->ai_cmd_running)) { | |
345 | /* | |
346 | * not running a command | |
347 | * do not continue execution if no asynchronous command | |
348 | * is running in particular not resubmit | |
349 | */ | |
f47c697d BP |
350 | return; |
351 | } | |
352 | ||
4e8ad0dc MK |
353 | if (unlikely(!udfs->attached)) { |
354 | /* no comedi device there */ | |
f47c697d BP |
355 | return; |
356 | } | |
4e8ad0dc | 357 | /* subdevice which is the AD converter */ |
f47c697d BP |
358 | s = this_comedidev->subdevices + SUBDEV_AD; |
359 | ||
4e8ad0dc | 360 | /* first we test if something unusual has just happened */ |
f47c697d BP |
361 | switch (urb->status) { |
362 | case 0: | |
363 | break; | |
364 | ||
4e8ad0dc MK |
365 | /* |
366 | * happens after an unlink command or when the device | |
367 | * is plugged out | |
368 | */ | |
f47c697d BP |
369 | case -ECONNRESET: |
370 | case -ENOENT: | |
371 | case -ESHUTDOWN: | |
372 | case -ECONNABORTED: | |
4e8ad0dc | 373 | /* tell this comedi */ |
f47c697d BP |
374 | s->async->events |= COMEDI_CB_EOA; |
375 | s->async->events |= COMEDI_CB_ERROR; | |
4e8ad0dc MK |
376 | comedi_event(udfs->comedidev, s); |
377 | /* stop the transfer w/o unlink */ | |
378 | usbduxfast_ai_stop(udfs, 0); | |
f47c697d BP |
379 | return; |
380 | ||
381 | default: | |
4e8ad0dc MK |
382 | printk("comedi%d: usbduxfast: non-zero urb status received in " |
383 | "ai intr context: %d\n", | |
384 | udfs->comedidev->minor, urb->status); | |
f47c697d BP |
385 | s->async->events |= COMEDI_CB_EOA; |
386 | s->async->events |= COMEDI_CB_ERROR; | |
4e8ad0dc MK |
387 | comedi_event(udfs->comedidev, s); |
388 | usbduxfast_ai_stop(udfs, 0); | |
f47c697d BP |
389 | return; |
390 | } | |
391 | ||
392 | p = urb->transfer_buffer; | |
4e8ad0dc MK |
393 | if (!udfs->ignore) { |
394 | if (!udfs->ai_continous) { | |
25985edc | 395 | /* not continuous, fixed number of samples */ |
f47c697d | 396 | n = urb->actual_length / sizeof(uint16_t); |
4e8ad0dc MK |
397 | if (unlikely(udfs->ai_sample_count < n)) { |
398 | /* | |
399 | * we have send only a fraction of the bytes | |
400 | * received | |
401 | */ | |
f47c697d | 402 | cfc_write_array_to_buffer(s, |
0a85b6f0 MT |
403 | urb->transfer_buffer, |
404 | udfs->ai_sample_count | |
405 | * sizeof(uint16_t)); | |
4e8ad0dc | 406 | usbduxfast_ai_stop(udfs, 0); |
efe8d60a | 407 | /* tell comedi that the acquistion is over */ |
f47c697d | 408 | s->async->events |= COMEDI_CB_EOA; |
4e8ad0dc | 409 | comedi_event(udfs->comedidev, s); |
f47c697d BP |
410 | return; |
411 | } | |
4e8ad0dc | 412 | udfs->ai_sample_count -= n; |
f47c697d | 413 | } |
4e8ad0dc | 414 | /* write the full buffer to comedi */ |
efe8d60a BP |
415 | err = cfc_write_array_to_buffer(s, urb->transfer_buffer, |
416 | urb->actual_length); | |
417 | if (unlikely(err == 0)) { | |
418 | /* buffer overflow */ | |
419 | usbduxfast_ai_stop(udfs, 0); | |
420 | return; | |
421 | } | |
f47c697d | 422 | |
4e8ad0dc MK |
423 | /* tell comedi that data is there */ |
424 | comedi_event(udfs->comedidev, s); | |
f47c697d BP |
425 | |
426 | } else { | |
4e8ad0dc MK |
427 | /* ignore this packet */ |
428 | udfs->ignore--; | |
f47c697d BP |
429 | } |
430 | ||
4e8ad0dc MK |
431 | /* |
432 | * command is still running | |
433 | * resubmit urb for BULK transfer | |
434 | */ | |
435 | urb->dev = udfs->usbdev; | |
f47c697d | 436 | urb->status = 0; |
88676359 GKH |
437 | err = usb_submit_urb(urb, GFP_ATOMIC); |
438 | if (err < 0) { | |
4e8ad0dc | 439 | printk(KERN_ERR "comedi%d: usbduxfast: urb resubm failed: %d", |
0a85b6f0 | 440 | udfs->comedidev->minor, err); |
f47c697d BP |
441 | s->async->events |= COMEDI_CB_EOA; |
442 | s->async->events |= COMEDI_CB_ERROR; | |
4e8ad0dc MK |
443 | comedi_event(udfs->comedidev, s); |
444 | usbduxfast_ai_stop(udfs, 0); | |
f47c697d BP |
445 | } |
446 | } | |
447 | ||
4e8ad0dc | 448 | static int usbduxfastsub_start(struct usbduxfastsub_s *udfs) |
f47c697d | 449 | { |
4e8ad0dc | 450 | int ret; |
f47c697d BP |
451 | unsigned char local_transfer_buffer[16]; |
452 | ||
4e8ad0dc MK |
453 | /* 7f92 to zero */ |
454 | local_transfer_buffer[0] = 0; | |
de55a7a5 SR |
455 | /* bRequest, "Firmware" */ |
456 | ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, | |
457 | VENDOR_DIR_OUT, /* bmRequestType */ | |
458 | USBDUXFASTSUB_CPUCS, /* Value */ | |
459 | 0x0000, /* Index */ | |
460 | /* address of the transfer buffer */ | |
461 | local_transfer_buffer, | |
462 | 1, /* Length */ | |
463 | EZTIMEOUT); /* Timeout */ | |
4e8ad0dc MK |
464 | if (ret < 0) { |
465 | printk("comedi_: usbduxfast_: control msg failed (start)\n"); | |
466 | return ret; | |
f47c697d | 467 | } |
4e8ad0dc | 468 | |
f47c697d BP |
469 | return 0; |
470 | } | |
471 | ||
4e8ad0dc | 472 | static int usbduxfastsub_stop(struct usbduxfastsub_s *udfs) |
f47c697d | 473 | { |
4e8ad0dc | 474 | int ret; |
f47c697d | 475 | unsigned char local_transfer_buffer[16]; |
4e8ad0dc | 476 | |
4e8ad0dc MK |
477 | /* 7f92 to one */ |
478 | local_transfer_buffer[0] = 1; | |
de55a7a5 SR |
479 | /* bRequest, "Firmware" */ |
480 | ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, | |
0a85b6f0 MT |
481 | VENDOR_DIR_OUT, /* bmRequestType */ |
482 | USBDUXFASTSUB_CPUCS, /* Value */ | |
483 | 0x0000, /* Index */ | |
484 | local_transfer_buffer, 1, /* Length */ | |
485 | EZTIMEOUT); /* Timeout */ | |
4e8ad0dc MK |
486 | if (ret < 0) { |
487 | printk(KERN_ERR "comedi_: usbduxfast: control msg failed " | |
488 | "(stop)\n"); | |
489 | return ret; | |
f47c697d | 490 | } |
4e8ad0dc | 491 | |
f47c697d BP |
492 | return 0; |
493 | } | |
494 | ||
4e8ad0dc | 495 | static int usbduxfastsub_upload(struct usbduxfastsub_s *udfs, |
0a85b6f0 MT |
496 | unsigned char *local_transfer_buffer, |
497 | unsigned int startAddr, unsigned int len) | |
f47c697d | 498 | { |
4e8ad0dc MK |
499 | int ret; |
500 | ||
f47c697d | 501 | #ifdef CONFIG_COMEDI_DEBUG |
d52a63bf | 502 | printk(KERN_DEBUG "comedi: usbduxfast: uploading %d bytes", len); |
4e8ad0dc | 503 | printk(KERN_DEBUG " to addr %d, first byte=%d.\n", |
0a85b6f0 | 504 | startAddr, local_transfer_buffer[0]); |
f47c697d | 505 | #endif |
de55a7a5 SR |
506 | /* brequest, firmware */ |
507 | ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, | |
508 | VENDOR_DIR_OUT, /* bmRequestType */ | |
509 | startAddr, /* value */ | |
510 | 0x0000, /* index */ | |
511 | /* our local safe buffer */ | |
512 | local_transfer_buffer, | |
513 | len, /* length */ | |
514 | EZTIMEOUT); /* timeout */ | |
4e8ad0dc | 515 | |
f47c697d | 516 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc | 517 | printk(KERN_DEBUG "comedi_: usbduxfast: result=%d\n", ret); |
f47c697d | 518 | #endif |
4e8ad0dc MK |
519 | |
520 | if (ret < 0) { | |
521 | printk(KERN_ERR "comedi_: usbduxfast: uppload failed\n"); | |
522 | return ret; | |
f47c697d | 523 | } |
4e8ad0dc | 524 | |
f47c697d BP |
525 | return 0; |
526 | } | |
527 | ||
643a5420 | 528 | static int usbduxfastsub_submit_InURBs(struct usbduxfastsub_s *udfs) |
f47c697d | 529 | { |
4e8ad0dc | 530 | int ret; |
f47c697d | 531 | |
4e8ad0dc | 532 | if (!udfs) |
f47c697d | 533 | return -EFAULT; |
4e8ad0dc MK |
534 | |
535 | usb_fill_bulk_urb(udfs->urbIn, udfs->usbdev, | |
536 | usb_rcvbulkpipe(udfs->usbdev, BULKINEP), | |
537 | udfs->transfer_buffer, | |
538 | SIZEINBUF, usbduxfastsub_ai_Irq, udfs->comedidev); | |
f47c697d BP |
539 | |
540 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
541 | printk(KERN_DEBUG "comedi%d: usbduxfast: submitting in-urb: " |
542 | "0x%p,0x%p\n", udfs->comedidev->minor, udfs->urbIn->context, | |
0a85b6f0 | 543 | udfs->urbIn->dev); |
f47c697d | 544 | #endif |
4e8ad0dc MK |
545 | ret = usb_submit_urb(udfs->urbIn, GFP_ATOMIC); |
546 | if (ret) { | |
547 | printk(KERN_ERR "comedi_: usbduxfast: ai: usb_submit_urb error" | |
548 | " %d\n", ret); | |
549 | return ret; | |
f47c697d BP |
550 | } |
551 | return 0; | |
552 | } | |
553 | ||
71b5f4f1 | 554 | static int usbduxfast_ai_cmdtest(struct comedi_device *dev, |
0a85b6f0 MT |
555 | struct comedi_subdevice *s, |
556 | struct comedi_cmd *cmd) | |
f47c697d BP |
557 | { |
558 | int err = 0, stop_mask = 0; | |
4e8ad0dc | 559 | long int steps, tmp; |
f47c697d | 560 | int minSamplPer; |
4e8ad0dc MK |
561 | struct usbduxfastsub_s *udfs = dev->private; |
562 | ||
563 | if (!udfs->probed) | |
f47c697d | 564 | return -ENODEV; |
4e8ad0dc | 565 | |
f47c697d | 566 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc MK |
567 | printk(KERN_DEBUG "comedi%d: usbduxfast_ai_cmdtest\n", dev->minor); |
568 | printk(KERN_DEBUG "comedi%d: usbduxfast: convert_arg=%u " | |
569 | "scan_begin_arg=%u\n", | |
570 | dev->minor, cmd->convert_arg, cmd->scan_begin_arg); | |
f47c697d BP |
571 | #endif |
572 | /* step 1: make sure trigger sources are trivially valid */ | |
573 | ||
574 | tmp = cmd->start_src; | |
575 | cmd->start_src &= TRIG_NOW | TRIG_EXT | TRIG_INT; | |
576 | if (!cmd->start_src || tmp != cmd->start_src) | |
577 | err++; | |
578 | ||
579 | tmp = cmd->scan_begin_src; | |
580 | cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT; | |
581 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
582 | err++; | |
583 | ||
584 | tmp = cmd->convert_src; | |
585 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
586 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
587 | err++; | |
588 | ||
589 | tmp = cmd->scan_end_src; | |
590 | cmd->scan_end_src &= TRIG_COUNT; | |
591 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
592 | err++; | |
593 | ||
594 | tmp = cmd->stop_src; | |
595 | stop_mask = TRIG_COUNT | TRIG_NONE; | |
596 | cmd->stop_src &= stop_mask; | |
597 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
598 | err++; | |
599 | ||
600 | if (err) | |
601 | return 1; | |
602 | ||
4e8ad0dc MK |
603 | /* |
604 | * step 2: make sure trigger sources are unique and mutually compatible | |
605 | */ | |
f47c697d BP |
606 | |
607 | if (cmd->start_src != TRIG_NOW && | |
0a85b6f0 | 608 | cmd->start_src != TRIG_EXT && cmd->start_src != TRIG_INT) |
f47c697d BP |
609 | err++; |
610 | if (cmd->scan_begin_src != TRIG_TIMER && | |
0a85b6f0 MT |
611 | cmd->scan_begin_src != TRIG_FOLLOW && |
612 | cmd->scan_begin_src != TRIG_EXT) | |
f47c697d BP |
613 | err++; |
614 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
615 | err++; | |
616 | if (cmd->stop_src != TRIG_COUNT && | |
0a85b6f0 | 617 | cmd->stop_src != TRIG_EXT && cmd->stop_src != TRIG_NONE) |
f47c697d BP |
618 | err++; |
619 | ||
4e8ad0dc | 620 | /* can't have external stop and start triggers at once */ |
f47c697d BP |
621 | if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) |
622 | err++; | |
623 | ||
624 | if (err) | |
625 | return 2; | |
626 | ||
627 | /* step 3: make sure arguments are trivially compatible */ | |
628 | ||
629 | if (cmd->start_src == TRIG_NOW && cmd->start_arg != 0) { | |
630 | cmd->start_arg = 0; | |
631 | err++; | |
632 | } | |
633 | ||
4e8ad0dc | 634 | if (!cmd->chanlist_len) |
f47c697d | 635 | err++; |
4e8ad0dc | 636 | |
f47c697d BP |
637 | if (cmd->scan_end_arg != cmd->chanlist_len) { |
638 | cmd->scan_end_arg = cmd->chanlist_len; | |
639 | err++; | |
640 | } | |
641 | ||
4e8ad0dc | 642 | if (cmd->chanlist_len == 1) |
f47c697d | 643 | minSamplPer = 1; |
4e8ad0dc | 644 | else |
f47c697d | 645 | minSamplPer = MIN_SAMPLING_PERIOD; |
f47c697d BP |
646 | |
647 | if (cmd->convert_src == TRIG_TIMER) { | |
648 | steps = cmd->convert_arg * 30; | |
4e8ad0dc | 649 | if (steps < (minSamplPer * 1000)) |
f47c697d | 650 | steps = minSamplPer * 1000; |
4e8ad0dc MK |
651 | |
652 | if (steps > (MAX_SAMPLING_PERIOD * 1000)) | |
f47c697d | 653 | steps = MAX_SAMPLING_PERIOD * 1000; |
4e8ad0dc MK |
654 | |
655 | /* calc arg again */ | |
f47c697d BP |
656 | tmp = steps / 30; |
657 | if (cmd->convert_arg != tmp) { | |
658 | cmd->convert_arg = tmp; | |
659 | err++; | |
660 | } | |
661 | } | |
662 | ||
4e8ad0dc | 663 | if (cmd->scan_begin_src == TRIG_TIMER) |
f47c697d | 664 | err++; |
4e8ad0dc MK |
665 | |
666 | /* stop source */ | |
f47c697d BP |
667 | switch (cmd->stop_src) { |
668 | case TRIG_COUNT: | |
669 | if (!cmd->stop_arg) { | |
670 | cmd->stop_arg = 1; | |
671 | err++; | |
672 | } | |
673 | break; | |
674 | case TRIG_NONE: | |
675 | if (cmd->stop_arg != 0) { | |
676 | cmd->stop_arg = 0; | |
677 | err++; | |
678 | } | |
679 | break; | |
4e8ad0dc MK |
680 | /* |
681 | * TRIG_EXT doesn't care since it doesn't trigger | |
682 | * off a numbered channel | |
683 | */ | |
f47c697d BP |
684 | default: |
685 | break; | |
686 | } | |
687 | ||
688 | if (err) | |
689 | return 3; | |
690 | ||
691 | /* step 4: fix up any arguments */ | |
692 | ||
693 | return 0; | |
694 | ||
695 | } | |
696 | ||
71b5f4f1 | 697 | static int usbduxfast_ai_inttrig(struct comedi_device *dev, |
0a85b6f0 MT |
698 | struct comedi_subdevice *s, |
699 | unsigned int trignum) | |
f47c697d BP |
700 | { |
701 | int ret; | |
4e8ad0dc MK |
702 | struct usbduxfastsub_s *udfs = dev->private; |
703 | ||
704 | if (!udfs) | |
f47c697d | 705 | return -EFAULT; |
4e8ad0dc MK |
706 | |
707 | down(&udfs->sem); | |
708 | if (!udfs->probed) { | |
709 | up(&udfs->sem); | |
f47c697d BP |
710 | return -ENODEV; |
711 | } | |
712 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc | 713 | printk(KERN_DEBUG "comedi%d: usbduxfast_ai_inttrig\n", dev->minor); |
f47c697d BP |
714 | #endif |
715 | ||
716 | if (trignum != 0) { | |
4e8ad0dc MK |
717 | printk(KERN_ERR "comedi%d: usbduxfast_ai_inttrig: invalid" |
718 | " trignum\n", dev->minor); | |
719 | up(&udfs->sem); | |
f47c697d BP |
720 | return -EINVAL; |
721 | } | |
4e8ad0dc MK |
722 | if (!udfs->ai_cmd_running) { |
723 | udfs->ai_cmd_running = 1; | |
724 | ret = usbduxfastsub_submit_InURBs(udfs); | |
f47c697d | 725 | if (ret < 0) { |
4e8ad0dc MK |
726 | printk(KERN_ERR "comedi%d: usbduxfast_ai_inttrig: " |
727 | "urbSubmit: err=%d\n", dev->minor, ret); | |
728 | udfs->ai_cmd_running = 0; | |
729 | up(&udfs->sem); | |
f47c697d BP |
730 | return ret; |
731 | } | |
732 | s->async->inttrig = NULL; | |
733 | } else { | |
4e8ad0dc MK |
734 | printk(KERN_ERR "comedi%d: ai_inttrig but acqu is already" |
735 | " running\n", dev->minor); | |
f47c697d | 736 | } |
4e8ad0dc | 737 | up(&udfs->sem); |
f47c697d BP |
738 | return 1; |
739 | } | |
740 | ||
4e8ad0dc MK |
741 | /* |
742 | * offsets for the GPIF bytes | |
743 | * the first byte is the command byte | |
744 | */ | |
745 | #define LENBASE (1+0x00) | |
746 | #define OPBASE (1+0x08) | |
747 | #define OUTBASE (1+0x10) | |
748 | #define LOGBASE (1+0x18) | |
f47c697d | 749 | |
0a85b6f0 MT |
750 | static int usbduxfast_ai_cmd(struct comedi_device *dev, |
751 | struct comedi_subdevice *s) | |
f47c697d | 752 | { |
ea6d0d4c | 753 | struct comedi_cmd *cmd = &s->async->cmd; |
f47c697d BP |
754 | unsigned int chan, gain, rngmask = 0xff; |
755 | int i, j, ret; | |
4e8ad0dc | 756 | struct usbduxfastsub_s *udfs; |
f47c697d BP |
757 | int result; |
758 | long steps, steps_tmp; | |
759 | ||
760 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc | 761 | printk(KERN_DEBUG "comedi%d: usbduxfast_ai_cmd\n", dev->minor); |
f47c697d | 762 | #endif |
4e8ad0dc MK |
763 | udfs = dev->private; |
764 | if (!udfs) | |
f47c697d | 765 | return -EFAULT; |
4e8ad0dc MK |
766 | |
767 | down(&udfs->sem); | |
768 | if (!udfs->probed) { | |
769 | up(&udfs->sem); | |
f47c697d BP |
770 | return -ENODEV; |
771 | } | |
4e8ad0dc MK |
772 | if (udfs->ai_cmd_running) { |
773 | printk(KERN_ERR "comedi%d: ai_cmd not possible. Another ai_cmd" | |
774 | " is running.\n", dev->minor); | |
775 | up(&udfs->sem); | |
f47c697d BP |
776 | return -EBUSY; |
777 | } | |
25985edc | 778 | /* set current channel of the running acquisition to zero */ |
f47c697d BP |
779 | s->async->cur_chan = 0; |
780 | ||
4e8ad0dc MK |
781 | /* |
782 | * ignore the first buffers from the device if there | |
783 | * is an error condition | |
784 | */ | |
785 | udfs->ignore = PACKETS_TO_IGNORE; | |
f47c697d BP |
786 | |
787 | if (cmd->chanlist_len > 0) { | |
788 | gain = CR_RANGE(cmd->chanlist[0]); | |
789 | for (i = 0; i < cmd->chanlist_len; ++i) { | |
790 | chan = CR_CHAN(cmd->chanlist[i]); | |
791 | if (chan != i) { | |
4e8ad0dc MK |
792 | printk(KERN_ERR "comedi%d: cmd is accepting " |
793 | "only consecutive channels.\n", | |
794 | dev->minor); | |
795 | up(&udfs->sem); | |
f47c697d BP |
796 | return -EINVAL; |
797 | } | |
798 | if ((gain != CR_RANGE(cmd->chanlist[i])) | |
0a85b6f0 | 799 | && (cmd->chanlist_len > 3)) { |
4e8ad0dc MK |
800 | printk(KERN_ERR "comedi%d: the gain must be" |
801 | " the same for all channels.\n", | |
802 | dev->minor); | |
803 | up(&udfs->sem); | |
f47c697d BP |
804 | return -EINVAL; |
805 | } | |
806 | if (i >= NUMCHANNELS) { | |
4e8ad0dc MK |
807 | printk(KERN_ERR "comedi%d: channel list too" |
808 | " long\n", dev->minor); | |
f47c697d BP |
809 | break; |
810 | } | |
811 | } | |
812 | } | |
813 | steps = 0; | |
814 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
4e8ad0dc MK |
815 | printk(KERN_ERR "comedi%d: usbduxfast: " |
816 | "scan_begin_src==TRIG_TIMER not valid.\n", dev->minor); | |
817 | up(&udfs->sem); | |
f47c697d BP |
818 | return -EINVAL; |
819 | } | |
4e8ad0dc | 820 | if (cmd->convert_src == TRIG_TIMER) |
f47c697d | 821 | steps = (cmd->convert_arg * 30) / 1000; |
4e8ad0dc | 822 | |
f47c697d | 823 | if ((steps < MIN_SAMPLING_PERIOD) && (cmd->chanlist_len != 1)) { |
4e8ad0dc MK |
824 | printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: steps=%ld, " |
825 | "scan_begin_arg=%d. Not properly tested by cmdtest?\n", | |
826 | dev->minor, steps, cmd->scan_begin_arg); | |
827 | up(&udfs->sem); | |
f47c697d BP |
828 | return -EINVAL; |
829 | } | |
830 | if (steps > MAX_SAMPLING_PERIOD) { | |
4e8ad0dc MK |
831 | printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: sampling rate " |
832 | "too low.\n", dev->minor); | |
833 | up(&udfs->sem); | |
f47c697d BP |
834 | return -EINVAL; |
835 | } | |
836 | if ((cmd->start_src == TRIG_EXT) && (cmd->chanlist_len != 1) | |
0a85b6f0 | 837 | && (cmd->chanlist_len != 16)) { |
4e8ad0dc MK |
838 | printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: TRIG_EXT only" |
839 | " with 1 or 16 channels possible.\n", dev->minor); | |
840 | up(&udfs->sem); | |
f47c697d BP |
841 | return -EINVAL; |
842 | } | |
843 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
844 | printk(KERN_DEBUG "comedi%d: usbduxfast: steps=%ld, convert_arg=%u\n", |
845 | dev->minor, steps, cmd->convert_arg); | |
f47c697d BP |
846 | #endif |
847 | ||
848 | switch (cmd->chanlist_len) { | |
f47c697d | 849 | case 1: |
4e8ad0dc MK |
850 | /* |
851 | * one channel | |
852 | */ | |
853 | ||
f47c697d BP |
854 | if (CR_RANGE(cmd->chanlist[0]) > 0) |
855 | rngmask = 0xff - 0x04; | |
856 | else | |
857 | rngmask = 0xff; | |
858 | ||
4e8ad0dc MK |
859 | /* |
860 | * for external trigger: looping in this state until | |
861 | * the RDY0 pin becomes zero | |
862 | */ | |
863 | ||
864 | /* we loop here until ready has been set */ | |
865 | if (cmd->start_src == TRIG_EXT) { | |
866 | /* branch back to state 0 */ | |
0a85b6f0 | 867 | udfs->dux_commands[LENBASE + 0] = 0x01; |
4e8ad0dc | 868 | /* deceision state w/o data */ |
0a85b6f0 MT |
869 | udfs->dux_commands[OPBASE + 0] = 0x01; |
870 | udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; | |
4e8ad0dc | 871 | /* RDY0 = 0 */ |
0a85b6f0 | 872 | udfs->dux_commands[LOGBASE + 0] = 0x00; |
4e8ad0dc | 873 | } else { /* we just proceed to state 1 */ |
0a85b6f0 MT |
874 | udfs->dux_commands[LENBASE + 0] = 1; |
875 | udfs->dux_commands[OPBASE + 0] = 0; | |
876 | udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; | |
877 | udfs->dux_commands[LOGBASE + 0] = 0; | |
f47c697d BP |
878 | } |
879 | ||
880 | if (steps < MIN_SAMPLING_PERIOD) { | |
4e8ad0dc | 881 | /* for fast single channel aqu without mux */ |
f47c697d | 882 | if (steps <= 1) { |
4e8ad0dc MK |
883 | /* |
884 | * we just stay here at state 1 and rexecute | |
885 | * the same state this gives us 30MHz sampling | |
886 | * rate | |
887 | */ | |
888 | ||
889 | /* branch back to state 1 */ | |
0a85b6f0 | 890 | udfs->dux_commands[LENBASE + 1] = 0x89; |
4e8ad0dc | 891 | /* deceision state with data */ |
0a85b6f0 MT |
892 | udfs->dux_commands[OPBASE + 1] = 0x03; |
893 | udfs->dux_commands[OUTBASE + 1] = | |
894 | 0xFF & rngmask; | |
4e8ad0dc | 895 | /* doesn't matter */ |
0a85b6f0 | 896 | udfs->dux_commands[LOGBASE + 1] = 0xFF; |
f47c697d | 897 | } else { |
4e8ad0dc MK |
898 | /* |
899 | * we loop through two states: data and delay | |
900 | * max rate is 15MHz | |
901 | */ | |
0a85b6f0 | 902 | udfs->dux_commands[LENBASE + 1] = steps - 1; |
4e8ad0dc | 903 | /* data */ |
0a85b6f0 MT |
904 | udfs->dux_commands[OPBASE + 1] = 0x02; |
905 | udfs->dux_commands[OUTBASE + 1] = | |
906 | 0xFF & rngmask; | |
4e8ad0dc | 907 | /* doesn't matter */ |
0a85b6f0 | 908 | udfs->dux_commands[LOGBASE + 1] = 0; |
4e8ad0dc | 909 | /* branch back to state 1 */ |
0a85b6f0 | 910 | udfs->dux_commands[LENBASE + 2] = 0x09; |
4e8ad0dc | 911 | /* deceision state w/o data */ |
0a85b6f0 MT |
912 | udfs->dux_commands[OPBASE + 2] = 0x01; |
913 | udfs->dux_commands[OUTBASE + 2] = | |
914 | 0xFF & rngmask; | |
4e8ad0dc | 915 | /* doesn't matter */ |
0a85b6f0 | 916 | udfs->dux_commands[LOGBASE + 2] = 0xFF; |
f47c697d BP |
917 | } |
918 | } else { | |
4e8ad0dc MK |
919 | /* |
920 | * we loop through 3 states: 2x delay and 1x data | |
921 | * this gives a min sampling rate of 60kHz | |
922 | */ | |
f47c697d | 923 | |
4e8ad0dc | 924 | /* we have 1 state with duration 1 */ |
f47c697d BP |
925 | steps = steps - 1; |
926 | ||
4e8ad0dc | 927 | /* do the first part of the delay */ |
0a85b6f0 MT |
928 | udfs->dux_commands[LENBASE + 1] = steps / 2; |
929 | udfs->dux_commands[OPBASE + 1] = 0; | |
930 | udfs->dux_commands[OUTBASE + 1] = 0xFF & rngmask; | |
931 | udfs->dux_commands[LOGBASE + 1] = 0; | |
4e8ad0dc MK |
932 | |
933 | /* and the second part */ | |
0a85b6f0 MT |
934 | udfs->dux_commands[LENBASE + 2] = steps - steps / 2; |
935 | udfs->dux_commands[OPBASE + 2] = 0; | |
936 | udfs->dux_commands[OUTBASE + 2] = 0xFF & rngmask; | |
937 | udfs->dux_commands[LOGBASE + 2] = 0; | |
4e8ad0dc MK |
938 | |
939 | /* get the data and branch back */ | |
940 | ||
941 | /* branch back to state 1 */ | |
0a85b6f0 | 942 | udfs->dux_commands[LENBASE + 3] = 0x09; |
4e8ad0dc | 943 | /* deceision state w data */ |
0a85b6f0 MT |
944 | udfs->dux_commands[OPBASE + 3] = 0x03; |
945 | udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; | |
4e8ad0dc | 946 | /* doesn't matter */ |
0a85b6f0 | 947 | udfs->dux_commands[LOGBASE + 3] = 0xFF; |
f47c697d BP |
948 | } |
949 | break; | |
950 | ||
951 | case 2: | |
4e8ad0dc MK |
952 | /* |
953 | * two channels | |
954 | * commit data to the FIFO | |
955 | */ | |
956 | ||
f47c697d BP |
957 | if (CR_RANGE(cmd->chanlist[0]) > 0) |
958 | rngmask = 0xff - 0x04; | |
959 | else | |
960 | rngmask = 0xff; | |
f47c697d | 961 | |
0a85b6f0 | 962 | udfs->dux_commands[LENBASE + 0] = 1; |
4e8ad0dc | 963 | /* data */ |
0a85b6f0 MT |
964 | udfs->dux_commands[OPBASE + 0] = 0x02; |
965 | udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; | |
966 | udfs->dux_commands[LOGBASE + 0] = 0; | |
4e8ad0dc MK |
967 | |
968 | /* we have 1 state with duration 1: state 0 */ | |
f47c697d BP |
969 | steps_tmp = steps - 1; |
970 | ||
971 | if (CR_RANGE(cmd->chanlist[1]) > 0) | |
972 | rngmask = 0xff - 0x04; | |
973 | else | |
974 | rngmask = 0xff; | |
4e8ad0dc MK |
975 | |
976 | /* do the first part of the delay */ | |
0a85b6f0 MT |
977 | udfs->dux_commands[LENBASE + 1] = steps_tmp / 2; |
978 | udfs->dux_commands[OPBASE + 1] = 0; | |
4e8ad0dc | 979 | /* count */ |
0a85b6f0 MT |
980 | udfs->dux_commands[OUTBASE + 1] = 0xFE & rngmask; |
981 | udfs->dux_commands[LOGBASE + 1] = 0; | |
4e8ad0dc MK |
982 | |
983 | /* and the second part */ | |
0a85b6f0 MT |
984 | udfs->dux_commands[LENBASE + 2] = steps_tmp - steps_tmp / 2; |
985 | udfs->dux_commands[OPBASE + 2] = 0; | |
986 | udfs->dux_commands[OUTBASE + 2] = 0xFF & rngmask; | |
987 | udfs->dux_commands[LOGBASE + 2] = 0; | |
4e8ad0dc | 988 | |
0a85b6f0 | 989 | udfs->dux_commands[LENBASE + 3] = 1; |
4e8ad0dc | 990 | /* data */ |
0a85b6f0 MT |
991 | udfs->dux_commands[OPBASE + 3] = 0x02; |
992 | udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; | |
993 | udfs->dux_commands[LOGBASE + 3] = 0; | |
4e8ad0dc MK |
994 | |
995 | /* | |
996 | * we have 2 states with duration 1: step 6 and | |
997 | * the IDLE state | |
998 | */ | |
f47c697d BP |
999 | steps_tmp = steps - 2; |
1000 | ||
1001 | if (CR_RANGE(cmd->chanlist[0]) > 0) | |
1002 | rngmask = 0xff - 0x04; | |
1003 | else | |
1004 | rngmask = 0xff; | |
4e8ad0dc MK |
1005 | |
1006 | /* do the first part of the delay */ | |
0a85b6f0 MT |
1007 | udfs->dux_commands[LENBASE + 4] = steps_tmp / 2; |
1008 | udfs->dux_commands[OPBASE + 4] = 0; | |
4e8ad0dc | 1009 | /* reset */ |
0a85b6f0 MT |
1010 | udfs->dux_commands[OUTBASE + 4] = (0xFF - 0x02) & rngmask; |
1011 | udfs->dux_commands[LOGBASE + 4] = 0; | |
4e8ad0dc MK |
1012 | |
1013 | /* and the second part */ | |
0a85b6f0 MT |
1014 | udfs->dux_commands[LENBASE + 5] = steps_tmp - steps_tmp / 2; |
1015 | udfs->dux_commands[OPBASE + 5] = 0; | |
1016 | udfs->dux_commands[OUTBASE + 5] = 0xFF & rngmask; | |
1017 | udfs->dux_commands[LOGBASE + 5] = 0; | |
1018 | ||
1019 | udfs->dux_commands[LENBASE + 6] = 1; | |
1020 | udfs->dux_commands[OPBASE + 6] = 0; | |
1021 | udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; | |
1022 | udfs->dux_commands[LOGBASE + 6] = 0; | |
f47c697d BP |
1023 | break; |
1024 | ||
1025 | case 3: | |
4e8ad0dc MK |
1026 | /* |
1027 | * three channels | |
1028 | */ | |
f47c697d BP |
1029 | for (j = 0; j < 1; j++) { |
1030 | if (CR_RANGE(cmd->chanlist[j]) > 0) | |
1031 | rngmask = 0xff - 0x04; | |
1032 | else | |
1033 | rngmask = 0xff; | |
4e8ad0dc MK |
1034 | /* |
1035 | * commit data to the FIFO and do the first part | |
1036 | * of the delay | |
1037 | */ | |
0a85b6f0 | 1038 | udfs->dux_commands[LENBASE + j * 2] = steps / 2; |
4e8ad0dc | 1039 | /* data */ |
0a85b6f0 | 1040 | udfs->dux_commands[OPBASE + j * 2] = 0x02; |
4e8ad0dc | 1041 | /* no change */ |
0a85b6f0 MT |
1042 | udfs->dux_commands[OUTBASE + j * 2] = 0xFF & rngmask; |
1043 | udfs->dux_commands[LOGBASE + j * 2] = 0; | |
f47c697d BP |
1044 | |
1045 | if (CR_RANGE(cmd->chanlist[j + 1]) > 0) | |
1046 | rngmask = 0xff - 0x04; | |
1047 | else | |
1048 | rngmask = 0xff; | |
4e8ad0dc MK |
1049 | |
1050 | /* do the second part of the delay */ | |
0a85b6f0 MT |
1051 | udfs->dux_commands[LENBASE + j * 2 + 1] = |
1052 | steps - steps / 2; | |
4e8ad0dc | 1053 | /* no data */ |
0a85b6f0 | 1054 | udfs->dux_commands[OPBASE + j * 2 + 1] = 0; |
4e8ad0dc | 1055 | /* count */ |
0a85b6f0 MT |
1056 | udfs->dux_commands[OUTBASE + j * 2 + 1] = |
1057 | 0xFE & rngmask; | |
1058 | udfs->dux_commands[LOGBASE + j * 2 + 1] = 0; | |
f47c697d BP |
1059 | } |
1060 | ||
4e8ad0dc | 1061 | /* 2 steps with duration 1: the idele step and step 6: */ |
f47c697d | 1062 | steps_tmp = steps - 2; |
4e8ad0dc MK |
1063 | |
1064 | /* commit data to the FIFO and do the first part of the delay */ | |
0a85b6f0 | 1065 | udfs->dux_commands[LENBASE + 4] = steps_tmp / 2; |
4e8ad0dc | 1066 | /* data */ |
0a85b6f0 MT |
1067 | udfs->dux_commands[OPBASE + 4] = 0x02; |
1068 | udfs->dux_commands[OUTBASE + 4] = 0xFF & rngmask; | |
1069 | udfs->dux_commands[LOGBASE + 4] = 0; | |
f47c697d BP |
1070 | |
1071 | if (CR_RANGE(cmd->chanlist[0]) > 0) | |
1072 | rngmask = 0xff - 0x04; | |
1073 | else | |
1074 | rngmask = 0xff; | |
4e8ad0dc MK |
1075 | |
1076 | /* do the second part of the delay */ | |
0a85b6f0 | 1077 | udfs->dux_commands[LENBASE + 5] = steps_tmp - steps_tmp / 2; |
4e8ad0dc | 1078 | /* no data */ |
0a85b6f0 | 1079 | udfs->dux_commands[OPBASE + 5] = 0; |
4e8ad0dc | 1080 | /* reset */ |
0a85b6f0 MT |
1081 | udfs->dux_commands[OUTBASE + 5] = (0xFF - 0x02) & rngmask; |
1082 | udfs->dux_commands[LOGBASE + 5] = 0; | |
4e8ad0dc | 1083 | |
0a85b6f0 MT |
1084 | udfs->dux_commands[LENBASE + 6] = 1; |
1085 | udfs->dux_commands[OPBASE + 6] = 0; | |
1086 | udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; | |
1087 | udfs->dux_commands[LOGBASE + 6] = 0; | |
f47c697d BP |
1088 | |
1089 | case 16: | |
1090 | if (CR_RANGE(cmd->chanlist[0]) > 0) | |
1091 | rngmask = 0xff - 0x04; | |
1092 | else | |
1093 | rngmask = 0xff; | |
4e8ad0dc MK |
1094 | |
1095 | if (cmd->start_src == TRIG_EXT) { | |
1096 | /* | |
1097 | * we loop here until ready has been set | |
1098 | */ | |
1099 | ||
1100 | /* branch back to state 0 */ | |
0a85b6f0 | 1101 | udfs->dux_commands[LENBASE + 0] = 0x01; |
4e8ad0dc | 1102 | /* deceision state w/o data */ |
0a85b6f0 | 1103 | udfs->dux_commands[OPBASE + 0] = 0x01; |
4e8ad0dc | 1104 | /* reset */ |
0a85b6f0 MT |
1105 | udfs->dux_commands[OUTBASE + 0] = |
1106 | (0xFF - 0x02) & rngmask; | |
4e8ad0dc | 1107 | /* RDY0 = 0 */ |
0a85b6f0 | 1108 | udfs->dux_commands[LOGBASE + 0] = 0x00; |
4e8ad0dc MK |
1109 | } else { |
1110 | /* | |
1111 | * we just proceed to state 1 | |
1112 | */ | |
1113 | ||
1114 | /* 30us reset pulse */ | |
0a85b6f0 MT |
1115 | udfs->dux_commands[LENBASE + 0] = 255; |
1116 | udfs->dux_commands[OPBASE + 0] = 0; | |
4e8ad0dc | 1117 | /* reset */ |
0a85b6f0 MT |
1118 | udfs->dux_commands[OUTBASE + 0] = |
1119 | (0xFF - 0x02) & rngmask; | |
1120 | udfs->dux_commands[LOGBASE + 0] = 0; | |
f47c697d BP |
1121 | } |
1122 | ||
4e8ad0dc | 1123 | /* commit data to the FIFO */ |
0a85b6f0 | 1124 | udfs->dux_commands[LENBASE + 1] = 1; |
4e8ad0dc | 1125 | /* data */ |
0a85b6f0 MT |
1126 | udfs->dux_commands[OPBASE + 1] = 0x02; |
1127 | udfs->dux_commands[OUTBASE + 1] = 0xFF & rngmask; | |
1128 | udfs->dux_commands[LOGBASE + 1] = 0; | |
f47c697d | 1129 | |
4e8ad0dc | 1130 | /* we have 2 states with duration 1 */ |
f47c697d BP |
1131 | steps = steps - 2; |
1132 | ||
4e8ad0dc | 1133 | /* do the first part of the delay */ |
0a85b6f0 MT |
1134 | udfs->dux_commands[LENBASE + 2] = steps / 2; |
1135 | udfs->dux_commands[OPBASE + 2] = 0; | |
1136 | udfs->dux_commands[OUTBASE + 2] = 0xFE & rngmask; | |
1137 | udfs->dux_commands[LOGBASE + 2] = 0; | |
4e8ad0dc MK |
1138 | |
1139 | /* and the second part */ | |
0a85b6f0 MT |
1140 | udfs->dux_commands[LENBASE + 3] = steps - steps / 2; |
1141 | udfs->dux_commands[OPBASE + 3] = 0; | |
1142 | udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; | |
1143 | udfs->dux_commands[LOGBASE + 3] = 0; | |
4e8ad0dc MK |
1144 | |
1145 | /* branch back to state 1 */ | |
0a85b6f0 | 1146 | udfs->dux_commands[LENBASE + 4] = 0x09; |
4e8ad0dc | 1147 | /* deceision state w/o data */ |
0a85b6f0 MT |
1148 | udfs->dux_commands[OPBASE + 4] = 0x01; |
1149 | udfs->dux_commands[OUTBASE + 4] = 0xFF & rngmask; | |
4e8ad0dc | 1150 | /* doesn't matter */ |
0a85b6f0 | 1151 | udfs->dux_commands[LOGBASE + 4] = 0xFF; |
f47c697d BP |
1152 | |
1153 | break; | |
1154 | ||
1155 | default: | |
4e8ad0dc MK |
1156 | printk(KERN_ERR "comedi %d: unsupported combination of " |
1157 | "channels\n", dev->minor); | |
1158 | up(&udfs->sem); | |
f47c697d BP |
1159 | return -EFAULT; |
1160 | } | |
1161 | ||
1162 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1163 | printk(KERN_DEBUG "comedi %d: sending commands to the usb device\n", |
1164 | dev->minor); | |
f47c697d | 1165 | #endif |
4e8ad0dc MK |
1166 | /* 0 means that the AD commands are sent */ |
1167 | result = send_dux_commands(udfs, SENDADCOMMANDS); | |
f47c697d | 1168 | if (result < 0) { |
4e8ad0dc MK |
1169 | printk(KERN_ERR "comedi%d: adc command could not be submitted." |
1170 | "Aborting...\n", dev->minor); | |
1171 | up(&udfs->sem); | |
f47c697d BP |
1172 | return result; |
1173 | } | |
1174 | if (cmd->stop_src == TRIG_COUNT) { | |
0a85b6f0 | 1175 | udfs->ai_sample_count = cmd->stop_arg * cmd->scan_end_arg; |
4e8ad0dc MK |
1176 | if (udfs->ai_sample_count < 1) { |
1177 | printk(KERN_ERR "comedi%d: " | |
1178 | "(cmd->stop_arg)*(cmd->scan_end_arg)<1, " | |
1179 | "aborting.\n", dev->minor); | |
1180 | up(&udfs->sem); | |
f47c697d BP |
1181 | return -EFAULT; |
1182 | } | |
4e8ad0dc | 1183 | udfs->ai_continous = 0; |
f47c697d | 1184 | } else { |
25985edc | 1185 | /* continous acquisition */ |
4e8ad0dc MK |
1186 | udfs->ai_continous = 1; |
1187 | udfs->ai_sample_count = 0; | |
f47c697d BP |
1188 | } |
1189 | ||
1190 | if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) { | |
4e8ad0dc MK |
1191 | /* enable this acquisition operation */ |
1192 | udfs->ai_cmd_running = 1; | |
1193 | ret = usbduxfastsub_submit_InURBs(udfs); | |
f47c697d | 1194 | if (ret < 0) { |
4e8ad0dc MK |
1195 | udfs->ai_cmd_running = 0; |
1196 | /* fixme: unlink here?? */ | |
1197 | up(&udfs->sem); | |
f47c697d BP |
1198 | return ret; |
1199 | } | |
1200 | s->async->inttrig = NULL; | |
1201 | } else { | |
4e8ad0dc MK |
1202 | /* |
1203 | * TRIG_INT | |
1204 | * don't enable the acquision operation | |
1205 | * wait for an internal signal | |
1206 | */ | |
f47c697d BP |
1207 | s->async->inttrig = usbduxfast_ai_inttrig; |
1208 | } | |
4e8ad0dc | 1209 | up(&udfs->sem); |
f47c697d BP |
1210 | |
1211 | return 0; | |
1212 | } | |
1213 | ||
4e8ad0dc MK |
1214 | /* |
1215 | * Mode 0 is used to get a single conversion on demand. | |
1216 | */ | |
71b5f4f1 | 1217 | static int usbduxfast_ai_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
1218 | struct comedi_subdevice *s, |
1219 | struct comedi_insn *insn, unsigned int *data) | |
f47c697d BP |
1220 | { |
1221 | int i, j, n, actual_length; | |
1222 | int chan, range, rngmask; | |
1223 | int err; | |
4e8ad0dc | 1224 | struct usbduxfastsub_s *udfs; |
f47c697d | 1225 | |
4e8ad0dc MK |
1226 | udfs = dev->private; |
1227 | if (!udfs) { | |
1228 | printk(KERN_ERR "comedi%d: ai_insn_read: no usb dev.\n", | |
1229 | dev->minor); | |
f47c697d BP |
1230 | return -ENODEV; |
1231 | } | |
1232 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1233 | printk(KERN_DEBUG "comedi%d: ai_insn_read, insn->n=%d, " |
1234 | "insn->subdev=%d\n", dev->minor, insn->n, insn->subdev); | |
f47c697d | 1235 | #endif |
4e8ad0dc MK |
1236 | down(&udfs->sem); |
1237 | if (!udfs->probed) { | |
1238 | up(&udfs->sem); | |
f47c697d BP |
1239 | return -ENODEV; |
1240 | } | |
4e8ad0dc MK |
1241 | if (udfs->ai_cmd_running) { |
1242 | printk(KERN_ERR "comedi%d: ai_insn_read not possible. Async " | |
1243 | "Command is running.\n", dev->minor); | |
1244 | up(&udfs->sem); | |
f47c697d BP |
1245 | return -EBUSY; |
1246 | } | |
4e8ad0dc | 1247 | /* sample one channel */ |
f47c697d BP |
1248 | chan = CR_CHAN(insn->chanspec); |
1249 | range = CR_RANGE(insn->chanspec); | |
4e8ad0dc | 1250 | /* set command for the first channel */ |
f47c697d BP |
1251 | |
1252 | if (range > 0) | |
1253 | rngmask = 0xff - 0x04; | |
1254 | else | |
1255 | rngmask = 0xff; | |
4e8ad0dc MK |
1256 | |
1257 | /* commit data to the FIFO */ | |
0a85b6f0 | 1258 | udfs->dux_commands[LENBASE + 0] = 1; |
4e8ad0dc | 1259 | /* data */ |
0a85b6f0 MT |
1260 | udfs->dux_commands[OPBASE + 0] = 0x02; |
1261 | udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; | |
1262 | udfs->dux_commands[LOGBASE + 0] = 0; | |
4e8ad0dc MK |
1263 | |
1264 | /* do the first part of the delay */ | |
0a85b6f0 MT |
1265 | udfs->dux_commands[LENBASE + 1] = 12; |
1266 | udfs->dux_commands[OPBASE + 1] = 0; | |
1267 | udfs->dux_commands[OUTBASE + 1] = 0xFE & rngmask; | |
1268 | udfs->dux_commands[LOGBASE + 1] = 0; | |
1269 | ||
1270 | udfs->dux_commands[LENBASE + 2] = 1; | |
1271 | udfs->dux_commands[OPBASE + 2] = 0; | |
1272 | udfs->dux_commands[OUTBASE + 2] = 0xFE & rngmask; | |
1273 | udfs->dux_commands[LOGBASE + 2] = 0; | |
1274 | ||
1275 | udfs->dux_commands[LENBASE + 3] = 1; | |
1276 | udfs->dux_commands[OPBASE + 3] = 0; | |
1277 | udfs->dux_commands[OUTBASE + 3] = 0xFE & rngmask; | |
1278 | udfs->dux_commands[LOGBASE + 3] = 0; | |
1279 | ||
1280 | udfs->dux_commands[LENBASE + 4] = 1; | |
1281 | udfs->dux_commands[OPBASE + 4] = 0; | |
1282 | udfs->dux_commands[OUTBASE + 4] = 0xFE & rngmask; | |
1283 | udfs->dux_commands[LOGBASE + 4] = 0; | |
4e8ad0dc MK |
1284 | |
1285 | /* second part */ | |
0a85b6f0 MT |
1286 | udfs->dux_commands[LENBASE + 5] = 12; |
1287 | udfs->dux_commands[OPBASE + 5] = 0; | |
1288 | udfs->dux_commands[OUTBASE + 5] = 0xFF & rngmask; | |
1289 | udfs->dux_commands[LOGBASE + 5] = 0; | |
4e8ad0dc | 1290 | |
0a85b6f0 MT |
1291 | udfs->dux_commands[LENBASE + 6] = 1; |
1292 | udfs->dux_commands[OPBASE + 6] = 0; | |
1293 | udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; | |
1294 | udfs->dux_commands[LOGBASE + 0] = 0; | |
f47c697d BP |
1295 | |
1296 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1297 | printk(KERN_DEBUG "comedi %d: sending commands to the usb device\n", |
1298 | dev->minor); | |
f47c697d | 1299 | #endif |
4e8ad0dc MK |
1300 | /* 0 means that the AD commands are sent */ |
1301 | err = send_dux_commands(udfs, SENDADCOMMANDS); | |
f47c697d | 1302 | if (err < 0) { |
4e8ad0dc MK |
1303 | printk(KERN_ERR "comedi%d: adc command could not be submitted." |
1304 | "Aborting...\n", dev->minor); | |
1305 | up(&udfs->sem); | |
f47c697d BP |
1306 | return err; |
1307 | } | |
1308 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1309 | printk(KERN_DEBUG "comedi%d: usbduxfast: submitting in-urb: " |
1310 | "0x%p,0x%p\n", udfs->comedidev->minor, udfs->urbIn->context, | |
1311 | udfs->urbIn->dev); | |
f47c697d BP |
1312 | #endif |
1313 | for (i = 0; i < PACKETS_TO_IGNORE; i++) { | |
4e8ad0dc MK |
1314 | err = usb_bulk_msg(udfs->usbdev, |
1315 | usb_rcvbulkpipe(udfs->usbdev, BULKINEP), | |
1316 | udfs->transfer_buffer, SIZEINBUF, | |
88676359 | 1317 | &actual_length, 10000); |
f47c697d | 1318 | if (err < 0) { |
4e8ad0dc | 1319 | printk(KERN_ERR "comedi%d: insn timeout. No data.\n", |
0a85b6f0 | 1320 | dev->minor); |
4e8ad0dc | 1321 | up(&udfs->sem); |
f47c697d BP |
1322 | return err; |
1323 | } | |
1324 | } | |
4e8ad0dc | 1325 | /* data points */ |
f47c697d | 1326 | for (i = 0; i < insn->n;) { |
4e8ad0dc MK |
1327 | err = usb_bulk_msg(udfs->usbdev, |
1328 | usb_rcvbulkpipe(udfs->usbdev, BULKINEP), | |
1329 | udfs->transfer_buffer, SIZEINBUF, | |
88676359 | 1330 | &actual_length, 10000); |
f47c697d | 1331 | if (err < 0) { |
4e8ad0dc | 1332 | printk(KERN_ERR "comedi%d: insn data error: %d\n", |
0a85b6f0 | 1333 | dev->minor, err); |
4e8ad0dc | 1334 | up(&udfs->sem); |
f47c697d BP |
1335 | return err; |
1336 | } | |
1337 | n = actual_length / sizeof(uint16_t); | |
1338 | if ((n % 16) != 0) { | |
4e8ad0dc MK |
1339 | printk(KERN_ERR "comedi%d: insn data packet " |
1340 | "corrupted.\n", dev->minor); | |
1341 | up(&udfs->sem); | |
f47c697d BP |
1342 | return -EINVAL; |
1343 | } | |
1344 | for (j = chan; (j < n) && (i < insn->n); j = j + 16) { | |
4e8ad0dc | 1345 | data[i] = ((uint16_t *) (udfs->transfer_buffer))[j]; |
f47c697d BP |
1346 | i++; |
1347 | } | |
1348 | } | |
4e8ad0dc | 1349 | up(&udfs->sem); |
f47c697d BP |
1350 | return i; |
1351 | } | |
1352 | ||
f47c697d BP |
1353 | #define FIRMWARE_MAX_LEN 0x2000 |
1354 | ||
81874ff7 | 1355 | static int firmwareUpload(struct usbduxfastsub_s *usbduxfastsub, |
de55a7a5 | 1356 | const u8 *firmwareBinary, int sizeFirmware) |
f47c697d | 1357 | { |
81874ff7 BP |
1358 | int ret; |
1359 | uint8_t *fwBuf; | |
f47c697d | 1360 | |
81874ff7 BP |
1361 | if (!firmwareBinary) |
1362 | return 0; | |
f47c697d | 1363 | |
0a85b6f0 | 1364 | if (sizeFirmware > FIRMWARE_MAX_LEN) { |
81874ff7 BP |
1365 | dev_err(&usbduxfastsub->interface->dev, |
1366 | "comedi_: usbduxfast firmware binary it too large for FX2.\n"); | |
1367 | return -ENOMEM; | |
1368 | } | |
4e8ad0dc | 1369 | |
81874ff7 | 1370 | /* we generate a local buffer for the firmware */ |
94002c07 | 1371 | fwBuf = kmemdup(firmwareBinary, sizeFirmware, GFP_KERNEL); |
81874ff7 BP |
1372 | if (!fwBuf) { |
1373 | dev_err(&usbduxfastsub->interface->dev, | |
1374 | "comedi_: mem alloc for firmware failed\n"); | |
1375 | return -ENOMEM; | |
1376 | } | |
f47c697d | 1377 | |
81874ff7 BP |
1378 | ret = usbduxfastsub_stop(usbduxfastsub); |
1379 | if (ret < 0) { | |
1380 | dev_err(&usbduxfastsub->interface->dev, | |
1381 | "comedi_: can not stop firmware\n"); | |
1382 | kfree(fwBuf); | |
1383 | return ret; | |
1384 | } | |
f47c697d | 1385 | |
81874ff7 BP |
1386 | ret = usbduxfastsub_upload(usbduxfastsub, fwBuf, 0, sizeFirmware); |
1387 | if (ret < 0) { | |
1388 | dev_err(&usbduxfastsub->interface->dev, | |
1389 | "comedi_: firmware upload failed\n"); | |
1390 | kfree(fwBuf); | |
1391 | return ret; | |
f47c697d | 1392 | } |
81874ff7 BP |
1393 | ret = usbduxfastsub_start(usbduxfastsub); |
1394 | if (ret < 0) { | |
1395 | dev_err(&usbduxfastsub->interface->dev, | |
1396 | "comedi_: can not start firmware\n"); | |
1397 | kfree(fwBuf); | |
1398 | return ret; | |
1399 | } | |
1400 | kfree(fwBuf); | |
1401 | return 0; | |
f47c697d BP |
1402 | } |
1403 | ||
4e8ad0dc | 1404 | static void tidy_up(struct usbduxfastsub_s *udfs) |
f47c697d BP |
1405 | { |
1406 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc | 1407 | printk(KERN_DEBUG "comedi_: usbduxfast: tiding up\n"); |
f47c697d | 1408 | #endif |
4e8ad0dc MK |
1409 | |
1410 | if (!udfs) | |
f47c697d | 1411 | return; |
6fffdb35 | 1412 | |
4e8ad0dc MK |
1413 | /* shows the usb subsystem that the driver is down */ |
1414 | if (udfs->interface) | |
1415 | usb_set_intfdata(udfs->interface, NULL); | |
f47c697d | 1416 | |
4e8ad0dc | 1417 | udfs->probed = 0; |
f47c697d | 1418 | |
4e8ad0dc MK |
1419 | if (udfs->urbIn) { |
1420 | /* waits until a running transfer is over */ | |
1421 | usb_kill_urb(udfs->urbIn); | |
1422 | ||
1423 | kfree(udfs->transfer_buffer); | |
1424 | udfs->transfer_buffer = NULL; | |
1425 | ||
1426 | usb_free_urb(udfs->urbIn); | |
1427 | udfs->urbIn = NULL; | |
f47c697d | 1428 | } |
4e8ad0dc MK |
1429 | |
1430 | kfree(udfs->insnBuffer); | |
1431 | udfs->insnBuffer = NULL; | |
1432 | ||
1433 | kfree(udfs->dux_commands); | |
1434 | udfs->dux_commands = NULL; | |
1435 | ||
1436 | udfs->ai_cmd_running = 0; | |
f47c697d BP |
1437 | } |
1438 | ||
0a85b6f0 MT |
1439 | static void usbduxfast_firmware_request_complete_handler(const struct firmware |
1440 | *fw, void *context) | |
6742c0af BP |
1441 | { |
1442 | struct usbduxfastsub_s *usbduxfastsub_tmp = context; | |
1443 | struct usb_device *usbdev = usbduxfastsub_tmp->usbdev; | |
1444 | int ret; | |
1445 | ||
1446 | if (fw == NULL) | |
1447 | return; | |
1448 | ||
1449 | /* | |
1450 | * we need to upload the firmware here because fw will be | |
1451 | * freed once we've left this function | |
1452 | */ | |
81874ff7 | 1453 | ret = firmwareUpload(usbduxfastsub_tmp, fw->data, fw->size); |
6742c0af BP |
1454 | |
1455 | if (ret) { | |
1456 | dev_err(&usbdev->dev, | |
0a85b6f0 | 1457 | "Could not upload firmware (err=%d)\n", ret); |
9ebfbd45 | 1458 | goto out; |
6742c0af BP |
1459 | } |
1460 | ||
1461 | comedi_usb_auto_config(usbdev, BOARDNAME); | |
9ebfbd45 JB |
1462 | out: |
1463 | release_firmware(fw); | |
6742c0af BP |
1464 | } |
1465 | ||
4e8ad0dc MK |
1466 | /* |
1467 | * allocate memory for the urbs and initialise them | |
1468 | */ | |
f47c697d | 1469 | static int usbduxfastsub_probe(struct usb_interface *uinterf, |
0a85b6f0 | 1470 | const struct usb_device_id *id) |
f47c697d BP |
1471 | { |
1472 | struct usb_device *udev = interface_to_usbdev(uinterf); | |
f47c697d BP |
1473 | int i; |
1474 | int index; | |
6742c0af | 1475 | int ret; |
f47c697d BP |
1476 | |
1477 | if (udev->speed != USB_SPEED_HIGH) { | |
4e8ad0dc MK |
1478 | printk(KERN_ERR "comedi_: usbduxfast_: This driver needs" |
1479 | "USB 2.0 to operate. Aborting...\n"); | |
88676359 | 1480 | return -ENODEV; |
f47c697d BP |
1481 | } |
1482 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1483 | printk(KERN_DEBUG "comedi_: usbduxfast_: finding a free structure for " |
1484 | "the usb-device\n"); | |
f47c697d BP |
1485 | #endif |
1486 | down(&start_stop_sem); | |
4e8ad0dc | 1487 | /* look for a free place in the usbduxfast array */ |
f47c697d BP |
1488 | index = -1; |
1489 | for (i = 0; i < NUMUSBDUXFAST; i++) { | |
4e8ad0dc | 1490 | if (!usbduxfastsub[i].probed) { |
f47c697d BP |
1491 | index = i; |
1492 | break; | |
1493 | } | |
1494 | } | |
1495 | ||
4e8ad0dc | 1496 | /* no more space */ |
f47c697d | 1497 | if (index == -1) { |
4e8ad0dc | 1498 | printk(KERN_ERR "Too many usbduxfast-devices connected.\n"); |
f47c697d | 1499 | up(&start_stop_sem); |
88676359 | 1500 | return -EMFILE; |
f47c697d BP |
1501 | } |
1502 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1503 | printk(KERN_DEBUG "comedi_: usbduxfast: usbduxfastsub[%d] is ready to " |
1504 | "connect to comedi.\n", index); | |
f47c697d BP |
1505 | #endif |
1506 | ||
45f4d024 | 1507 | sema_init(&(usbduxfastsub[index].sem), 1); |
4e8ad0dc | 1508 | /* save a pointer to the usb device */ |
f47c697d BP |
1509 | usbduxfastsub[index].usbdev = udev; |
1510 | ||
4e8ad0dc | 1511 | /* save the interface itself */ |
f47c697d | 1512 | usbduxfastsub[index].interface = uinterf; |
4e8ad0dc | 1513 | /* get the interface number from the interface */ |
f47c697d | 1514 | usbduxfastsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; |
4e8ad0dc MK |
1515 | /* |
1516 | * hand the private data over to the usb subsystem | |
1517 | * will be needed for disconnect | |
1518 | */ | |
f47c697d | 1519 | usb_set_intfdata(uinterf, &(usbduxfastsub[index])); |
f47c697d BP |
1520 | |
1521 | #ifdef CONFIG_COMEDI_DEBUG | |
4e8ad0dc MK |
1522 | printk(KERN_DEBUG "comedi_: usbduxfast: ifnum=%d\n", |
1523 | usbduxfastsub[index].ifnum); | |
f47c697d | 1524 | #endif |
4e8ad0dc | 1525 | /* create space for the commands going to the usb device */ |
f47c697d | 1526 | usbduxfastsub[index].dux_commands = kmalloc(SIZEOFDUXBUFFER, |
4e8ad0dc | 1527 | GFP_KERNEL); |
f47c697d | 1528 | if (!usbduxfastsub[index].dux_commands) { |
4e8ad0dc MK |
1529 | printk(KERN_ERR "comedi_: usbduxfast: error alloc space for " |
1530 | "dac commands\n"); | |
f47c697d BP |
1531 | tidy_up(&(usbduxfastsub[index])); |
1532 | up(&start_stop_sem); | |
88676359 | 1533 | return -ENOMEM; |
f47c697d | 1534 | } |
4e8ad0dc | 1535 | /* create space of the instruction buffer */ |
f47c697d | 1536 | usbduxfastsub[index].insnBuffer = kmalloc(SIZEINSNBUF, GFP_KERNEL); |
4e8ad0dc MK |
1537 | if (!usbduxfastsub[index].insnBuffer) { |
1538 | printk(KERN_ERR "comedi_: usbduxfast: could not alloc space " | |
1539 | "for insnBuffer\n"); | |
f47c697d BP |
1540 | tidy_up(&(usbduxfastsub[index])); |
1541 | up(&start_stop_sem); | |
88676359 | 1542 | return -ENOMEM; |
f47c697d | 1543 | } |
4e8ad0dc | 1544 | /* setting to alternate setting 1: enabling bulk ep */ |
f47c697d | 1545 | i = usb_set_interface(usbduxfastsub[index].usbdev, |
0a85b6f0 | 1546 | usbduxfastsub[index].ifnum, 1); |
f47c697d | 1547 | if (i < 0) { |
4e8ad0dc MK |
1548 | printk(KERN_ERR "comedi_: usbduxfast%d: could not switch to " |
1549 | "alternate setting 1.\n", index); | |
f47c697d BP |
1550 | tidy_up(&(usbduxfastsub[index])); |
1551 | up(&start_stop_sem); | |
88676359 | 1552 | return -ENODEV; |
f47c697d | 1553 | } |
88676359 | 1554 | usbduxfastsub[index].urbIn = usb_alloc_urb(0, GFP_KERNEL); |
4e8ad0dc MK |
1555 | if (!usbduxfastsub[index].urbIn) { |
1556 | printk(KERN_ERR "comedi_: usbduxfast%d: Could not alloc." | |
1557 | "urb\n", index); | |
f47c697d BP |
1558 | tidy_up(&(usbduxfastsub[index])); |
1559 | up(&start_stop_sem); | |
88676359 | 1560 | return -ENOMEM; |
f47c697d BP |
1561 | } |
1562 | usbduxfastsub[index].transfer_buffer = kmalloc(SIZEINBUF, GFP_KERNEL); | |
4e8ad0dc MK |
1563 | if (!usbduxfastsub[index].transfer_buffer) { |
1564 | printk(KERN_ERR "comedi_: usbduxfast%d: could not alloc. " | |
1565 | "transb.\n", index); | |
f47c697d BP |
1566 | tidy_up(&(usbduxfastsub[index])); |
1567 | up(&start_stop_sem); | |
88676359 | 1568 | return -ENOMEM; |
f47c697d | 1569 | } |
4e8ad0dc | 1570 | /* we've reached the bottom of the function */ |
f47c697d BP |
1571 | usbduxfastsub[index].probed = 1; |
1572 | up(&start_stop_sem); | |
6742c0af BP |
1573 | |
1574 | ret = request_firmware_nowait(THIS_MODULE, | |
1575 | FW_ACTION_HOTPLUG, | |
81874ff7 | 1576 | "usbduxfast_firmware.bin", |
6742c0af | 1577 | &udev->dev, |
9ebfbd45 | 1578 | GFP_KERNEL, |
6742c0af BP |
1579 | usbduxfastsub + index, |
1580 | usbduxfast_firmware_request_complete_handler); | |
1581 | ||
1582 | if (ret) { | |
0a85b6f0 | 1583 | dev_err(&udev->dev, "could not load firmware (err=%d)\n", ret); |
6742c0af BP |
1584 | return ret; |
1585 | } | |
1586 | ||
4e8ad0dc MK |
1587 | printk(KERN_INFO "comedi_: usbduxfast%d has been successfully " |
1588 | "initialized.\n", index); | |
1589 | /* success */ | |
f47c697d | 1590 | return 0; |
f47c697d BP |
1591 | } |
1592 | ||
f47c697d BP |
1593 | static void usbduxfastsub_disconnect(struct usb_interface *intf) |
1594 | { | |
4e8ad0dc | 1595 | struct usbduxfastsub_s *udfs = usb_get_intfdata(intf); |
f47c697d | 1596 | struct usb_device *udev = interface_to_usbdev(intf); |
6fffdb35 | 1597 | |
4e8ad0dc MK |
1598 | if (!udfs) { |
1599 | printk(KERN_ERR "comedi_: usbduxfast: disconnect called with " | |
1600 | "null pointer.\n"); | |
f47c697d BP |
1601 | return; |
1602 | } | |
4e8ad0dc MK |
1603 | if (udfs->usbdev != udev) { |
1604 | printk(KERN_ERR "comedi_: usbduxfast: BUG! called with wrong " | |
1605 | "ptr!!!\n"); | |
f47c697d BP |
1606 | return; |
1607 | } | |
6742c0af BP |
1608 | |
1609 | comedi_usb_auto_unconfig(udev); | |
1610 | ||
f47c697d | 1611 | down(&start_stop_sem); |
4e8ad0dc MK |
1612 | down(&udfs->sem); |
1613 | tidy_up(udfs); | |
1614 | up(&udfs->sem); | |
f47c697d | 1615 | up(&start_stop_sem); |
4e8ad0dc | 1616 | |
f47c697d | 1617 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc | 1618 | printk(KERN_DEBUG "comedi_: usbduxfast: disconnected from the usb\n"); |
f47c697d BP |
1619 | #endif |
1620 | } | |
1621 | ||
4e8ad0dc MK |
1622 | /* |
1623 | * is called when comedi-config is called | |
1624 | */ | |
0a85b6f0 MT |
1625 | static int usbduxfast_attach(struct comedi_device *dev, |
1626 | struct comedi_devconfig *it) | |
f47c697d BP |
1627 | { |
1628 | int ret; | |
1629 | int index; | |
1630 | int i; | |
34c43922 | 1631 | struct comedi_subdevice *s = NULL; |
f47c697d BP |
1632 | dev->private = NULL; |
1633 | ||
1634 | down(&start_stop_sem); | |
4e8ad0dc MK |
1635 | /* |
1636 | * find a valid device which has been detected by the | |
1637 | * probe function of the usb | |
1638 | */ | |
f47c697d BP |
1639 | index = -1; |
1640 | for (i = 0; i < NUMUSBDUXFAST; i++) { | |
4e8ad0dc | 1641 | if (usbduxfastsub[i].probed && !usbduxfastsub[i].attached) { |
f47c697d BP |
1642 | index = i; |
1643 | break; | |
1644 | } | |
1645 | } | |
1646 | ||
1647 | if (index < 0) { | |
4e8ad0dc MK |
1648 | printk(KERN_ERR "comedi%d: usbduxfast: error: attach failed, " |
1649 | "no usbduxfast devs connected to the usb bus.\n", | |
1650 | dev->minor); | |
f47c697d BP |
1651 | up(&start_stop_sem); |
1652 | return -ENODEV; | |
1653 | } | |
1654 | ||
1655 | down(&(usbduxfastsub[index].sem)); | |
4e8ad0dc | 1656 | /* pointer back to the corresponding comedi device */ |
f47c697d BP |
1657 | usbduxfastsub[index].comedidev = dev; |
1658 | ||
4e8ad0dc | 1659 | /* trying to upload the firmware into the chip */ |
f47c697d | 1660 | if (comedi_aux_data(it->options, 0) && |
6742c0af | 1661 | it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { |
81874ff7 BP |
1662 | firmwareUpload(&usbduxfastsub[index], |
1663 | comedi_aux_data(it->options, 0), | |
1664 | it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); | |
f47c697d BP |
1665 | } |
1666 | ||
1667 | dev->board_name = BOARDNAME; | |
1668 | ||
1669 | /* set number of subdevices */ | |
1670 | dev->n_subdevices = N_SUBDEVICES; | |
1671 | ||
4e8ad0dc MK |
1672 | /* allocate space for the subdevices */ |
1673 | ret = alloc_subdevices(dev, N_SUBDEVICES); | |
1674 | if (ret < 0) { | |
1675 | printk(KERN_ERR "comedi%d: usbduxfast: error alloc space for " | |
1676 | "subdev\n", dev->minor); | |
e57795a1 | 1677 | up(&(usbduxfastsub[index].sem)); |
f47c697d BP |
1678 | up(&start_stop_sem); |
1679 | return ret; | |
1680 | } | |
1681 | ||
4e8ad0dc MK |
1682 | printk(KERN_INFO "comedi%d: usbduxfast: usb-device %d is attached to " |
1683 | "comedi.\n", dev->minor, index); | |
1684 | /* private structure is also simply the usb-structure */ | |
f47c697d | 1685 | dev->private = usbduxfastsub + index; |
4e8ad0dc | 1686 | /* the first subdevice is the A/D converter */ |
f47c697d | 1687 | s = dev->subdevices + SUBDEV_AD; |
4e8ad0dc MK |
1688 | /* |
1689 | * the URBs get the comedi subdevice which is responsible for reading | |
1690 | * this is the subdevice which reads data | |
1691 | */ | |
f47c697d | 1692 | dev->read_subdev = s; |
4e8ad0dc | 1693 | /* the subdevice receives as private structure the usb-structure */ |
f47c697d | 1694 | s->private = NULL; |
4e8ad0dc | 1695 | /* analog input */ |
f47c697d | 1696 | s->type = COMEDI_SUBD_AI; |
4e8ad0dc | 1697 | /* readable and ref is to ground */ |
f47c697d | 1698 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; |
4e8ad0dc | 1699 | /* 16 channels */ |
f47c697d | 1700 | s->n_chan = 16; |
4e8ad0dc | 1701 | /* length of the channellist */ |
f47c697d | 1702 | s->len_chanlist = 16; |
4e8ad0dc | 1703 | /* callback functions */ |
f47c697d BP |
1704 | s->insn_read = usbduxfast_ai_insn_read; |
1705 | s->do_cmdtest = usbduxfast_ai_cmdtest; | |
1706 | s->do_cmd = usbduxfast_ai_cmd; | |
1707 | s->cancel = usbduxfast_ai_cancel; | |
4e8ad0dc | 1708 | /* max value from the A/D converter (12bit+1 bit for overflow) */ |
f47c697d | 1709 | s->maxdata = 0x1000; |
4e8ad0dc | 1710 | /* range table to convert to physical units */ |
f47c697d BP |
1711 | s->range_table = &range_usbduxfast_ai_range; |
1712 | ||
4e8ad0dc | 1713 | /* finally decide that it's attached */ |
f47c697d BP |
1714 | usbduxfastsub[index].attached = 1; |
1715 | ||
1716 | up(&(usbduxfastsub[index].sem)); | |
f47c697d | 1717 | up(&start_stop_sem); |
4e8ad0dc MK |
1718 | printk(KERN_INFO "comedi%d: successfully attached to usbduxfast.\n", |
1719 | dev->minor); | |
f47c697d BP |
1720 | |
1721 | return 0; | |
1722 | } | |
1723 | ||
71b5f4f1 | 1724 | static int usbduxfast_detach(struct comedi_device *dev) |
f47c697d | 1725 | { |
4e8ad0dc | 1726 | struct usbduxfastsub_s *udfs; |
f47c697d | 1727 | |
f47c697d | 1728 | if (!dev) { |
4e8ad0dc MK |
1729 | printk(KERN_ERR "comedi?: usbduxfast: detach without dev " |
1730 | "variable...\n"); | |
f47c697d BP |
1731 | return -EFAULT; |
1732 | } | |
98ccdc56 | 1733 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc MK |
1734 | printk(KERN_DEBUG "comedi%d: usbduxfast: detach usb device\n", |
1735 | dev->minor); | |
98ccdc56 JL |
1736 | #endif |
1737 | ||
4e8ad0dc MK |
1738 | udfs = dev->private; |
1739 | if (!udfs) { | |
1740 | printk(KERN_ERR "comedi?: usbduxfast: detach without ptr to " | |
1741 | "usbduxfastsub[]\n"); | |
f47c697d BP |
1742 | return -EFAULT; |
1743 | } | |
1744 | ||
4e8ad0dc | 1745 | down(&udfs->sem); |
f47c697d | 1746 | down(&start_stop_sem); |
4e8ad0dc MK |
1747 | /* |
1748 | * Don't allow detach to free the private structure | |
1749 | * It's one entry of of usbduxfastsub[] | |
1750 | */ | |
f47c697d | 1751 | dev->private = NULL; |
4e8ad0dc MK |
1752 | udfs->attached = 0; |
1753 | udfs->comedidev = NULL; | |
f47c697d | 1754 | #ifdef CONFIG_COMEDI_DEBUG |
4e8ad0dc MK |
1755 | printk(KERN_DEBUG "comedi%d: usbduxfast: detach: successfully " |
1756 | "removed\n", dev->minor); | |
f47c697d BP |
1757 | #endif |
1758 | up(&start_stop_sem); | |
4e8ad0dc | 1759 | up(&udfs->sem); |
f47c697d BP |
1760 | return 0; |
1761 | } | |
1762 | ||
4e8ad0dc MK |
1763 | /* |
1764 | * main driver struct | |
1765 | */ | |
139dfbdf | 1766 | static struct comedi_driver driver_usbduxfast = { |
0a85b6f0 MT |
1767 | .driver_name = "usbduxfast", |
1768 | .module = THIS_MODULE, | |
1769 | .attach = usbduxfast_attach, | |
1770 | .detach = usbduxfast_detach | |
f47c697d BP |
1771 | }; |
1772 | ||
4e8ad0dc MK |
1773 | /* |
1774 | * Table with the USB-devices: just now only testing IDs | |
1775 | */ | |
a457732b | 1776 | static const struct usb_device_id usbduxfastsub_table[] = { |
4e8ad0dc | 1777 | /* { USB_DEVICE(0x4b4, 0x8613) }, testing */ |
0a85b6f0 MT |
1778 | {USB_DEVICE(0x13d8, 0x0010)}, /* real ID */ |
1779 | {USB_DEVICE(0x13d8, 0x0011)}, /* real ID */ | |
1780 | {} /* Terminating entry */ | |
f47c697d BP |
1781 | }; |
1782 | ||
1783 | MODULE_DEVICE_TABLE(usb, usbduxfastsub_table); | |
1784 | ||
4e8ad0dc MK |
1785 | /* |
1786 | * The usbduxfastsub-driver | |
1787 | */ | |
f47c697d BP |
1788 | static struct usb_driver usbduxfastsub_driver = { |
1789 | #ifdef COMEDI_HAVE_USB_DRIVER_OWNER | |
0a85b6f0 | 1790 | .owner = THIS_MODULE, |
f47c697d | 1791 | #endif |
0a85b6f0 MT |
1792 | .name = BOARDNAME, |
1793 | .probe = usbduxfastsub_probe, | |
1794 | .disconnect = usbduxfastsub_disconnect, | |
1795 | .id_table = usbduxfastsub_table | |
f47c697d BP |
1796 | }; |
1797 | ||
4e8ad0dc MK |
1798 | /* |
1799 | * Can't use the nice macro as I have also to initialise the USB subsystem: | |
1800 | * registering the usb-system _and_ the comedi-driver | |
1801 | */ | |
7dcb582c | 1802 | static int __init init_usbduxfast(void) |
f47c697d | 1803 | { |
4e8ad0dc MK |
1804 | printk(KERN_INFO |
1805 | KBUILD_MODNAME ": " DRIVER_VERSION ":" DRIVER_DESC "\n"); | |
f47c697d BP |
1806 | usb_register(&usbduxfastsub_driver); |
1807 | comedi_driver_register(&driver_usbduxfast); | |
1808 | return 0; | |
1809 | } | |
1810 | ||
4e8ad0dc MK |
1811 | /* |
1812 | * deregistering the comedi driver and the usb-subsystem | |
1813 | */ | |
7dcb582c | 1814 | static void __exit exit_usbduxfast(void) |
f47c697d BP |
1815 | { |
1816 | comedi_driver_unregister(&driver_usbduxfast); | |
1817 | usb_deregister(&usbduxfastsub_driver); | |
1818 | } | |
1819 | ||
1820 | module_init(init_usbduxfast); | |
1821 | module_exit(exit_usbduxfast); | |
1822 | ||
1823 | MODULE_AUTHOR(DRIVER_AUTHOR); | |
1824 | MODULE_DESCRIPTION(DRIVER_DESC); | |
1825 | MODULE_LICENSE("GPL"); |