]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/staging/comedi/drivers/comedi_test.c
Staging: comedi: remove typedefs from comedi_test.c
[linux-2.6-omap-h63xx.git] / 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
59 /* Board descriptions */
60 struct waveform_board {
61         const char *name;
62         int ai_chans;
63         int ai_bits;
64         int have_dio;
65 };
66
67 #define N_CHANS 8
68
69 static const struct waveform_board waveform_boards[] = {
70         {
71                 .name =         "comedi_test",
72                 .ai_chans =     N_CHANS,
73                 .ai_bits =      16,
74                 .have_dio =     0,
75         },
76 };
77
78 #define thisboard ((const struct waveform_board *)dev->board_ptr)
79
80 /* Data unique to this driver */
81 struct waveform_private {
82         struct timer_list timer;
83         struct timeval last;    /* time at which last timer interrupt occured */
84         unsigned int uvolt_amplitude;   /* waveform amplitude in microvolts */
85         unsigned long usec_period;      /* waveform period in microseconds */
86         unsigned long usec_current; /* current time (modulo waveform period) */
87         unsigned long usec_remainder;   /* usec since last scan; */
88         unsigned long ai_count; /* number of conversions remaining */
89         unsigned int scan_period;       /* scan period in usec */
90         unsigned int convert_period;    /* conversion period in usec */
91         unsigned timer_running:1;
92         lsampl_t ao_loopbacks[N_CHANS];
93 };
94 #define devpriv ((struct waveform_private *)dev->private)
95
96 static int waveform_attach(comedi_device *dev, comedi_devconfig *it);
97 static int waveform_detach(comedi_device *dev);
98 static comedi_driver driver_waveform = {
99       .driver_name =    "comedi_test",
100       .module =         THIS_MODULE,
101       .attach =         waveform_attach,
102       .detach =         waveform_detach,
103       .board_name =     &waveform_boards[0].name,
104       .offset =         sizeof(struct waveform_board),
105       .num_names =      sizeof(waveform_boards) / sizeof(struct waveform_board),
106 };
107
108 COMEDI_INITCLEANUP(driver_waveform);
109
110 static int waveform_ai_cmdtest(comedi_device *dev, comedi_subdevice *s,
111                                comedi_cmd *cmd);
112 static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s);
113 static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s);
114 static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s,
115                                  comedi_insn *insn, lsampl_t *data);
116 static int waveform_ao_insn_write(comedi_device *dev, comedi_subdevice *s,
117                                   comedi_insn *insn, lsampl_t *data);
118 static sampl_t fake_sawtooth(comedi_device *dev, unsigned int range,
119                              unsigned long current_time);
120 static sampl_t fake_squarewave(comedi_device *dev, unsigned int range,
121                                unsigned long current_time);
122 static sampl_t fake_flatline(comedi_device *dev, unsigned int range,
123                              unsigned long current_time);
124 static sampl_t fake_waveform(comedi_device *dev, unsigned int channel,
125                              unsigned int range, unsigned long current_time);
126
127 /* 1000 nanosec in a microsec */
128 static const int nano_per_micro = 1000;
129
130 /* fake analog input ranges */
131 static const comedi_lrange waveform_ai_ranges = {
132         2,
133         {
134                         BIP_RANGE(10),
135                         BIP_RANGE(5),
136                 }
137 };
138
139 /*
140    This is the background routine used to generate arbitrary data.
141    It should run in the background; therefore it is scheduled by
142    a timer mechanism.
143 */
144 static void waveform_ai_interrupt(unsigned long arg)
145 {
146         comedi_device *dev = (comedi_device *) arg;
147         comedi_async *async = dev->read_subdev->async;
148         comedi_cmd *cmd = &async->cmd;
149         unsigned int i, j;
150         /* all times in microsec */
151         unsigned long elapsed_time;
152         unsigned int num_scans;
153         struct timeval now;
154
155         do_gettimeofday(&now);
156
157         elapsed_time =
158                 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
159                 devpriv->last.tv_usec;
160         devpriv->last = now;
161         num_scans =
162                 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
163         devpriv->usec_remainder =
164                 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
165         async->events = 0;
166
167         for (i = 0; i < num_scans; i++) {
168                 for (j = 0; j < cmd->chanlist_len; j++) {
169                         cfc_write_to_buffer(dev->read_subdev,
170                                 fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
171                                         CR_RANGE(cmd->chanlist[j]),
172                                         devpriv->usec_current +
173                                         i * devpriv->scan_period +
174                                         j * devpriv->convert_period));
175                 }
176                 devpriv->ai_count++;
177                 if (cmd->stop_src == TRIG_COUNT
178                         && devpriv->ai_count >= cmd->stop_arg) {
179                         async->events |= COMEDI_CB_EOA;
180                         break;
181                 }
182         }
183
184         devpriv->usec_current += elapsed_time;
185         devpriv->usec_current %= devpriv->usec_period;
186
187         if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
188                 mod_timer(&devpriv->timer, jiffies + 1);
189         else
190                 del_timer(&devpriv->timer);
191
192         comedi_event(dev, dev->read_subdev);
193 }
194
195 static int waveform_attach(comedi_device *dev, comedi_devconfig *it)
196 {
197         comedi_subdevice *s;
198         int amplitude = it->options[0];
199         int period = it->options[1];
200         int i;
201
202         dev->board_name = thisboard->name;
203
204         if (alloc_private(dev, sizeof(struct waveform_private)) < 0)
205                 return -ENOMEM;
206
207         /* set default amplitude and period */
208         if (amplitude <= 0)
209                 amplitude = 1000000;    /* 1 volt */
210         if (period <= 0)
211                 period = 100000;        /* 0.1 sec */
212
213         devpriv->uvolt_amplitude = amplitude;
214         devpriv->usec_period = period;
215
216         dev->n_subdevices = 2;
217         if (alloc_subdevices(dev, dev->n_subdevices) < 0)
218                 return -ENOMEM;
219
220         s = dev->subdevices + 0;
221         dev->read_subdev = s;
222         /* analog input subdevice */
223         s->type = COMEDI_SUBD_AI;
224         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
225         s->n_chan = thisboard->ai_chans;
226         s->maxdata = (1 << thisboard->ai_bits) - 1;
227         s->range_table = &waveform_ai_ranges;
228         s->len_chanlist = s->n_chan * 2;
229         s->insn_read = waveform_ai_insn_read;
230         s->do_cmd = waveform_ai_cmd;
231         s->do_cmdtest = waveform_ai_cmdtest;
232         s->cancel = waveform_ai_cancel;
233
234         s = dev->subdevices + 1;
235         dev->write_subdev = s;
236         /* analog output subdevice (loopback) */
237         s->type = COMEDI_SUBD_AO;
238         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
239         s->n_chan = thisboard->ai_chans;
240         s->maxdata = (1 << thisboard->ai_bits) - 1;
241         s->range_table = &waveform_ai_ranges;
242         s->len_chanlist = s->n_chan * 2;
243         s->insn_write = waveform_ao_insn_write;
244         s->do_cmd = NULL;
245         s->do_cmdtest = NULL;
246         s->cancel = NULL;
247
248         /* Our default loopback value is just a 0V flatline */
249         for (i = 0; i < s->n_chan; i++)
250                 devpriv->ao_loopbacks[i] = s->maxdata / 2;
251
252         init_timer(&(devpriv->timer));
253         devpriv->timer.function = waveform_ai_interrupt;
254         devpriv->timer.data = (unsigned long)dev;
255
256         printk(KERN_INFO "comedi%d: comedi_test: "
257                 "%i microvolt, %li microsecond waveform attached\n", dev->minor,
258                 devpriv->uvolt_amplitude, devpriv->usec_period);
259         return 1;
260 }
261
262 static int waveform_detach(comedi_device *dev)
263 {
264         printk("comedi%d: comedi_test: remove\n", dev->minor);
265
266         if (dev->private)
267                 waveform_ai_cancel(dev, dev->read_subdev);
268
269         return 0;
270 }
271
272 static int waveform_ai_cmdtest(comedi_device *dev, comedi_subdevice *s,
273                                comedi_cmd *cmd)
274 {
275         int err = 0;
276         int tmp;
277
278         /* step 1: make sure trigger sources are trivially valid */
279
280         tmp = cmd->start_src;
281         cmd->start_src &= TRIG_NOW;
282         if (!cmd->start_src || tmp != cmd->start_src)
283                 err++;
284
285         tmp = cmd->scan_begin_src;
286         cmd->scan_begin_src &= TRIG_TIMER;
287         if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
288                 err++;
289
290         tmp = cmd->convert_src;
291         cmd->convert_src &= TRIG_NOW | TRIG_TIMER;
292         if (!cmd->convert_src || tmp != cmd->convert_src)
293                 err++;
294
295         tmp = cmd->scan_end_src;
296         cmd->scan_end_src &= TRIG_COUNT;
297         if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
298                 err++;
299
300         tmp = cmd->stop_src;
301         cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
302         if (!cmd->stop_src || tmp != cmd->stop_src)
303                 err++;
304
305         if (err)
306                 return 1;
307
308         /*
309          * step 2: make sure trigger sources are unique and mutually compatible
310          */
311
312         if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER)
313                 err++;
314         if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
315                 err++;
316
317         if (err)
318                 return 2;
319
320         /* step 3: make sure arguments are trivially compatible */
321
322         if (cmd->start_arg != 0) {
323                 cmd->start_arg = 0;
324                 err++;
325         }
326         if (cmd->convert_src == TRIG_NOW) {
327                 if (cmd->convert_arg != 0) {
328                         cmd->convert_arg = 0;
329                         err++;
330                 }
331         }
332         if (cmd->scan_begin_src == TRIG_TIMER) {
333                 if (cmd->scan_begin_arg < nano_per_micro) {
334                         cmd->scan_begin_arg = nano_per_micro;
335                         err++;
336                 }
337                 if (cmd->convert_src == TRIG_TIMER &&
338                         cmd->scan_begin_arg <
339                         cmd->convert_arg * cmd->chanlist_len) {
340                         cmd->scan_begin_arg =
341                                 cmd->convert_arg * cmd->chanlist_len;
342                         err++;
343                 }
344         }
345         /*
346          * XXX these checks are generic and should go in core if not there
347          * already
348          */
349         if (!cmd->chanlist_len) {
350                 cmd->chanlist_len = 1;
351                 err++;
352         }
353         if (cmd->scan_end_arg != cmd->chanlist_len) {
354                 cmd->scan_end_arg = cmd->chanlist_len;
355                 err++;
356         }
357
358         if (cmd->stop_src == TRIG_COUNT) {
359                 if (!cmd->stop_arg) {
360                         cmd->stop_arg = 1;
361                         err++;
362                 }
363         } else {                /* TRIG_NONE */
364                 if (cmd->stop_arg != 0) {
365                         cmd->stop_arg = 0;
366                         err++;
367                 }
368         }
369
370         if (err)
371                 return 3;
372
373         /* step 4: fix up any arguments */
374
375         if (cmd->scan_begin_src == TRIG_TIMER) {
376                 tmp = cmd->scan_begin_arg;
377                 /* round to nearest microsec */
378                 cmd->scan_begin_arg =
379                         nano_per_micro * ((tmp +
380                                 (nano_per_micro / 2)) / nano_per_micro);
381                 if (tmp != cmd->scan_begin_arg)
382                         err++;
383         }
384         if (cmd->convert_src == TRIG_TIMER) {
385                 tmp = cmd->convert_arg;
386                 /* round to nearest microsec */
387                 cmd->convert_arg =
388                         nano_per_micro * ((tmp +
389                                 (nano_per_micro / 2)) / nano_per_micro);
390                 if (tmp != cmd->convert_arg)
391                         err++;
392         }
393
394         if (err)
395                 return 4;
396
397         return 0;
398 }
399
400 static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s)
401 {
402         comedi_cmd *cmd = &s->async->cmd;
403
404         if (cmd->flags & TRIG_RT) {
405                 comedi_error(dev,
406                         "commands at RT priority not supported in this driver");
407                 return -1;
408         }
409
410         devpriv->timer_running = 1;
411         devpriv->ai_count = 0;
412         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
413
414         if (cmd->convert_src == TRIG_NOW)
415                 devpriv->convert_period = 0;
416         else if (cmd->convert_src == TRIG_TIMER)
417                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
418         else {
419                 comedi_error(dev, "bug setting conversion period");
420                 return -1;
421         }
422
423         do_gettimeofday(&devpriv->last);
424         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
425         devpriv->usec_remainder = 0;
426
427         devpriv->timer.expires = jiffies + 1;
428         add_timer(&devpriv->timer);
429         return 0;
430 }
431
432 static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s)
433 {
434         devpriv->timer_running = 0;
435         del_timer(&devpriv->timer);
436         return 0;
437 }
438
439 static sampl_t fake_sawtooth(comedi_device *dev, unsigned int range_index,
440                              unsigned long current_time)
441 {
442         comedi_subdevice *s = dev->read_subdev;
443         unsigned int offset = s->maxdata / 2;
444         u64 value;
445         const comedi_krange *krange = &s->range_table->range[range_index];
446         u64 binary_amplitude;
447
448         binary_amplitude = s->maxdata;
449         binary_amplitude *= devpriv->uvolt_amplitude;
450         do_div(binary_amplitude, krange->max - krange->min);
451
452         current_time %= devpriv->usec_period;
453         value = current_time;
454         value *= binary_amplitude * 2;
455         do_div(value, devpriv->usec_period);
456         value -= binary_amplitude;      /* get rid of sawtooth's dc offset */
457
458         return offset + value;
459 }
460 static sampl_t fake_squarewave(comedi_device *dev, unsigned int range_index,
461                                unsigned long current_time)
462 {
463         comedi_subdevice *s = dev->read_subdev;
464         unsigned int offset = s->maxdata / 2;
465         u64 value;
466         const comedi_krange *krange = &s->range_table->range[range_index];
467         current_time %= devpriv->usec_period;
468
469         value = s->maxdata;
470         value *= devpriv->uvolt_amplitude;
471         do_div(value, krange->max - krange->min);
472
473         if (current_time < devpriv->usec_period / 2)
474                 value *= -1;
475
476         return offset + value;
477 }
478
479 static sampl_t fake_flatline(comedi_device *dev, unsigned int range_index,
480                              unsigned long current_time)
481 {
482         return dev->read_subdev->maxdata / 2;
483 }
484
485 /* generates a different waveform depending on what channel is read */
486 static sampl_t fake_waveform(comedi_device *dev, unsigned int channel,
487                              unsigned int range, unsigned long current_time)
488 {
489         enum {
490                 SAWTOOTH_CHAN,
491                 SQUARE_CHAN,
492         };
493         switch (channel) {
494         case SAWTOOTH_CHAN:
495                 return fake_sawtooth(dev, range, current_time);
496                 break;
497         case SQUARE_CHAN:
498                 return fake_squarewave(dev, range, current_time);
499                 break;
500         default:
501                 break;
502         }
503
504         return fake_flatline(dev, range, current_time);
505 }
506
507 static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s,
508                                  comedi_insn *insn, lsampl_t *data)
509 {
510         int i, chan = CR_CHAN(insn->chanspec);
511
512         for (i = 0; i < insn->n; i++)
513                 data[i] = devpriv->ao_loopbacks[chan];
514
515         return insn->n;
516 }
517
518 static int waveform_ao_insn_write(comedi_device *dev, comedi_subdevice *s,
519                                   comedi_insn *insn, lsampl_t *data)
520 {
521         int i, chan = CR_CHAN(insn->chanspec);
522
523         for (i = 0; i < insn->n; i++)
524                 devpriv->ao_loopbacks[chan] = data[i];
525
526         return insn->n;
527 }