]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/comedi_test.c
Merge branch 'next' of git://git.kernel.org/pub/scm/virt/kvm/kvm
[karo-tx-linux.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 /*
26 Driver: comedi_test
27 Description: generates fake waveforms
28 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
29   <fmhess@users.sourceforge.net>, ds
30 Devices:
31 Status: works
32 Updated: Sat, 16 Mar 2002 17:34:48 -0800
33
34 This driver is mainly for testing purposes, but can also be used to
35 generate sample waveforms on systems that don't have data acquisition
36 hardware.
37
38 Configuration options:
39   [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
40   [1] - Period in microseconds for fake waveforms (default 0.1 sec)
41
42 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
43 waveforms could be added to other channels (currently they return flatline
44 zero volts).
45
46 */
47
48 #include <linux/module.h>
49 #include "../comedidev.h"
50
51 #include <asm/div64.h>
52
53 #include "comedi_fc.h"
54 #include <linux/timer.h>
55
56 #define N_CHANS 8
57
58 /* Data unique to this driver */
59 struct waveform_private {
60         struct timer_list timer;
61         struct timeval last;            /* time last timer interrupt occurred */
62         unsigned int uvolt_amplitude;   /* waveform amplitude in microvolts */
63         unsigned long usec_period;      /* waveform period in microseconds */
64         unsigned long usec_current;     /* current time (mod waveform period) */
65         unsigned long usec_remainder;   /* usec since last scan */
66         unsigned long ai_count;         /* number of conversions remaining */
67         unsigned int scan_period;       /* scan period in usec */
68         unsigned int convert_period;    /* conversion period in usec */
69         unsigned int ao_loopbacks[N_CHANS];
70 };
71
72 /* 1000 nanosec in a microsec */
73 static const int nano_per_micro = 1000;
74
75 /* fake analog input ranges */
76 static const struct comedi_lrange waveform_ai_ranges = {
77         2,
78         {
79          BIP_RANGE(10),
80          BIP_RANGE(5),
81          }
82 };
83
84 static unsigned short fake_sawtooth(struct comedi_device *dev,
85                                     unsigned int range_index,
86                                     unsigned long current_time)
87 {
88         struct waveform_private *devpriv = dev->private;
89         struct comedi_subdevice *s = dev->read_subdev;
90         unsigned int offset = s->maxdata / 2;
91         u64 value;
92         const struct comedi_krange *krange =
93             &s->range_table->range[range_index];
94         u64 binary_amplitude;
95
96         binary_amplitude = s->maxdata;
97         binary_amplitude *= devpriv->uvolt_amplitude;
98         do_div(binary_amplitude, krange->max - krange->min);
99
100         current_time %= devpriv->usec_period;
101         value = current_time;
102         value *= binary_amplitude * 2;
103         do_div(value, devpriv->usec_period);
104         value -= binary_amplitude;      /* get rid of sawtooth's dc offset */
105
106         return offset + value;
107 }
108
109 static unsigned short fake_squarewave(struct comedi_device *dev,
110                                       unsigned int range_index,
111                                       unsigned long current_time)
112 {
113         struct waveform_private *devpriv = dev->private;
114         struct comedi_subdevice *s = dev->read_subdev;
115         unsigned int offset = s->maxdata / 2;
116         u64 value;
117         const struct comedi_krange *krange =
118             &s->range_table->range[range_index];
119         current_time %= devpriv->usec_period;
120
121         value = s->maxdata;
122         value *= devpriv->uvolt_amplitude;
123         do_div(value, krange->max - krange->min);
124
125         if (current_time < devpriv->usec_period / 2)
126                 value *= -1;
127
128         return offset + value;
129 }
130
131 static unsigned short fake_flatline(struct comedi_device *dev,
132                                     unsigned int range_index,
133                                     unsigned long current_time)
134 {
135         return dev->read_subdev->maxdata / 2;
136 }
137
138 /* generates a different waveform depending on what channel is read */
139 static unsigned short fake_waveform(struct comedi_device *dev,
140                                     unsigned int channel, unsigned int range,
141                                     unsigned long current_time)
142 {
143         enum {
144                 SAWTOOTH_CHAN,
145                 SQUARE_CHAN,
146         };
147         switch (channel) {
148         case SAWTOOTH_CHAN:
149                 return fake_sawtooth(dev, range, current_time);
150                 break;
151         case SQUARE_CHAN:
152                 return fake_squarewave(dev, range, current_time);
153                 break;
154         default:
155                 break;
156         }
157
158         return fake_flatline(dev, range, current_time);
159 }
160
161 /*
162    This is the background routine used to generate arbitrary data.
163    It should run in the background; therefore it is scheduled by
164    a timer mechanism.
165 */
166 static void waveform_ai_interrupt(unsigned long arg)
167 {
168         struct comedi_device *dev = (struct comedi_device *)arg;
169         struct waveform_private *devpriv = dev->private;
170         struct comedi_async *async = dev->read_subdev->async;
171         struct comedi_cmd *cmd = &async->cmd;
172         unsigned int i, j;
173         /* all times in microsec */
174         unsigned long elapsed_time;
175         unsigned int num_scans;
176         struct timeval now;
177         bool stopping = false;
178
179         do_gettimeofday(&now);
180
181         elapsed_time =
182             1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
183             devpriv->last.tv_usec;
184         devpriv->last = now;
185         num_scans =
186             (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
187         devpriv->usec_remainder =
188             (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
189         async->events = 0;
190
191         if (cmd->stop_src == TRIG_COUNT) {
192                 unsigned int remaining = cmd->stop_arg - devpriv->ai_count;
193                 if (num_scans >= remaining) {
194                         /* about to finish */
195                         num_scans = remaining;
196                         stopping = true;
197                 }
198         }
199
200         for (i = 0; i < num_scans; i++) {
201                 for (j = 0; j < cmd->chanlist_len; j++) {
202                         unsigned short sample;
203                         sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
204                                                CR_RANGE(cmd->chanlist[j]),
205                                                devpriv->usec_current +
206                                                    i * devpriv->scan_period +
207                                                    j * devpriv->convert_period);
208                         cfc_write_to_buffer(dev->read_subdev, sample);
209                 }
210         }
211
212         devpriv->ai_count += i;
213         devpriv->usec_current += elapsed_time;
214         devpriv->usec_current %= devpriv->usec_period;
215
216         if (stopping)
217                 async->events |= COMEDI_CB_EOA;
218         else
219                 mod_timer(&devpriv->timer, jiffies + 1);
220
221         comedi_event(dev, dev->read_subdev);
222 }
223
224 static int waveform_ai_cmdtest(struct comedi_device *dev,
225                                struct comedi_subdevice *s,
226                                struct comedi_cmd *cmd)
227 {
228         int err = 0;
229         int tmp;
230
231         /* Step 1 : check if triggers are trivially valid */
232
233         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
234         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
235         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER);
236         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
237         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
238
239         if (err)
240                 return 1;
241
242         /* Step 2a : make sure trigger sources are unique */
243
244         err |= cfc_check_trigger_is_unique(cmd->convert_src);
245         err |= cfc_check_trigger_is_unique(cmd->stop_src);
246
247         /* Step 2b : and mutually compatible */
248
249         if (err)
250                 return 2;
251
252         /* Step 3: check if arguments are trivially valid */
253
254         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
255
256         if (cmd->convert_src == TRIG_NOW)
257                 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
258
259         if (cmd->scan_begin_src == TRIG_TIMER) {
260                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
261                                                  nano_per_micro);
262                 if (cmd->convert_src == TRIG_TIMER)
263                         err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
264                                         cmd->convert_arg * cmd->chanlist_len);
265         }
266
267         err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
268         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
269
270         if (cmd->stop_src == TRIG_COUNT)
271                 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
272         else    /* TRIG_NONE */
273                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
274
275         if (err)
276                 return 3;
277
278         /* step 4: fix up any arguments */
279
280         if (cmd->scan_begin_src == TRIG_TIMER) {
281                 tmp = cmd->scan_begin_arg;
282                 /* round to nearest microsec */
283                 cmd->scan_begin_arg =
284                     nano_per_micro * ((tmp +
285                                        (nano_per_micro / 2)) / nano_per_micro);
286                 if (tmp != cmd->scan_begin_arg)
287                         err++;
288         }
289         if (cmd->convert_src == TRIG_TIMER) {
290                 tmp = cmd->convert_arg;
291                 /* round to nearest microsec */
292                 cmd->convert_arg =
293                     nano_per_micro * ((tmp +
294                                        (nano_per_micro / 2)) / nano_per_micro);
295                 if (tmp != cmd->convert_arg)
296                         err++;
297         }
298
299         if (err)
300                 return 4;
301
302         return 0;
303 }
304
305 static int waveform_ai_cmd(struct comedi_device *dev,
306                            struct comedi_subdevice *s)
307 {
308         struct waveform_private *devpriv = dev->private;
309         struct comedi_cmd *cmd = &s->async->cmd;
310
311         if (cmd->flags & TRIG_RT) {
312                 comedi_error(dev,
313                              "commands at RT priority not supported in this driver");
314                 return -1;
315         }
316
317         devpriv->ai_count = 0;
318         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
319
320         if (cmd->convert_src == TRIG_NOW)
321                 devpriv->convert_period = 0;
322         else if (cmd->convert_src == TRIG_TIMER)
323                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
324         else {
325                 comedi_error(dev, "bug setting conversion period");
326                 return -1;
327         }
328
329         do_gettimeofday(&devpriv->last);
330         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
331         devpriv->usec_remainder = 0;
332
333         devpriv->timer.expires = jiffies + 1;
334         add_timer(&devpriv->timer);
335         return 0;
336 }
337
338 static int waveform_ai_cancel(struct comedi_device *dev,
339                               struct comedi_subdevice *s)
340 {
341         struct waveform_private *devpriv = dev->private;
342
343         del_timer_sync(&devpriv->timer);
344         return 0;
345 }
346
347 static int waveform_ai_insn_read(struct comedi_device *dev,
348                                  struct comedi_subdevice *s,
349                                  struct comedi_insn *insn, unsigned int *data)
350 {
351         struct waveform_private *devpriv = dev->private;
352         int i, chan = CR_CHAN(insn->chanspec);
353
354         for (i = 0; i < insn->n; i++)
355                 data[i] = devpriv->ao_loopbacks[chan];
356
357         return insn->n;
358 }
359
360 static int waveform_ao_insn_write(struct comedi_device *dev,
361                                   struct comedi_subdevice *s,
362                                   struct comedi_insn *insn, unsigned int *data)
363 {
364         struct waveform_private *devpriv = dev->private;
365         int i, chan = CR_CHAN(insn->chanspec);
366
367         for (i = 0; i < insn->n; i++)
368                 devpriv->ao_loopbacks[chan] = data[i];
369
370         return insn->n;
371 }
372
373 static int waveform_attach(struct comedi_device *dev,
374                            struct comedi_devconfig *it)
375 {
376         struct waveform_private *devpriv;
377         struct comedi_subdevice *s;
378         int amplitude = it->options[0];
379         int period = it->options[1];
380         int i;
381         int ret;
382
383         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
384         if (!devpriv)
385                 return -ENOMEM;
386
387         /* set default amplitude and period */
388         if (amplitude <= 0)
389                 amplitude = 1000000;    /* 1 volt */
390         if (period <= 0)
391                 period = 100000;        /* 0.1 sec */
392
393         devpriv->uvolt_amplitude = amplitude;
394         devpriv->usec_period = period;
395
396         ret = comedi_alloc_subdevices(dev, 2);
397         if (ret)
398                 return ret;
399
400         s = &dev->subdevices[0];
401         dev->read_subdev = s;
402         /* analog input subdevice */
403         s->type = COMEDI_SUBD_AI;
404         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
405         s->n_chan = N_CHANS;
406         s->maxdata = 0xffff;
407         s->range_table = &waveform_ai_ranges;
408         s->len_chanlist = s->n_chan * 2;
409         s->insn_read = waveform_ai_insn_read;
410         s->do_cmd = waveform_ai_cmd;
411         s->do_cmdtest = waveform_ai_cmdtest;
412         s->cancel = waveform_ai_cancel;
413
414         s = &dev->subdevices[1];
415         dev->write_subdev = s;
416         /* analog output subdevice (loopback) */
417         s->type = COMEDI_SUBD_AO;
418         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
419         s->n_chan = N_CHANS;
420         s->maxdata = 0xffff;
421         s->range_table = &waveform_ai_ranges;
422         s->len_chanlist = s->n_chan * 2;
423         s->insn_write = waveform_ao_insn_write;
424         s->do_cmd = NULL;
425         s->do_cmdtest = NULL;
426         s->cancel = NULL;
427
428         /* Our default loopback value is just a 0V flatline */
429         for (i = 0; i < s->n_chan; i++)
430                 devpriv->ao_loopbacks[i] = s->maxdata / 2;
431
432         init_timer(&(devpriv->timer));
433         devpriv->timer.function = waveform_ai_interrupt;
434         devpriv->timer.data = (unsigned long)dev;
435
436         dev_info(dev->class_dev,
437                 "%s: %i microvolt, %li microsecond waveform attached\n",
438                 dev->board_name,
439                 devpriv->uvolt_amplitude, devpriv->usec_period);
440
441         return 0;
442 }
443
444 static void waveform_detach(struct comedi_device *dev)
445 {
446         struct waveform_private *devpriv = dev->private;
447
448         if (devpriv)
449                 waveform_ai_cancel(dev, dev->read_subdev);
450 }
451
452 static struct comedi_driver waveform_driver = {
453         .driver_name    = "comedi_test",
454         .module         = THIS_MODULE,
455         .attach         = waveform_attach,
456         .detach         = waveform_detach,
457 };
458 module_comedi_driver(waveform_driver);
459
460 MODULE_AUTHOR("Comedi http://www.comedi.org");
461 MODULE_DESCRIPTION("Comedi low-level driver");
462 MODULE_LICENSE("GPL");