Commit | Line | Data |
---|---|---|
d96cba07 DS |
1 | /* |
2 | comedi/drivers/comedi_rt_timer.c | |
3 | virtual driver for using RTL timing sources | |
4 | ||
5 | Authors: David A. Schleef, Frank M. Hess | |
6 | ||
7 | COMEDI - Linux Control and Measurement Device Interface | |
8 | Copyright (C) 1999,2001 David A. Schleef <ds@schleef.org> | |
9 | ||
10 | This program is free software; you can redistribute it and/or modify | |
11 | it under the terms of the GNU General Public License as published by | |
12 | the Free Software Foundation; either version 2 of the License, or | |
13 | (at your option) any later version. | |
14 | ||
15 | This program is distributed in the hope that it will be useful, | |
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | GNU General Public License for more details. | |
19 | ||
20 | You should have received a copy of the GNU General Public License | |
21 | along with this program; if not, write to the Free Software | |
22 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
23 | ||
24 | ************************************************************************** | |
25 | */ | |
26 | /* | |
27 | Driver: comedi_rt_timer | |
28 | Description: Command emulator using real-time tasks | |
29 | Author: ds, fmhess | |
30 | Devices: | |
31 | Status: works | |
32 | ||
33 | This driver requires RTAI or RTLinux to work correctly. It doesn't | |
34 | actually drive hardware directly, but calls other drivers and uses | |
35 | a real-time task to emulate commands for drivers and devices that | |
36 | are incapable of native commands. Thus, you can get accurately | |
37 | timed I/O on any device. | |
38 | ||
39 | Since the timing is all done in software, sampling jitter is much | |
40 | higher than with a device that has an on-board timer, and maximum | |
41 | sample rate is much lower. | |
42 | ||
43 | Configuration options: | |
44 | [0] - minor number of device you wish to emulate commands for | |
45 | [1] - subdevice number you wish to emulate commands for | |
46 | */ | |
47 | /* | |
48 | TODO: | |
49 | Support for digital io commands could be added, except I can't see why | |
50 | anyone would want to use them | |
51 | What happens if device we are emulating for is de-configured? | |
52 | */ | |
53 | ||
54 | #include "../comedidev.h" | |
55 | #include "../comedilib.h" | |
56 | ||
57 | #include "comedi_fc.h" | |
58 | ||
59 | #ifdef CONFIG_COMEDI_RTL_V1 | |
60 | #include <rtl_sched.h> | |
61 | #include <asm/rt_irq.h> | |
62 | #endif | |
63 | #ifdef CONFIG_COMEDI_RTL | |
64 | #include <rtl.h> | |
65 | #include <rtl_sched.h> | |
66 | #include <rtl_compat.h> | |
67 | #include <asm/div64.h> | |
68 | ||
69 | #ifndef RTLINUX_VERSION_CODE | |
70 | #define RTLINUX_VERSION_CODE 0 | |
71 | #endif | |
72 | #ifndef RTLINUX_VERSION | |
73 | #define RTLINUX_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) | |
74 | #endif | |
75 | ||
2696fb57 | 76 | /* begin hack to workaround broken HRT_TO_8254() function on rtlinux */ |
d96cba07 | 77 | #if RTLINUX_VERSION_CODE <= RTLINUX_VERSION(3,0,100) |
2696fb57 | 78 | /* this function sole purpose is to divide a long long by 838 */ |
d96cba07 DS |
79 | static inline RTIME nano2count(long long ns) |
80 | { | |
81 | do_div(ns, 838); | |
82 | return ns; | |
83 | } | |
84 | ||
85 | #ifdef rt_get_time() | |
86 | #undef rt_get_time() | |
87 | #endif | |
88 | #define rt_get_time() nano2count(gethrtime()) | |
89 | ||
90 | #else | |
91 | ||
92 | #define nano2count(x) HRT_TO_8254(x) | |
93 | #endif | |
2696fb57 | 94 | /* end hack */ |
d96cba07 | 95 | |
2696fb57 | 96 | /* rtl-rtai compatibility */ |
d96cba07 DS |
97 | #define rt_task_wait_period() rt_task_wait() |
98 | #define rt_pend_linux_srq(irq) rtl_global_pend_irq(irq) | |
99 | #define rt_free_srq(irq) rtl_free_soft_irq(irq) | |
100 | #define rt_request_srq(x,y,z) rtl_get_soft_irq(y,"timer") | |
101 | #define rt_task_init(a,b,c,d,e,f,g) rt_task_init(a,b,c,d,(e)+1) | |
102 | #define rt_task_resume(x) rt_task_wakeup(x) | |
103 | #define rt_set_oneshot_mode() | |
104 | #define start_rt_timer(x) | |
105 | #define stop_rt_timer() | |
106 | ||
d96cba07 DS |
107 | #endif |
108 | #ifdef CONFIG_COMEDI_RTAI | |
109 | #include <rtai.h> | |
110 | #include <rtai_sched.h> | |
111 | ||
112 | #if RTAI_VERSION_CODE < RTAI_MANGLE_VERSION(3,3,0) | |
113 | #define comedi_rt_task_context_t int | |
114 | #else | |
115 | #define comedi_rt_task_context_t long | |
116 | #endif | |
117 | ||
118 | #endif | |
119 | ||
120 | /* This defines the fastest speed we will emulate. Note that | |
121 | * without a watchdog (like in RTAI), we could easily overrun our | |
122 | * task period because analog input tends to be slow. */ | |
123 | #define SPEED_LIMIT 100000 /* in nanoseconds */ | |
124 | ||
0707bb04 | 125 | static int timer_attach(struct comedi_device * dev, struct comedi_devconfig * it); |
71b5f4f1 | 126 | static int timer_detach(struct comedi_device * dev); |
34c43922 | 127 | static int timer_inttrig(struct comedi_device * dev, struct comedi_subdevice * s, |
d96cba07 | 128 | unsigned int trig_num); |
34c43922 | 129 | static int timer_start_cmd(struct comedi_device * dev, struct comedi_subdevice * s); |
d96cba07 | 130 | |
139dfbdf | 131 | static struct comedi_driver driver_timer = { |
d96cba07 DS |
132 | module:THIS_MODULE, |
133 | driver_name:"comedi_rt_timer", | |
134 | attach:timer_attach, | |
135 | detach:timer_detach, | |
2696fb57 | 136 | /* open: timer_open, */ |
d96cba07 DS |
137 | }; |
138 | ||
139 | COMEDI_INITCLEANUP(driver_timer); | |
140 | ||
ecce6332 | 141 | struct timer_private { |
2696fb57 BP |
142 | comedi_t *device; /* device we are emulating commands for */ |
143 | int subd; /* subdevice we are emulating commands for */ | |
144 | RT_TASK *rt_task; /* rt task that starts scans */ | |
145 | RT_TASK *scan_task; /* rt task that controls conversion timing in a scan */ | |
d96cba07 DS |
146 | /* io_function can point to either an input or output function |
147 | * depending on what kind of subdevice we are emulating for */ | |
ea6d0d4c | 148 | int (*io_function) (struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 | 149 | unsigned int index); |
2696fb57 BP |
150 | /* |
151 | * RTIME has units of 1 = 838 nanoseconds time at which first scan | |
152 | * started, used to check scan timing | |
153 | */ | |
d96cba07 | 154 | RTIME start; |
2696fb57 | 155 | /* time between scans */ |
d96cba07 | 156 | RTIME scan_period; |
2696fb57 | 157 | /* time between conversions in a scan */ |
d96cba07 | 158 | RTIME convert_period; |
2696fb57 BP |
159 | /* flags */ |
160 | volatile int stop; /* indicates we should stop */ | |
161 | volatile int rt_task_active; /* indicates rt_task is servicing a struct comedi_cmd */ | |
162 | volatile int scan_task_active; /* indicates scan_task is servicing a struct comedi_cmd */ | |
d96cba07 | 163 | unsigned timer_running:1; |
ecce6332 BP |
164 | }; |
165 | #define devpriv ((struct timer_private *)dev->private) | |
d96cba07 | 166 | |
34c43922 | 167 | static int timer_cancel(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 DS |
168 | { |
169 | devpriv->stop = 1; | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
2696fb57 | 174 | /* checks for scan timing error */ |
71b5f4f1 | 175 | inline static int check_scan_timing(struct comedi_device * dev, |
d96cba07 DS |
176 | unsigned long long scan) |
177 | { | |
178 | RTIME now, timing_error; | |
179 | ||
180 | now = rt_get_time(); | |
181 | timing_error = now - (devpriv->start + scan * devpriv->scan_period); | |
182 | if (timing_error > devpriv->scan_period) { | |
183 | comedi_error(dev, "timing error"); | |
184 | rt_printk("scan started %i ns late\n", timing_error * 838); | |
185 | return -1; | |
186 | } | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
2696fb57 | 191 | /* checks for conversion timing error */ |
71b5f4f1 | 192 | inline static int check_conversion_timing(struct comedi_device * dev, |
d96cba07 DS |
193 | RTIME scan_start, unsigned int conversion) |
194 | { | |
195 | RTIME now, timing_error; | |
196 | ||
197 | now = rt_get_time(); | |
198 | timing_error = | |
199 | now - (scan_start + conversion * devpriv->convert_period); | |
200 | if (timing_error > devpriv->convert_period) { | |
201 | comedi_error(dev, "timing error"); | |
202 | rt_printk("conversion started %i ns late\n", | |
203 | timing_error * 838); | |
204 | return -1; | |
205 | } | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
2696fb57 | 210 | /* devpriv->io_function for an input subdevice */ |
ea6d0d4c | 211 | static int timer_data_read(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
212 | unsigned int index) |
213 | { | |
34c43922 | 214 | struct comedi_subdevice *s = dev->read_subdev; |
d96cba07 | 215 | int ret; |
790c5541 | 216 | unsigned int data; |
d96cba07 DS |
217 | |
218 | ret = comedi_data_read(devpriv->device, devpriv->subd, | |
219 | CR_CHAN(cmd->chanlist[index]), | |
220 | CR_RANGE(cmd->chanlist[index]), | |
221 | CR_AREF(cmd->chanlist[index]), &data); | |
222 | if (ret < 0) { | |
223 | comedi_error(dev, "read error"); | |
224 | return -EIO; | |
225 | } | |
226 | if (s->flags & SDF_LSAMPL) { | |
227 | cfc_write_long_to_buffer(s, data); | |
228 | } else { | |
229 | comedi_buf_put(s->async, data); | |
230 | } | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
2696fb57 | 235 | /* devpriv->io_function for an output subdevice */ |
ea6d0d4c | 236 | static int timer_data_write(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
237 | unsigned int index) |
238 | { | |
34c43922 | 239 | struct comedi_subdevice *s = dev->write_subdev; |
d96cba07 | 240 | unsigned int num_bytes; |
790c5541 BP |
241 | short data; |
242 | unsigned int long_data; | |
d96cba07 DS |
243 | int ret; |
244 | ||
245 | if (s->flags & SDF_LSAMPL) { | |
246 | num_bytes = | |
247 | cfc_read_array_from_buffer(s, &long_data, | |
248 | sizeof(long_data)); | |
249 | } else { | |
250 | num_bytes = cfc_read_array_from_buffer(s, &data, sizeof(data)); | |
251 | long_data = data; | |
252 | } | |
253 | ||
254 | if (num_bytes == 0) { | |
255 | comedi_error(dev, "buffer underrun"); | |
256 | return -EAGAIN; | |
257 | } | |
258 | ret = comedi_data_write(devpriv->device, devpriv->subd, | |
259 | CR_CHAN(cmd->chanlist[index]), | |
260 | CR_RANGE(cmd->chanlist[index]), | |
261 | CR_AREF(cmd->chanlist[index]), long_data); | |
262 | if (ret < 0) { | |
263 | comedi_error(dev, "write error"); | |
264 | return -EIO; | |
265 | } | |
266 | ||
267 | return 0; | |
268 | } | |
269 | ||
2696fb57 | 270 | /* devpriv->io_function for DIO subdevices */ |
ea6d0d4c | 271 | static int timer_dio_read(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
272 | unsigned int index) |
273 | { | |
34c43922 | 274 | struct comedi_subdevice *s = dev->read_subdev; |
d96cba07 | 275 | int ret; |
790c5541 | 276 | unsigned int data; |
d96cba07 DS |
277 | |
278 | ret = comedi_dio_bitfield(devpriv->device, devpriv->subd, 0, &data); | |
279 | if (ret < 0) { | |
280 | comedi_error(dev, "read error"); | |
281 | return -EIO; | |
282 | } | |
283 | ||
284 | if (s->flags & SDF_LSAMPL) | |
285 | cfc_write_long_to_buffer(s, data); | |
286 | else | |
287 | cfc_write_to_buffer(s, data); | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
2696fb57 | 292 | /* performs scans */ |
d96cba07 DS |
293 | static void scan_task_func(comedi_rt_task_context_t d) |
294 | { | |
71b5f4f1 | 295 | struct comedi_device *dev = (struct comedi_device *) d; |
34c43922 | 296 | struct comedi_subdevice *s = dev->subdevices + 0; |
d163679c | 297 | struct comedi_async *async = s->async; |
ea6d0d4c | 298 | struct comedi_cmd *cmd = &async->cmd; |
d96cba07 DS |
299 | int i, ret; |
300 | unsigned long long n; | |
301 | RTIME scan_start; | |
302 | ||
2696fb57 | 303 | /* every struct comedi_cmd causes one execution of while loop */ |
d96cba07 DS |
304 | while (1) { |
305 | devpriv->scan_task_active = 1; | |
2696fb57 | 306 | /* each for loop completes one scan */ |
d96cba07 DS |
307 | for (n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; |
308 | n++) { | |
309 | if (n) { | |
2696fb57 | 310 | /* suspend task until next scan */ |
d96cba07 DS |
311 | ret = rt_task_suspend(devpriv->scan_task); |
312 | if (ret < 0) { | |
313 | comedi_error(dev, | |
314 | "error suspending scan task"); | |
315 | async->events |= COMEDI_CB_ERROR; | |
316 | goto cleanup; | |
317 | } | |
318 | } | |
2696fb57 | 319 | /* check if stop flag was set (by timer_cancel()) */ |
d96cba07 DS |
320 | if (devpriv->stop) |
321 | goto cleanup; | |
322 | ret = check_scan_timing(dev, n); | |
323 | if (ret < 0) { | |
324 | async->events |= COMEDI_CB_ERROR; | |
325 | goto cleanup; | |
326 | } | |
327 | scan_start = rt_get_time(); | |
328 | for (i = 0; i < cmd->scan_end_arg; i++) { | |
2696fb57 | 329 | /* conversion timing */ |
d96cba07 DS |
330 | if (cmd->convert_src == TRIG_TIMER && i) { |
331 | rt_task_wait_period(); | |
332 | ret = check_conversion_timing(dev, | |
333 | scan_start, i); | |
334 | if (ret < 0) { | |
335 | async->events |= | |
336 | COMEDI_CB_ERROR; | |
337 | goto cleanup; | |
338 | } | |
339 | } | |
340 | ret = devpriv->io_function(dev, cmd, i); | |
341 | if (ret < 0) { | |
342 | async->events |= COMEDI_CB_ERROR; | |
343 | goto cleanup; | |
344 | } | |
345 | } | |
346 | s->async->events |= COMEDI_CB_BLOCK; | |
347 | comedi_event(dev, s); | |
348 | s->async->events = 0; | |
349 | } | |
350 | ||
351 | cleanup: | |
352 | ||
353 | comedi_unlock(devpriv->device, devpriv->subd); | |
354 | async->events |= COMEDI_CB_EOA; | |
355 | comedi_event(dev, s); | |
356 | async->events = 0; | |
357 | devpriv->scan_task_active = 0; | |
2696fb57 | 358 | /* suspend task until next struct comedi_cmd */ |
d96cba07 DS |
359 | rt_task_suspend(devpriv->scan_task); |
360 | } | |
361 | } | |
362 | ||
363 | static void timer_task_func(comedi_rt_task_context_t d) | |
364 | { | |
71b5f4f1 | 365 | struct comedi_device *dev = (struct comedi_device *) d; |
34c43922 | 366 | struct comedi_subdevice *s = dev->subdevices + 0; |
ea6d0d4c | 367 | struct comedi_cmd *cmd = &s->async->cmd; |
d96cba07 DS |
368 | int ret; |
369 | unsigned long long n; | |
370 | ||
2696fb57 | 371 | /* every struct comedi_cmd causes one execution of while loop */ |
d96cba07 DS |
372 | while (1) { |
373 | devpriv->rt_task_active = 1; | |
374 | devpriv->scan_task_active = 1; | |
375 | devpriv->start = rt_get_time(); | |
376 | ||
377 | for (n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; | |
378 | n++) { | |
2696fb57 | 379 | /* scan timing */ |
d96cba07 DS |
380 | if (n) |
381 | rt_task_wait_period(); | |
382 | if (devpriv->scan_task_active == 0) { | |
383 | goto cleanup; | |
384 | } | |
385 | ret = rt_task_make_periodic(devpriv->scan_task, | |
386 | devpriv->start + devpriv->scan_period * n, | |
387 | devpriv->convert_period); | |
388 | if (ret < 0) { | |
389 | comedi_error(dev, "bug!"); | |
390 | } | |
391 | } | |
392 | ||
393 | cleanup: | |
394 | ||
395 | devpriv->rt_task_active = 0; | |
2696fb57 | 396 | /* suspend until next struct comedi_cmd */ |
d96cba07 DS |
397 | rt_task_suspend(devpriv->rt_task); |
398 | } | |
399 | } | |
400 | ||
34c43922 | 401 | static int timer_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 402 | struct comedi_insn * insn, unsigned int * data) |
d96cba07 | 403 | { |
90035c08 | 404 | struct comedi_insn xinsn = *insn; |
d96cba07 DS |
405 | |
406 | xinsn.data = data; | |
407 | xinsn.subdev = devpriv->subd; | |
408 | ||
409 | return comedi_do_insn(devpriv->device, &xinsn); | |
410 | } | |
411 | ||
ea6d0d4c | 412 | static int cmdtest_helper(struct comedi_cmd * cmd, |
d96cba07 DS |
413 | unsigned int start_src, |
414 | unsigned int scan_begin_src, | |
415 | unsigned int convert_src, | |
416 | unsigned int scan_end_src, unsigned int stop_src) | |
417 | { | |
418 | int err = 0; | |
419 | int tmp; | |
420 | ||
421 | tmp = cmd->start_src; | |
422 | cmd->start_src &= start_src; | |
423 | if (!cmd->start_src || tmp != cmd->start_src) | |
424 | err++; | |
425 | ||
426 | tmp = cmd->scan_begin_src; | |
427 | cmd->scan_begin_src &= scan_begin_src; | |
428 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
429 | err++; | |
430 | ||
431 | tmp = cmd->convert_src; | |
432 | cmd->convert_src &= convert_src; | |
433 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
434 | err++; | |
435 | ||
436 | tmp = cmd->scan_end_src; | |
437 | cmd->scan_end_src &= scan_end_src; | |
438 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
439 | err++; | |
440 | ||
441 | tmp = cmd->stop_src; | |
442 | cmd->stop_src &= stop_src; | |
443 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
444 | err++; | |
445 | ||
446 | return err; | |
447 | } | |
448 | ||
34c43922 | 449 | static int timer_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 450 | struct comedi_cmd * cmd) |
d96cba07 DS |
451 | { |
452 | int err = 0; | |
453 | unsigned int start_src = 0; | |
454 | ||
455 | if (s->type == COMEDI_SUBD_AO) | |
456 | start_src = TRIG_INT; | |
457 | else | |
458 | start_src = TRIG_NOW; | |
459 | ||
460 | err = cmdtest_helper(cmd, start_src, /* start_src */ | |
461 | TRIG_TIMER | TRIG_FOLLOW, /* scan_begin_src */ | |
462 | TRIG_NOW | TRIG_TIMER, /* convert_src */ | |
463 | TRIG_COUNT, /* scan_end_src */ | |
464 | TRIG_COUNT | TRIG_NONE); /* stop_src */ | |
465 | if (err) | |
466 | return 1; | |
467 | ||
468 | /* step 2: make sure trigger sources are unique and mutually | |
469 | * compatible */ | |
470 | ||
471 | if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_INT) | |
472 | err++; | |
473 | if (cmd->scan_begin_src != TRIG_TIMER && | |
474 | cmd->scan_begin_src != TRIG_FOLLOW) | |
475 | err++; | |
476 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_NOW) | |
477 | err++; | |
478 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
479 | err++; | |
480 | if (cmd->scan_begin_src == TRIG_FOLLOW | |
481 | && cmd->convert_src != TRIG_TIMER) | |
482 | err++; | |
483 | if (cmd->convert_src == TRIG_NOW && cmd->scan_begin_src != TRIG_TIMER) | |
484 | err++; | |
485 | ||
486 | if (err) | |
487 | return 2; | |
488 | ||
489 | /* step 3: make sure arguments are trivially compatible */ | |
2696fb57 | 490 | /* limit frequency, this is fairly arbitrary */ |
d96cba07 DS |
491 | if (cmd->scan_begin_src == TRIG_TIMER) { |
492 | if (cmd->scan_begin_arg < SPEED_LIMIT) { | |
493 | cmd->scan_begin_arg = SPEED_LIMIT; | |
494 | err++; | |
495 | } | |
496 | } | |
497 | if (cmd->convert_src == TRIG_TIMER) { | |
498 | if (cmd->convert_arg < SPEED_LIMIT) { | |
499 | cmd->convert_arg = SPEED_LIMIT; | |
500 | err++; | |
501 | } | |
502 | } | |
2696fb57 | 503 | /* make sure conversion and scan frequencies are compatible */ |
d96cba07 DS |
504 | if (cmd->convert_src == TRIG_TIMER && cmd->scan_begin_src == TRIG_TIMER) { |
505 | if (cmd->convert_arg * cmd->scan_end_arg > cmd->scan_begin_arg) { | |
506 | cmd->scan_begin_arg = | |
507 | cmd->convert_arg * cmd->scan_end_arg; | |
508 | err++; | |
509 | } | |
510 | } | |
511 | if (err) | |
512 | return 3; | |
513 | ||
514 | /* step 4: fix up and arguments */ | |
515 | if (err) | |
516 | return 4; | |
517 | ||
518 | return 0; | |
519 | } | |
520 | ||
34c43922 | 521 | static int timer_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 DS |
522 | { |
523 | int ret; | |
ea6d0d4c | 524 | struct comedi_cmd *cmd = &s->async->cmd; |
d96cba07 DS |
525 | |
526 | /* hack attack: drivers are not supposed to do this: */ | |
527 | dev->rt = 1; | |
528 | ||
2696fb57 | 529 | /* make sure tasks have finished cleanup of last struct comedi_cmd */ |
d96cba07 DS |
530 | if (devpriv->rt_task_active || devpriv->scan_task_active) |
531 | return -EBUSY; | |
532 | ||
533 | ret = comedi_lock(devpriv->device, devpriv->subd); | |
534 | if (ret < 0) { | |
535 | comedi_error(dev, "failed to obtain lock"); | |
536 | return ret; | |
537 | } | |
538 | switch (cmd->scan_begin_src) { | |
539 | case TRIG_TIMER: | |
540 | devpriv->scan_period = nano2count(cmd->scan_begin_arg); | |
541 | break; | |
542 | case TRIG_FOLLOW: | |
543 | devpriv->scan_period = | |
544 | nano2count(cmd->convert_arg * cmd->scan_end_arg); | |
545 | break; | |
546 | default: | |
547 | comedi_error(dev, "bug setting scan period!"); | |
548 | return -1; | |
549 | break; | |
550 | } | |
551 | switch (cmd->convert_src) { | |
552 | case TRIG_TIMER: | |
553 | devpriv->convert_period = nano2count(cmd->convert_arg); | |
554 | break; | |
555 | case TRIG_NOW: | |
556 | devpriv->convert_period = 1; | |
557 | break; | |
558 | default: | |
559 | comedi_error(dev, "bug setting conversion period!"); | |
560 | return -1; | |
561 | break; | |
562 | } | |
563 | ||
564 | if (cmd->start_src == TRIG_NOW) | |
565 | return timer_start_cmd(dev, s); | |
566 | ||
567 | s->async->inttrig = timer_inttrig; | |
568 | ||
569 | return 0; | |
570 | } | |
571 | ||
34c43922 | 572 | static int timer_inttrig(struct comedi_device * dev, struct comedi_subdevice * s, |
d96cba07 DS |
573 | unsigned int trig_num) |
574 | { | |
575 | if (trig_num != 0) | |
576 | return -EINVAL; | |
577 | ||
578 | s->async->inttrig = NULL; | |
579 | ||
580 | return timer_start_cmd(dev, s); | |
581 | } | |
582 | ||
34c43922 | 583 | static int timer_start_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 | 584 | { |
d163679c | 585 | struct comedi_async *async = s->async; |
ea6d0d4c | 586 | struct comedi_cmd *cmd = &async->cmd; |
d96cba07 DS |
587 | RTIME now, delay, period; |
588 | int ret; | |
589 | ||
590 | devpriv->stop = 0; | |
591 | s->async->events = 0; | |
592 | ||
593 | if (cmd->start_src == TRIG_NOW) | |
594 | delay = nano2count(cmd->start_arg); | |
595 | else | |
596 | delay = 0; | |
597 | ||
598 | now = rt_get_time(); | |
599 | /* Using 'period' this way gets around some weird bug in gcc-2.95.2 | |
600 | * that generates the compile error 'internal error--unrecognizable insn' | |
601 | * when rt_task_make_period() is called (observed with rtlinux-3.1, linux-2.2.19). | |
602 | * - fmhess */ | |
603 | period = devpriv->scan_period; | |
604 | ret = rt_task_make_periodic(devpriv->rt_task, now + delay, period); | |
605 | if (ret < 0) { | |
606 | comedi_error(dev, "error starting rt_task"); | |
607 | return ret; | |
608 | } | |
609 | return 0; | |
610 | } | |
611 | ||
0707bb04 | 612 | static int timer_attach(struct comedi_device * dev, struct comedi_devconfig * it) |
d96cba07 DS |
613 | { |
614 | int ret; | |
34c43922 | 615 | struct comedi_subdevice *s, *emul_s; |
71b5f4f1 | 616 | struct comedi_device *emul_dev; |
d96cba07 DS |
617 | /* These should probably be devconfig options[] */ |
618 | const int timer_priority = 4; | |
619 | const int scan_priority = timer_priority + 1; | |
620 | char path[20]; | |
621 | ||
622 | printk("comedi%d: timer: ", dev->minor); | |
623 | ||
624 | dev->board_name = "timer"; | |
625 | ||
626 | if ((ret = alloc_subdevices(dev, 1)) < 0) | |
627 | return ret; | |
ecce6332 | 628 | if ((ret = alloc_private(dev, sizeof(struct timer_private))) < 0) |
d96cba07 DS |
629 | return ret; |
630 | ||
631 | sprintf(path, "/dev/comedi%d", it->options[0]); | |
632 | devpriv->device = comedi_open(path); | |
633 | devpriv->subd = it->options[1]; | |
634 | ||
635 | printk("emulating commands for minor %i, subdevice %d\n", | |
636 | it->options[0], devpriv->subd); | |
637 | ||
638 | emul_dev = devpriv->device; | |
639 | emul_s = emul_dev->subdevices + devpriv->subd; | |
640 | ||
2696fb57 | 641 | /* input or output subdevice */ |
d96cba07 DS |
642 | s = dev->subdevices + 0; |
643 | s->type = emul_s->type; | |
644 | s->subdev_flags = emul_s->subdev_flags; /* SDF_GROUND (to fool check_driver) */ | |
645 | s->n_chan = emul_s->n_chan; | |
646 | s->len_chanlist = 1024; | |
647 | s->do_cmd = timer_cmd; | |
648 | s->do_cmdtest = timer_cmdtest; | |
649 | s->cancel = timer_cancel; | |
650 | s->maxdata = emul_s->maxdata; | |
651 | s->range_table = emul_s->range_table; | |
652 | s->range_table_list = emul_s->range_table_list; | |
653 | switch (emul_s->type) { | |
654 | case COMEDI_SUBD_AI: | |
655 | s->insn_read = timer_insn; | |
656 | dev->read_subdev = s; | |
657 | s->subdev_flags |= SDF_CMD_READ; | |
658 | devpriv->io_function = timer_data_read; | |
659 | break; | |
660 | case COMEDI_SUBD_AO: | |
661 | s->insn_write = timer_insn; | |
662 | s->insn_read = timer_insn; | |
663 | dev->write_subdev = s; | |
664 | s->subdev_flags |= SDF_CMD_WRITE; | |
665 | devpriv->io_function = timer_data_write; | |
666 | break; | |
667 | case COMEDI_SUBD_DIO: | |
668 | s->insn_write = timer_insn; | |
669 | s->insn_read = timer_insn; | |
670 | s->insn_bits = timer_insn; | |
671 | dev->read_subdev = s; | |
672 | s->subdev_flags |= SDF_CMD_READ; | |
673 | devpriv->io_function = timer_dio_read; | |
674 | break; | |
675 | default: | |
676 | comedi_error(dev, "failed to determine subdevice type!"); | |
677 | return -EINVAL; | |
678 | } | |
679 | ||
680 | rt_set_oneshot_mode(); | |
681 | start_rt_timer(1); | |
682 | devpriv->timer_running = 1; | |
683 | ||
684 | devpriv->rt_task = kzalloc(sizeof(RT_TASK), GFP_KERNEL); | |
685 | ||
2696fb57 | 686 | /* initialize real-time tasks */ |
d96cba07 DS |
687 | ret = rt_task_init(devpriv->rt_task, timer_task_func, |
688 | (comedi_rt_task_context_t) dev, 3000, timer_priority, 0, 0); | |
689 | if (ret < 0) { | |
690 | comedi_error(dev, "error initalizing rt_task"); | |
691 | kfree(devpriv->rt_task); | |
692 | devpriv->rt_task = 0; | |
693 | return ret; | |
694 | } | |
695 | ||
696 | devpriv->scan_task = kzalloc(sizeof(RT_TASK), GFP_KERNEL); | |
697 | ||
698 | ret = rt_task_init(devpriv->scan_task, scan_task_func, | |
699 | (comedi_rt_task_context_t) dev, 3000, scan_priority, 0, 0); | |
700 | if (ret < 0) { | |
701 | comedi_error(dev, "error initalizing scan_task"); | |
702 | kfree(devpriv->scan_task); | |
703 | devpriv->scan_task = 0; | |
704 | return ret; | |
705 | } | |
706 | ||
707 | return 1; | |
708 | } | |
709 | ||
2696fb57 | 710 | /* free allocated resources */ |
71b5f4f1 | 711 | static int timer_detach(struct comedi_device * dev) |
d96cba07 DS |
712 | { |
713 | printk("comedi%d: timer: remove\n", dev->minor); | |
714 | ||
715 | if (devpriv) { | |
716 | if (devpriv->rt_task) { | |
717 | rt_task_delete(devpriv->rt_task); | |
718 | kfree(devpriv->rt_task); | |
719 | } | |
720 | if (devpriv->scan_task) { | |
721 | rt_task_delete(devpriv->scan_task); | |
722 | kfree(devpriv->scan_task); | |
723 | } | |
724 | if (devpriv->timer_running) | |
725 | stop_rt_timer(); | |
726 | if (devpriv->device) | |
727 | comedi_close(devpriv->device); | |
728 | } | |
729 | return 0; | |
730 | } |