]> Pileus Git - ~andy/linux/blob - drivers/staging/comedi/drivers/comedi_test.c
9f3c3d5d02a4a0b21b93dc2a332692b6cfc33373
[~andy/linux] / drivers / staging / comedi / drivers / comedi_test.c
1 /*
2     comedi/drivers/comedi_test.c
3
4     Generates fake waveform signals that can be read through
5     the command interface.  It does _not_ read from any board;
6     it just generates deterministic waveforms.
7     Useful for various testing purposes.
8
9     Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
10     Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
11
12     COMEDI - Linux Control and Measurement Device Interface
13     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
14
15     This program is free software; you can redistribute it and/or modify
16     it under the terms of the GNU General Public License as published by
17     the Free Software Foundation; either version 2 of the License, or
18     (at your option) any later version.
19
20     This program is distributed in the hope that it will be useful,
21     but WITHOUT ANY WARRANTY; without even the implied warranty of
22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     GNU General Public License for more details.
24
25     You should have received a copy of the GNU General Public License
26     along with this program; if not, write to the Free Software
27     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28
29 ************************************************************************/
30 /*
31 Driver: comedi_test
32 Description: generates fake waveforms
33 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
34   <fmhess@users.sourceforge.net>, ds
35 Devices:
36 Status: works
37 Updated: Sat, 16 Mar 2002 17:34:48 -0800
38
39 This driver is mainly for testing purposes, but can also be used to
40 generate sample waveforms on systems that don't have data acquisition
41 hardware.
42
43 Configuration options:
44   [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
45   [1] - Period in microseconds for fake waveforms (default 0.1 sec)
46
47 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
48 waveforms could be added to other channels (currently they return flatline
49 zero volts).
50
51 */
52
53 #include "../comedidev.h"
54
55 #include <asm/div64.h>
56
57 #include "comedi_fc.h"
58 #include <linux/timer.h>
59
60 /* Board descriptions */
61 struct waveform_board {
62         const char *name;
63         int ai_chans;
64         int ai_bits;
65         int have_dio;
66 };
67
68 #define N_CHANS 8
69
70 /* Data unique to this driver */
71 struct waveform_private {
72         struct timer_list timer;
73         struct timeval last;    /* time at which last timer interrupt occurred */
74         unsigned int uvolt_amplitude;   /* waveform amplitude in microvolts */
75         unsigned long usec_period;      /* waveform period in microseconds */
76         unsigned long usec_current;     /* current time (modulo waveform period) */
77         unsigned long usec_remainder;   /* usec since last scan; */
78         unsigned long ai_count; /* number of conversions remaining */
79         unsigned int scan_period;       /* scan period in usec */
80         unsigned int convert_period;    /* conversion period in usec */
81         unsigned timer_running:1;
82         unsigned int ao_loopbacks[N_CHANS];
83 };
84 #define devpriv ((struct waveform_private *)dev->private)
85
86 /* 1000 nanosec in a microsec */
87 static const int nano_per_micro = 1000;
88
89 /* fake analog input ranges */
90 static const struct comedi_lrange waveform_ai_ranges = {
91         2,
92         {
93          BIP_RANGE(10),
94          BIP_RANGE(5),
95          }
96 };
97
98 static short fake_sawtooth(struct comedi_device *dev, unsigned int range_index,
99                            unsigned long current_time)
100 {
101         struct comedi_subdevice *s = dev->read_subdev;
102         unsigned int offset = s->maxdata / 2;
103         u64 value;
104         const struct comedi_krange *krange =
105             &s->range_table->range[range_index];
106         u64 binary_amplitude;
107
108         binary_amplitude = s->maxdata;
109         binary_amplitude *= devpriv->uvolt_amplitude;
110         do_div(binary_amplitude, krange->max - krange->min);
111
112         current_time %= devpriv->usec_period;
113         value = current_time;
114         value *= binary_amplitude * 2;
115         do_div(value, devpriv->usec_period);
116         value -= binary_amplitude;      /* get rid of sawtooth's dc offset */
117
118         return offset + value;
119 }
120
121 static short fake_squarewave(struct comedi_device *dev,
122                              unsigned int range_index,
123                              unsigned long current_time)
124 {
125         struct comedi_subdevice *s = dev->read_subdev;
126         unsigned int offset = s->maxdata / 2;
127         u64 value;
128         const struct comedi_krange *krange =
129             &s->range_table->range[range_index];
130         current_time %= devpriv->usec_period;
131
132         value = s->maxdata;
133         value *= devpriv->uvolt_amplitude;
134         do_div(value, krange->max - krange->min);
135
136         if (current_time < devpriv->usec_period / 2)
137                 value *= -1;
138
139         return offset + value;
140 }
141
142 static short fake_flatline(struct comedi_device *dev, unsigned int range_index,
143                            unsigned long current_time)
144 {
145         return dev->read_subdev->maxdata / 2;
146 }
147
148 /* generates a different waveform depending on what channel is read */
149 static short fake_waveform(struct comedi_device *dev, unsigned int channel,
150                            unsigned int range, unsigned long current_time)
151 {
152         enum {
153                 SAWTOOTH_CHAN,
154                 SQUARE_CHAN,
155         };
156         switch (channel) {
157         case SAWTOOTH_CHAN:
158                 return fake_sawtooth(dev, range, current_time);
159                 break;
160         case SQUARE_CHAN:
161                 return fake_squarewave(dev, range, current_time);
162                 break;
163         default:
164                 break;
165         }
166
167         return fake_flatline(dev, range, current_time);
168 }
169
170 /*
171    This is the background routine used to generate arbitrary data.
172    It should run in the background; therefore it is scheduled by
173    a timer mechanism.
174 */
175 static void waveform_ai_interrupt(unsigned long arg)
176 {
177         struct comedi_device *dev = (struct comedi_device *)arg;
178         struct comedi_async *async = dev->read_subdev->async;
179         struct comedi_cmd *cmd = &async->cmd;
180         unsigned int i, j;
181         /* all times in microsec */
182         unsigned long elapsed_time;
183         unsigned int num_scans;
184         struct timeval now;
185
186         do_gettimeofday(&now);
187
188         elapsed_time =
189             1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
190             devpriv->last.tv_usec;
191         devpriv->last = now;
192         num_scans =
193             (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
194         devpriv->usec_remainder =
195             (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
196         async->events = 0;
197
198         for (i = 0; i < num_scans; i++) {
199                 for (j = 0; j < cmd->chanlist_len; j++) {
200                         cfc_write_to_buffer(dev->read_subdev,
201                                             fake_waveform(dev,
202                                                           CR_CHAN(cmd->
203                                                                   chanlist[j]),
204                                                           CR_RANGE(cmd->
205                                                                    chanlist[j]),
206                                                           devpriv->
207                                                           usec_current +
208                                                           i *
209                                                           devpriv->scan_period +
210                                                           j *
211                                                           devpriv->
212                                                           convert_period));
213                 }
214                 devpriv->ai_count++;
215                 if (cmd->stop_src == TRIG_COUNT
216                     && devpriv->ai_count >= cmd->stop_arg) {
217                         async->events |= COMEDI_CB_EOA;
218                         break;
219                 }
220         }
221
222         devpriv->usec_current += elapsed_time;
223         devpriv->usec_current %= devpriv->usec_period;
224
225         if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
226                 mod_timer(&devpriv->timer, jiffies + 1);
227         else
228                 del_timer(&devpriv->timer);
229
230         comedi_event(dev, dev->read_subdev);
231 }
232
233 static int waveform_ai_cmdtest(struct comedi_device *dev,
234                                struct comedi_subdevice *s,
235                                struct comedi_cmd *cmd)
236 {
237         int err = 0;
238         int tmp;
239
240         /* step 1: make sure trigger sources are trivially valid */
241
242         tmp = cmd->start_src;
243         cmd->start_src &= TRIG_NOW;
244         if (!cmd->start_src || tmp != cmd->start_src)
245                 err++;
246
247         tmp = cmd->scan_begin_src;
248         cmd->scan_begin_src &= TRIG_TIMER;
249         if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
250                 err++;
251
252         tmp = cmd->convert_src;
253         cmd->convert_src &= TRIG_NOW | TRIG_TIMER;
254         if (!cmd->convert_src || tmp != cmd->convert_src)
255                 err++;
256
257         tmp = cmd->scan_end_src;
258         cmd->scan_end_src &= TRIG_COUNT;
259         if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
260                 err++;
261
262         tmp = cmd->stop_src;
263         cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
264         if (!cmd->stop_src || tmp != cmd->stop_src)
265                 err++;
266
267         if (err)
268                 return 1;
269
270         /*
271          * step 2: make sure trigger sources are unique and mutually compatible
272          */
273
274         if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER)
275                 err++;
276         if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
277                 err++;
278
279         if (err)
280                 return 2;
281
282         /* step 3: make sure arguments are trivially compatible */
283
284         if (cmd->start_arg != 0) {
285                 cmd->start_arg = 0;
286                 err++;
287         }
288         if (cmd->convert_src == TRIG_NOW) {
289                 if (cmd->convert_arg != 0) {
290                         cmd->convert_arg = 0;
291                         err++;
292                 }
293         }
294         if (cmd->scan_begin_src == TRIG_TIMER) {
295                 if (cmd->scan_begin_arg < nano_per_micro) {
296                         cmd->scan_begin_arg = nano_per_micro;
297                         err++;
298                 }
299                 if (cmd->convert_src == TRIG_TIMER &&
300                     cmd->scan_begin_arg <
301                     cmd->convert_arg * cmd->chanlist_len) {
302                         cmd->scan_begin_arg =
303                             cmd->convert_arg * cmd->chanlist_len;
304                         err++;
305                 }
306         }
307         /*
308          * XXX these checks are generic and should go in core if not there
309          * already
310          */
311         if (!cmd->chanlist_len) {
312                 cmd->chanlist_len = 1;
313                 err++;
314         }
315         if (cmd->scan_end_arg != cmd->chanlist_len) {
316                 cmd->scan_end_arg = cmd->chanlist_len;
317                 err++;
318         }
319
320         if (cmd->stop_src == TRIG_COUNT) {
321                 if (!cmd->stop_arg) {
322                         cmd->stop_arg = 1;
323                         err++;
324                 }
325         } else {                /* TRIG_NONE */
326                 if (cmd->stop_arg != 0) {
327                         cmd->stop_arg = 0;
328                         err++;
329                 }
330         }
331
332         if (err)
333                 return 3;
334
335         /* step 4: fix up any arguments */
336
337         if (cmd->scan_begin_src == TRIG_TIMER) {
338                 tmp = cmd->scan_begin_arg;
339                 /* round to nearest microsec */
340                 cmd->scan_begin_arg =
341                     nano_per_micro * ((tmp +
342                                        (nano_per_micro / 2)) / nano_per_micro);
343                 if (tmp != cmd->scan_begin_arg)
344                         err++;
345         }
346         if (cmd->convert_src == TRIG_TIMER) {
347                 tmp = cmd->convert_arg;
348                 /* round to nearest microsec */
349                 cmd->convert_arg =
350                     nano_per_micro * ((tmp +
351                                        (nano_per_micro / 2)) / nano_per_micro);
352                 if (tmp != cmd->convert_arg)
353                         err++;
354         }
355
356         if (err)
357                 return 4;
358
359         return 0;
360 }
361
362 static int waveform_ai_cmd(struct comedi_device *dev,
363                            struct comedi_subdevice *s)
364 {
365         struct comedi_cmd *cmd = &s->async->cmd;
366
367         if (cmd->flags & TRIG_RT) {
368                 comedi_error(dev,
369                              "commands at RT priority not supported in this driver");
370                 return -1;
371         }
372
373         devpriv->timer_running = 1;
374         devpriv->ai_count = 0;
375         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
376
377         if (cmd->convert_src == TRIG_NOW)
378                 devpriv->convert_period = 0;
379         else if (cmd->convert_src == TRIG_TIMER)
380                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
381         else {
382                 comedi_error(dev, "bug setting conversion period");
383                 return -1;
384         }
385
386         do_gettimeofday(&devpriv->last);
387         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
388         devpriv->usec_remainder = 0;
389
390         devpriv->timer.expires = jiffies + 1;
391         add_timer(&devpriv->timer);
392         return 0;
393 }
394
395 static int waveform_ai_cancel(struct comedi_device *dev,
396                               struct comedi_subdevice *s)
397 {
398         devpriv->timer_running = 0;
399         del_timer(&devpriv->timer);
400         return 0;
401 }
402
403 static int waveform_ai_insn_read(struct comedi_device *dev,
404                                  struct comedi_subdevice *s,
405                                  struct comedi_insn *insn, unsigned int *data)
406 {
407         int i, chan = CR_CHAN(insn->chanspec);
408
409         for (i = 0; i < insn->n; i++)
410                 data[i] = devpriv->ao_loopbacks[chan];
411
412         return insn->n;
413 }
414
415 static int waveform_ao_insn_write(struct comedi_device *dev,
416                                   struct comedi_subdevice *s,
417                                   struct comedi_insn *insn, unsigned int *data)
418 {
419         int i, chan = CR_CHAN(insn->chanspec);
420
421         for (i = 0; i < insn->n; i++)
422                 devpriv->ao_loopbacks[chan] = data[i];
423
424         return insn->n;
425 }
426
427 static int waveform_attach(struct comedi_device *dev,
428                            struct comedi_devconfig *it)
429 {
430         const struct waveform_board *board = comedi_board(dev);
431         struct comedi_subdevice *s;
432         int amplitude = it->options[0];
433         int period = it->options[1];
434         int i;
435
436         dev->board_name = board->name;
437
438         if (alloc_private(dev, sizeof(struct waveform_private)) < 0)
439                 return -ENOMEM;
440
441         /* set default amplitude and period */
442         if (amplitude <= 0)
443                 amplitude = 1000000;    /* 1 volt */
444         if (period <= 0)
445                 period = 100000;        /* 0.1 sec */
446
447         devpriv->uvolt_amplitude = amplitude;
448         devpriv->usec_period = period;
449
450         dev->n_subdevices = 2;
451         if (alloc_subdevices(dev, dev->n_subdevices) < 0)
452                 return -ENOMEM;
453
454         s = dev->subdevices + 0;
455         dev->read_subdev = s;
456         /* analog input subdevice */
457         s->type = COMEDI_SUBD_AI;
458         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
459         s->n_chan = board->ai_chans;
460         s->maxdata = (1 << board->ai_bits) - 1;
461         s->range_table = &waveform_ai_ranges;
462         s->len_chanlist = s->n_chan * 2;
463         s->insn_read = waveform_ai_insn_read;
464         s->do_cmd = waveform_ai_cmd;
465         s->do_cmdtest = waveform_ai_cmdtest;
466         s->cancel = waveform_ai_cancel;
467
468         s = dev->subdevices + 1;
469         dev->write_subdev = s;
470         /* analog output subdevice (loopback) */
471         s->type = COMEDI_SUBD_AO;
472         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
473         s->n_chan = board->ai_chans;
474         s->maxdata = (1 << board->ai_bits) - 1;
475         s->range_table = &waveform_ai_ranges;
476         s->len_chanlist = s->n_chan * 2;
477         s->insn_write = waveform_ao_insn_write;
478         s->do_cmd = NULL;
479         s->do_cmdtest = NULL;
480         s->cancel = NULL;
481
482         /* Our default loopback value is just a 0V flatline */
483         for (i = 0; i < s->n_chan; i++)
484                 devpriv->ao_loopbacks[i] = s->maxdata / 2;
485
486         init_timer(&(devpriv->timer));
487         devpriv->timer.function = waveform_ai_interrupt;
488         devpriv->timer.data = (unsigned long)dev;
489
490         printk(KERN_INFO "comedi%d: comedi_test: "
491                "%i microvolt, %li microsecond waveform attached\n", dev->minor,
492                devpriv->uvolt_amplitude, devpriv->usec_period);
493         return 1;
494 }
495
496 static void waveform_detach(struct comedi_device *dev)
497 {
498         if (dev->private)
499                 waveform_ai_cancel(dev, dev->read_subdev);
500 }
501
502 static const struct waveform_board waveform_boards[] = {
503         {
504                 .name           = "comedi_test",
505                 .ai_chans       = N_CHANS,
506                 .ai_bits        = 16,
507                 .have_dio       = 0,
508         },
509 };
510
511 static struct comedi_driver waveform_driver = {
512         .driver_name    = "comedi_test",
513         .module         = THIS_MODULE,
514         .attach         = waveform_attach,
515         .detach         = waveform_detach,
516         .board_name     = &waveform_boards[0].name,
517         .offset         = sizeof(struct waveform_board),
518         .num_names      = ARRAY_SIZE(waveform_boards),
519 };
520 module_comedi_driver(waveform_driver);
521
522 MODULE_AUTHOR("Comedi http://www.comedi.org");
523 MODULE_DESCRIPTION("Comedi low-level driver");
524 MODULE_LICENSE("GPL");