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