]>
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 | ||
4bde29ed IA |
107 | #define comedi_rt_task_context_t int |
108 | ||
d96cba07 DS |
109 | #endif |
110 | #ifdef CONFIG_COMEDI_RTAI | |
111 | #include <rtai.h> | |
112 | #include <rtai_sched.h> | |
5b32f439 IA |
113 | #include <rtai_version.h> |
114 | ||
115 | /* RTAI_VERSION_CODE doesn't work for rtai-3.6-cv and other strange versions. | |
116 | * These are characterized by CONFIG_RTAI_REVISION_LEVEL being defined as an | |
117 | * empty macro and CONFIG_RTAI_VERSION_MINOR being defined as something like | |
118 | * '6-cv' or '7-test1'. The problem has been noted by the RTAI folks and they | |
119 | * promise not to do it again. :-) Try and work around it here. */ | |
120 | #if !(CONFIG_RTAI_REVISION_LEVEL + 0) | |
121 | #undef CONFIG_RTAI_REVISION_LEVEL | |
122 | #define CONFIG_RTAI_REVISION_LEVEL 0 | |
123 | #define cv 0 | |
124 | #define test1 0 | |
125 | #define test2 0 | |
126 | #define test3 0 | |
127 | #endif | |
d96cba07 DS |
128 | |
129 | #if RTAI_VERSION_CODE < RTAI_MANGLE_VERSION(3,3,0) | |
130 | #define comedi_rt_task_context_t int | |
131 | #else | |
132 | #define comedi_rt_task_context_t long | |
133 | #endif | |
134 | ||
5b32f439 IA |
135 | /* Finished checking RTAI_VERSION_CODE. */ |
136 | #undef cv | |
137 | #undef test1 | |
138 | #undef test2 | |
139 | #undef test3 | |
140 | ||
d96cba07 DS |
141 | #endif |
142 | ||
143 | /* This defines the fastest speed we will emulate. Note that | |
144 | * without a watchdog (like in RTAI), we could easily overrun our | |
145 | * task period because analog input tends to be slow. */ | |
146 | #define SPEED_LIMIT 100000 /* in nanoseconds */ | |
147 | ||
0707bb04 | 148 | static int timer_attach(struct comedi_device * dev, struct comedi_devconfig * it); |
71b5f4f1 | 149 | static int timer_detach(struct comedi_device * dev); |
34c43922 | 150 | static int timer_inttrig(struct comedi_device * dev, struct comedi_subdevice * s, |
d96cba07 | 151 | unsigned int trig_num); |
34c43922 | 152 | static int timer_start_cmd(struct comedi_device * dev, struct comedi_subdevice * s); |
d96cba07 | 153 | |
139dfbdf | 154 | static struct comedi_driver driver_timer = { |
d96cba07 DS |
155 | module:THIS_MODULE, |
156 | driver_name:"comedi_rt_timer", | |
157 | attach:timer_attach, | |
158 | detach:timer_detach, | |
2696fb57 | 159 | /* open: timer_open, */ |
d96cba07 DS |
160 | }; |
161 | ||
162 | COMEDI_INITCLEANUP(driver_timer); | |
163 | ||
ecce6332 | 164 | struct timer_private { |
2696fb57 BP |
165 | comedi_t *device; /* device we are emulating commands for */ |
166 | int subd; /* subdevice we are emulating commands for */ | |
167 | RT_TASK *rt_task; /* rt task that starts scans */ | |
168 | RT_TASK *scan_task; /* rt task that controls conversion timing in a scan */ | |
d96cba07 DS |
169 | /* io_function can point to either an input or output function |
170 | * depending on what kind of subdevice we are emulating for */ | |
ea6d0d4c | 171 | int (*io_function) (struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 | 172 | unsigned int index); |
2696fb57 BP |
173 | /* |
174 | * RTIME has units of 1 = 838 nanoseconds time at which first scan | |
175 | * started, used to check scan timing | |
176 | */ | |
d96cba07 | 177 | RTIME start; |
2696fb57 | 178 | /* time between scans */ |
d96cba07 | 179 | RTIME scan_period; |
2696fb57 | 180 | /* time between conversions in a scan */ |
d96cba07 | 181 | RTIME convert_period; |
2696fb57 BP |
182 | /* flags */ |
183 | volatile int stop; /* indicates we should stop */ | |
184 | volatile int rt_task_active; /* indicates rt_task is servicing a struct comedi_cmd */ | |
185 | volatile int scan_task_active; /* indicates scan_task is servicing a struct comedi_cmd */ | |
d96cba07 | 186 | unsigned timer_running:1; |
ecce6332 BP |
187 | }; |
188 | #define devpriv ((struct timer_private *)dev->private) | |
d96cba07 | 189 | |
34c43922 | 190 | static int timer_cancel(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 DS |
191 | { |
192 | devpriv->stop = 1; | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
2696fb57 | 197 | /* checks for scan timing error */ |
71b5f4f1 | 198 | inline static int check_scan_timing(struct comedi_device * dev, |
d96cba07 DS |
199 | unsigned long long scan) |
200 | { | |
201 | RTIME now, timing_error; | |
202 | ||
203 | now = rt_get_time(); | |
204 | timing_error = now - (devpriv->start + scan * devpriv->scan_period); | |
205 | if (timing_error > devpriv->scan_period) { | |
206 | comedi_error(dev, "timing error"); | |
207 | rt_printk("scan started %i ns late\n", timing_error * 838); | |
208 | return -1; | |
209 | } | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
2696fb57 | 214 | /* checks for conversion timing error */ |
71b5f4f1 | 215 | inline static int check_conversion_timing(struct comedi_device * dev, |
d96cba07 DS |
216 | RTIME scan_start, unsigned int conversion) |
217 | { | |
218 | RTIME now, timing_error; | |
219 | ||
220 | now = rt_get_time(); | |
221 | timing_error = | |
222 | now - (scan_start + conversion * devpriv->convert_period); | |
223 | if (timing_error > devpriv->convert_period) { | |
224 | comedi_error(dev, "timing error"); | |
225 | rt_printk("conversion started %i ns late\n", | |
226 | timing_error * 838); | |
227 | return -1; | |
228 | } | |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
2696fb57 | 233 | /* devpriv->io_function for an input subdevice */ |
ea6d0d4c | 234 | static int timer_data_read(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
235 | unsigned int index) |
236 | { | |
34c43922 | 237 | struct comedi_subdevice *s = dev->read_subdev; |
d96cba07 | 238 | int ret; |
790c5541 | 239 | unsigned int data; |
d96cba07 DS |
240 | |
241 | ret = comedi_data_read(devpriv->device, devpriv->subd, | |
242 | CR_CHAN(cmd->chanlist[index]), | |
243 | CR_RANGE(cmd->chanlist[index]), | |
244 | CR_AREF(cmd->chanlist[index]), &data); | |
245 | if (ret < 0) { | |
246 | comedi_error(dev, "read error"); | |
247 | return -EIO; | |
248 | } | |
249 | if (s->flags & SDF_LSAMPL) { | |
250 | cfc_write_long_to_buffer(s, data); | |
251 | } else { | |
252 | comedi_buf_put(s->async, data); | |
253 | } | |
254 | ||
255 | return 0; | |
256 | } | |
257 | ||
2696fb57 | 258 | /* devpriv->io_function for an output subdevice */ |
ea6d0d4c | 259 | static int timer_data_write(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
260 | unsigned int index) |
261 | { | |
34c43922 | 262 | struct comedi_subdevice *s = dev->write_subdev; |
d96cba07 | 263 | unsigned int num_bytes; |
790c5541 BP |
264 | short data; |
265 | unsigned int long_data; | |
d96cba07 DS |
266 | int ret; |
267 | ||
268 | if (s->flags & SDF_LSAMPL) { | |
269 | num_bytes = | |
270 | cfc_read_array_from_buffer(s, &long_data, | |
271 | sizeof(long_data)); | |
272 | } else { | |
273 | num_bytes = cfc_read_array_from_buffer(s, &data, sizeof(data)); | |
274 | long_data = data; | |
275 | } | |
276 | ||
277 | if (num_bytes == 0) { | |
278 | comedi_error(dev, "buffer underrun"); | |
279 | return -EAGAIN; | |
280 | } | |
281 | ret = comedi_data_write(devpriv->device, devpriv->subd, | |
282 | CR_CHAN(cmd->chanlist[index]), | |
283 | CR_RANGE(cmd->chanlist[index]), | |
284 | CR_AREF(cmd->chanlist[index]), long_data); | |
285 | if (ret < 0) { | |
286 | comedi_error(dev, "write error"); | |
287 | return -EIO; | |
288 | } | |
289 | ||
290 | return 0; | |
291 | } | |
292 | ||
2696fb57 | 293 | /* devpriv->io_function for DIO subdevices */ |
ea6d0d4c | 294 | static int timer_dio_read(struct comedi_device * dev, struct comedi_cmd * cmd, |
d96cba07 DS |
295 | unsigned int index) |
296 | { | |
34c43922 | 297 | struct comedi_subdevice *s = dev->read_subdev; |
d96cba07 | 298 | int ret; |
790c5541 | 299 | unsigned int data; |
d96cba07 DS |
300 | |
301 | ret = comedi_dio_bitfield(devpriv->device, devpriv->subd, 0, &data); | |
302 | if (ret < 0) { | |
303 | comedi_error(dev, "read error"); | |
304 | return -EIO; | |
305 | } | |
306 | ||
307 | if (s->flags & SDF_LSAMPL) | |
308 | cfc_write_long_to_buffer(s, data); | |
309 | else | |
310 | cfc_write_to_buffer(s, data); | |
311 | ||
312 | return 0; | |
313 | } | |
314 | ||
2696fb57 | 315 | /* performs scans */ |
d96cba07 DS |
316 | static void scan_task_func(comedi_rt_task_context_t d) |
317 | { | |
71b5f4f1 | 318 | struct comedi_device *dev = (struct comedi_device *) d; |
34c43922 | 319 | struct comedi_subdevice *s = dev->subdevices + 0; |
d163679c | 320 | struct comedi_async *async = s->async; |
ea6d0d4c | 321 | struct comedi_cmd *cmd = &async->cmd; |
d96cba07 DS |
322 | int i, ret; |
323 | unsigned long long n; | |
324 | RTIME scan_start; | |
325 | ||
2696fb57 | 326 | /* every struct comedi_cmd causes one execution of while loop */ |
d96cba07 DS |
327 | while (1) { |
328 | devpriv->scan_task_active = 1; | |
2696fb57 | 329 | /* each for loop completes one scan */ |
d96cba07 DS |
330 | for (n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; |
331 | n++) { | |
332 | if (n) { | |
2696fb57 | 333 | /* suspend task until next scan */ |
d96cba07 DS |
334 | ret = rt_task_suspend(devpriv->scan_task); |
335 | if (ret < 0) { | |
336 | comedi_error(dev, | |
337 | "error suspending scan task"); | |
338 | async->events |= COMEDI_CB_ERROR; | |
339 | goto cleanup; | |
340 | } | |
341 | } | |
2696fb57 | 342 | /* check if stop flag was set (by timer_cancel()) */ |
d96cba07 DS |
343 | if (devpriv->stop) |
344 | goto cleanup; | |
345 | ret = check_scan_timing(dev, n); | |
346 | if (ret < 0) { | |
347 | async->events |= COMEDI_CB_ERROR; | |
348 | goto cleanup; | |
349 | } | |
350 | scan_start = rt_get_time(); | |
351 | for (i = 0; i < cmd->scan_end_arg; i++) { | |
2696fb57 | 352 | /* conversion timing */ |
d96cba07 DS |
353 | if (cmd->convert_src == TRIG_TIMER && i) { |
354 | rt_task_wait_period(); | |
355 | ret = check_conversion_timing(dev, | |
356 | scan_start, i); | |
357 | if (ret < 0) { | |
358 | async->events |= | |
359 | COMEDI_CB_ERROR; | |
360 | goto cleanup; | |
361 | } | |
362 | } | |
363 | ret = devpriv->io_function(dev, cmd, i); | |
364 | if (ret < 0) { | |
365 | async->events |= COMEDI_CB_ERROR; | |
366 | goto cleanup; | |
367 | } | |
368 | } | |
369 | s->async->events |= COMEDI_CB_BLOCK; | |
370 | comedi_event(dev, s); | |
371 | s->async->events = 0; | |
372 | } | |
373 | ||
374 | cleanup: | |
375 | ||
376 | comedi_unlock(devpriv->device, devpriv->subd); | |
377 | async->events |= COMEDI_CB_EOA; | |
378 | comedi_event(dev, s); | |
379 | async->events = 0; | |
380 | devpriv->scan_task_active = 0; | |
2696fb57 | 381 | /* suspend task until next struct comedi_cmd */ |
d96cba07 DS |
382 | rt_task_suspend(devpriv->scan_task); |
383 | } | |
384 | } | |
385 | ||
386 | static void timer_task_func(comedi_rt_task_context_t d) | |
387 | { | |
71b5f4f1 | 388 | struct comedi_device *dev = (struct comedi_device *) d; |
34c43922 | 389 | struct comedi_subdevice *s = dev->subdevices + 0; |
ea6d0d4c | 390 | struct comedi_cmd *cmd = &s->async->cmd; |
d96cba07 DS |
391 | int ret; |
392 | unsigned long long n; | |
393 | ||
2696fb57 | 394 | /* every struct comedi_cmd causes one execution of while loop */ |
d96cba07 DS |
395 | while (1) { |
396 | devpriv->rt_task_active = 1; | |
397 | devpriv->scan_task_active = 1; | |
398 | devpriv->start = rt_get_time(); | |
399 | ||
400 | for (n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; | |
401 | n++) { | |
2696fb57 | 402 | /* scan timing */ |
d96cba07 DS |
403 | if (n) |
404 | rt_task_wait_period(); | |
405 | if (devpriv->scan_task_active == 0) { | |
406 | goto cleanup; | |
407 | } | |
408 | ret = rt_task_make_periodic(devpriv->scan_task, | |
409 | devpriv->start + devpriv->scan_period * n, | |
410 | devpriv->convert_period); | |
411 | if (ret < 0) { | |
412 | comedi_error(dev, "bug!"); | |
413 | } | |
414 | } | |
415 | ||
416 | cleanup: | |
417 | ||
418 | devpriv->rt_task_active = 0; | |
2696fb57 | 419 | /* suspend until next struct comedi_cmd */ |
d96cba07 DS |
420 | rt_task_suspend(devpriv->rt_task); |
421 | } | |
422 | } | |
423 | ||
34c43922 | 424 | static int timer_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 425 | struct comedi_insn * insn, unsigned int * data) |
d96cba07 | 426 | { |
90035c08 | 427 | struct comedi_insn xinsn = *insn; |
d96cba07 DS |
428 | |
429 | xinsn.data = data; | |
430 | xinsn.subdev = devpriv->subd; | |
431 | ||
432 | return comedi_do_insn(devpriv->device, &xinsn); | |
433 | } | |
434 | ||
ea6d0d4c | 435 | static int cmdtest_helper(struct comedi_cmd * cmd, |
d96cba07 DS |
436 | unsigned int start_src, |
437 | unsigned int scan_begin_src, | |
438 | unsigned int convert_src, | |
439 | unsigned int scan_end_src, unsigned int stop_src) | |
440 | { | |
441 | int err = 0; | |
442 | int tmp; | |
443 | ||
444 | tmp = cmd->start_src; | |
445 | cmd->start_src &= start_src; | |
446 | if (!cmd->start_src || tmp != cmd->start_src) | |
447 | err++; | |
448 | ||
449 | tmp = cmd->scan_begin_src; | |
450 | cmd->scan_begin_src &= scan_begin_src; | |
451 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
452 | err++; | |
453 | ||
454 | tmp = cmd->convert_src; | |
455 | cmd->convert_src &= convert_src; | |
456 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
457 | err++; | |
458 | ||
459 | tmp = cmd->scan_end_src; | |
460 | cmd->scan_end_src &= scan_end_src; | |
461 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
462 | err++; | |
463 | ||
464 | tmp = cmd->stop_src; | |
465 | cmd->stop_src &= stop_src; | |
466 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
467 | err++; | |
468 | ||
469 | return err; | |
470 | } | |
471 | ||
34c43922 | 472 | static int timer_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 473 | struct comedi_cmd * cmd) |
d96cba07 DS |
474 | { |
475 | int err = 0; | |
476 | unsigned int start_src = 0; | |
477 | ||
478 | if (s->type == COMEDI_SUBD_AO) | |
479 | start_src = TRIG_INT; | |
480 | else | |
481 | start_src = TRIG_NOW; | |
482 | ||
483 | err = cmdtest_helper(cmd, start_src, /* start_src */ | |
484 | TRIG_TIMER | TRIG_FOLLOW, /* scan_begin_src */ | |
485 | TRIG_NOW | TRIG_TIMER, /* convert_src */ | |
486 | TRIG_COUNT, /* scan_end_src */ | |
487 | TRIG_COUNT | TRIG_NONE); /* stop_src */ | |
488 | if (err) | |
489 | return 1; | |
490 | ||
491 | /* step 2: make sure trigger sources are unique and mutually | |
492 | * compatible */ | |
493 | ||
494 | if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_INT) | |
495 | err++; | |
496 | if (cmd->scan_begin_src != TRIG_TIMER && | |
497 | cmd->scan_begin_src != TRIG_FOLLOW) | |
498 | err++; | |
499 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_NOW) | |
500 | err++; | |
501 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
502 | err++; | |
503 | if (cmd->scan_begin_src == TRIG_FOLLOW | |
504 | && cmd->convert_src != TRIG_TIMER) | |
505 | err++; | |
506 | if (cmd->convert_src == TRIG_NOW && cmd->scan_begin_src != TRIG_TIMER) | |
507 | err++; | |
508 | ||
509 | if (err) | |
510 | return 2; | |
511 | ||
512 | /* step 3: make sure arguments are trivially compatible */ | |
2696fb57 | 513 | /* limit frequency, this is fairly arbitrary */ |
d96cba07 DS |
514 | if (cmd->scan_begin_src == TRIG_TIMER) { |
515 | if (cmd->scan_begin_arg < SPEED_LIMIT) { | |
516 | cmd->scan_begin_arg = SPEED_LIMIT; | |
517 | err++; | |
518 | } | |
519 | } | |
520 | if (cmd->convert_src == TRIG_TIMER) { | |
521 | if (cmd->convert_arg < SPEED_LIMIT) { | |
522 | cmd->convert_arg = SPEED_LIMIT; | |
523 | err++; | |
524 | } | |
525 | } | |
2696fb57 | 526 | /* make sure conversion and scan frequencies are compatible */ |
d96cba07 DS |
527 | if (cmd->convert_src == TRIG_TIMER && cmd->scan_begin_src == TRIG_TIMER) { |
528 | if (cmd->convert_arg * cmd->scan_end_arg > cmd->scan_begin_arg) { | |
529 | cmd->scan_begin_arg = | |
530 | cmd->convert_arg * cmd->scan_end_arg; | |
531 | err++; | |
532 | } | |
533 | } | |
534 | if (err) | |
535 | return 3; | |
536 | ||
537 | /* step 4: fix up and arguments */ | |
538 | if (err) | |
539 | return 4; | |
540 | ||
541 | return 0; | |
542 | } | |
543 | ||
34c43922 | 544 | static int timer_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 DS |
545 | { |
546 | int ret; | |
ea6d0d4c | 547 | struct comedi_cmd *cmd = &s->async->cmd; |
d96cba07 DS |
548 | |
549 | /* hack attack: drivers are not supposed to do this: */ | |
550 | dev->rt = 1; | |
551 | ||
2696fb57 | 552 | /* make sure tasks have finished cleanup of last struct comedi_cmd */ |
d96cba07 DS |
553 | if (devpriv->rt_task_active || devpriv->scan_task_active) |
554 | return -EBUSY; | |
555 | ||
556 | ret = comedi_lock(devpriv->device, devpriv->subd); | |
557 | if (ret < 0) { | |
558 | comedi_error(dev, "failed to obtain lock"); | |
559 | return ret; | |
560 | } | |
561 | switch (cmd->scan_begin_src) { | |
562 | case TRIG_TIMER: | |
563 | devpriv->scan_period = nano2count(cmd->scan_begin_arg); | |
564 | break; | |
565 | case TRIG_FOLLOW: | |
566 | devpriv->scan_period = | |
567 | nano2count(cmd->convert_arg * cmd->scan_end_arg); | |
568 | break; | |
569 | default: | |
570 | comedi_error(dev, "bug setting scan period!"); | |
571 | return -1; | |
572 | break; | |
573 | } | |
574 | switch (cmd->convert_src) { | |
575 | case TRIG_TIMER: | |
576 | devpriv->convert_period = nano2count(cmd->convert_arg); | |
577 | break; | |
578 | case TRIG_NOW: | |
579 | devpriv->convert_period = 1; | |
580 | break; | |
581 | default: | |
582 | comedi_error(dev, "bug setting conversion period!"); | |
583 | return -1; | |
584 | break; | |
585 | } | |
586 | ||
587 | if (cmd->start_src == TRIG_NOW) | |
588 | return timer_start_cmd(dev, s); | |
589 | ||
590 | s->async->inttrig = timer_inttrig; | |
591 | ||
592 | return 0; | |
593 | } | |
594 | ||
34c43922 | 595 | static int timer_inttrig(struct comedi_device * dev, struct comedi_subdevice * s, |
d96cba07 DS |
596 | unsigned int trig_num) |
597 | { | |
598 | if (trig_num != 0) | |
599 | return -EINVAL; | |
600 | ||
601 | s->async->inttrig = NULL; | |
602 | ||
603 | return timer_start_cmd(dev, s); | |
604 | } | |
605 | ||
34c43922 | 606 | static int timer_start_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
d96cba07 | 607 | { |
d163679c | 608 | struct comedi_async *async = s->async; |
ea6d0d4c | 609 | struct comedi_cmd *cmd = &async->cmd; |
d96cba07 DS |
610 | RTIME now, delay, period; |
611 | int ret; | |
612 | ||
613 | devpriv->stop = 0; | |
614 | s->async->events = 0; | |
615 | ||
616 | if (cmd->start_src == TRIG_NOW) | |
617 | delay = nano2count(cmd->start_arg); | |
618 | else | |
619 | delay = 0; | |
620 | ||
621 | now = rt_get_time(); | |
622 | /* Using 'period' this way gets around some weird bug in gcc-2.95.2 | |
623 | * that generates the compile error 'internal error--unrecognizable insn' | |
624 | * when rt_task_make_period() is called (observed with rtlinux-3.1, linux-2.2.19). | |
625 | * - fmhess */ | |
626 | period = devpriv->scan_period; | |
627 | ret = rt_task_make_periodic(devpriv->rt_task, now + delay, period); | |
628 | if (ret < 0) { | |
629 | comedi_error(dev, "error starting rt_task"); | |
630 | return ret; | |
631 | } | |
632 | return 0; | |
633 | } | |
634 | ||
0707bb04 | 635 | static int timer_attach(struct comedi_device * dev, struct comedi_devconfig * it) |
d96cba07 DS |
636 | { |
637 | int ret; | |
34c43922 | 638 | struct comedi_subdevice *s, *emul_s; |
71b5f4f1 | 639 | struct comedi_device *emul_dev; |
d96cba07 DS |
640 | /* These should probably be devconfig options[] */ |
641 | const int timer_priority = 4; | |
642 | const int scan_priority = timer_priority + 1; | |
643 | char path[20]; | |
644 | ||
645 | printk("comedi%d: timer: ", dev->minor); | |
646 | ||
647 | dev->board_name = "timer"; | |
648 | ||
649 | if ((ret = alloc_subdevices(dev, 1)) < 0) | |
650 | return ret; | |
ecce6332 | 651 | if ((ret = alloc_private(dev, sizeof(struct timer_private))) < 0) |
d96cba07 DS |
652 | return ret; |
653 | ||
654 | sprintf(path, "/dev/comedi%d", it->options[0]); | |
655 | devpriv->device = comedi_open(path); | |
656 | devpriv->subd = it->options[1]; | |
657 | ||
658 | printk("emulating commands for minor %i, subdevice %d\n", | |
659 | it->options[0], devpriv->subd); | |
660 | ||
661 | emul_dev = devpriv->device; | |
662 | emul_s = emul_dev->subdevices + devpriv->subd; | |
663 | ||
2696fb57 | 664 | /* input or output subdevice */ |
d96cba07 DS |
665 | s = dev->subdevices + 0; |
666 | s->type = emul_s->type; | |
667 | s->subdev_flags = emul_s->subdev_flags; /* SDF_GROUND (to fool check_driver) */ | |
668 | s->n_chan = emul_s->n_chan; | |
669 | s->len_chanlist = 1024; | |
670 | s->do_cmd = timer_cmd; | |
671 | s->do_cmdtest = timer_cmdtest; | |
672 | s->cancel = timer_cancel; | |
673 | s->maxdata = emul_s->maxdata; | |
674 | s->range_table = emul_s->range_table; | |
675 | s->range_table_list = emul_s->range_table_list; | |
676 | switch (emul_s->type) { | |
677 | case COMEDI_SUBD_AI: | |
678 | s->insn_read = timer_insn; | |
679 | dev->read_subdev = s; | |
680 | s->subdev_flags |= SDF_CMD_READ; | |
681 | devpriv->io_function = timer_data_read; | |
682 | break; | |
683 | case COMEDI_SUBD_AO: | |
684 | s->insn_write = timer_insn; | |
685 | s->insn_read = timer_insn; | |
686 | dev->write_subdev = s; | |
687 | s->subdev_flags |= SDF_CMD_WRITE; | |
688 | devpriv->io_function = timer_data_write; | |
689 | break; | |
690 | case COMEDI_SUBD_DIO: | |
691 | s->insn_write = timer_insn; | |
692 | s->insn_read = timer_insn; | |
693 | s->insn_bits = timer_insn; | |
694 | dev->read_subdev = s; | |
695 | s->subdev_flags |= SDF_CMD_READ; | |
696 | devpriv->io_function = timer_dio_read; | |
697 | break; | |
698 | default: | |
699 | comedi_error(dev, "failed to determine subdevice type!"); | |
700 | return -EINVAL; | |
701 | } | |
702 | ||
703 | rt_set_oneshot_mode(); | |
704 | start_rt_timer(1); | |
705 | devpriv->timer_running = 1; | |
706 | ||
707 | devpriv->rt_task = kzalloc(sizeof(RT_TASK), GFP_KERNEL); | |
708 | ||
2696fb57 | 709 | /* initialize real-time tasks */ |
d96cba07 DS |
710 | ret = rt_task_init(devpriv->rt_task, timer_task_func, |
711 | (comedi_rt_task_context_t) dev, 3000, timer_priority, 0, 0); | |
712 | if (ret < 0) { | |
713 | comedi_error(dev, "error initalizing rt_task"); | |
714 | kfree(devpriv->rt_task); | |
715 | devpriv->rt_task = 0; | |
716 | return ret; | |
717 | } | |
718 | ||
719 | devpriv->scan_task = kzalloc(sizeof(RT_TASK), GFP_KERNEL); | |
720 | ||
721 | ret = rt_task_init(devpriv->scan_task, scan_task_func, | |
722 | (comedi_rt_task_context_t) dev, 3000, scan_priority, 0, 0); | |
723 | if (ret < 0) { | |
724 | comedi_error(dev, "error initalizing scan_task"); | |
725 | kfree(devpriv->scan_task); | |
726 | devpriv->scan_task = 0; | |
727 | return ret; | |
728 | } | |
729 | ||
730 | return 1; | |
731 | } | |
732 | ||
2696fb57 | 733 | /* free allocated resources */ |
71b5f4f1 | 734 | static int timer_detach(struct comedi_device * dev) |
d96cba07 DS |
735 | { |
736 | printk("comedi%d: timer: remove\n", dev->minor); | |
737 | ||
738 | if (devpriv) { | |
739 | if (devpriv->rt_task) { | |
740 | rt_task_delete(devpriv->rt_task); | |
741 | kfree(devpriv->rt_task); | |
742 | } | |
743 | if (devpriv->scan_task) { | |
744 | rt_task_delete(devpriv->scan_task); | |
745 | kfree(devpriv->scan_task); | |
746 | } | |
747 | if (devpriv->timer_running) | |
748 | stop_rt_timer(); | |
749 | if (devpriv->device) | |
750 | comedi_close(devpriv->device); | |
751 | } | |
752 | return 0; | |
753 | } |