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