Linux-libre 3.10.70-gnu
[librecmc/linux-libre.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 #include <linux/timer.h>
59
60 #define N_CHANS 8
61
62 /* Data unique to this driver */
63 struct waveform_private {
64         struct timer_list timer;
65         struct timeval last;            /* time 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 (mod 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 int ao_loopbacks[N_CHANS];
74 };
75
76 /* 1000 nanosec in a microsec */
77 static const int nano_per_micro = 1000;
78
79 /* fake analog input ranges */
80 static const struct comedi_lrange waveform_ai_ranges = {
81         2,
82         {
83          BIP_RANGE(10),
84          BIP_RANGE(5),
85          }
86 };
87
88 static unsigned short fake_sawtooth(struct comedi_device *dev,
89                                     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 unsigned 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 unsigned short fake_flatline(struct comedi_device *dev,
136                                     unsigned int range_index,
137                                     unsigned long current_time)
138 {
139         return dev->read_subdev->maxdata / 2;
140 }
141
142 /* generates a different waveform depending on what channel is read */
143 static unsigned short fake_waveform(struct comedi_device *dev,
144                                     unsigned int channel, unsigned int range,
145                                     unsigned long current_time)
146 {
147         enum {
148                 SAWTOOTH_CHAN,
149                 SQUARE_CHAN,
150         };
151         switch (channel) {
152         case SAWTOOTH_CHAN:
153                 return fake_sawtooth(dev, range, current_time);
154                 break;
155         case SQUARE_CHAN:
156                 return fake_squarewave(dev, range, current_time);
157                 break;
158         default:
159                 break;
160         }
161
162         return fake_flatline(dev, range, current_time);
163 }
164
165 /*
166    This is the background routine used to generate arbitrary data.
167    It should run in the background; therefore it is scheduled by
168    a timer mechanism.
169 */
170 static void waveform_ai_interrupt(unsigned long arg)
171 {
172         struct comedi_device *dev = (struct comedi_device *)arg;
173         struct waveform_private *devpriv = dev->private;
174         struct comedi_async *async = dev->read_subdev->async;
175         struct comedi_cmd *cmd = &async->cmd;
176         unsigned int i, j;
177         /* all times in microsec */
178         unsigned long elapsed_time;
179         unsigned int num_scans;
180         struct timeval now;
181         bool stopping = false;
182
183         do_gettimeofday(&now);
184
185         elapsed_time =
186             1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
187             devpriv->last.tv_usec;
188         devpriv->last = now;
189         num_scans =
190             (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
191         devpriv->usec_remainder =
192             (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
193         async->events = 0;
194
195         if (cmd->stop_src == TRIG_COUNT) {
196                 unsigned int remaining = cmd->stop_arg - devpriv->ai_count;
197                 if (num_scans >= remaining) {
198                         /* about to finish */
199                         num_scans = remaining;
200                         stopping = true;
201                 }
202         }
203
204         for (i = 0; i < num_scans; i++) {
205                 for (j = 0; j < cmd->chanlist_len; j++) {
206                         unsigned short sample;
207                         sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
208                                                CR_RANGE(cmd->chanlist[j]),
209                                                devpriv->usec_current +
210                                                    i * devpriv->scan_period +
211                                                    j * devpriv->convert_period);
212                         cfc_write_to_buffer(dev->read_subdev, sample);
213                 }
214         }
215
216         devpriv->ai_count += i;
217         devpriv->usec_current += elapsed_time;
218         devpriv->usec_current %= devpriv->usec_period;
219
220         if (stopping)
221                 async->events |= COMEDI_CB_EOA;
222         else
223                 mod_timer(&devpriv->timer, jiffies + 1);
224
225         comedi_event(dev, dev->read_subdev);
226 }
227
228 static int waveform_ai_cmdtest(struct comedi_device *dev,
229                                struct comedi_subdevice *s,
230                                struct comedi_cmd *cmd)
231 {
232         int err = 0;
233         int tmp;
234
235         /* Step 1 : check if triggers are trivially valid */
236
237         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
238         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
239         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER);
240         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
241         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
242
243         if (err)
244                 return 1;
245
246         /* Step 2a : make sure trigger sources are unique */
247
248         err |= cfc_check_trigger_is_unique(cmd->convert_src);
249         err |= cfc_check_trigger_is_unique(cmd->stop_src);
250
251         /* Step 2b : and mutually compatible */
252
253         if (err)
254                 return 2;
255
256         /* Step 3: check if arguments are trivially valid */
257
258         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
259
260         if (cmd->convert_src == TRIG_NOW)
261                 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
262
263         if (cmd->scan_begin_src == TRIG_TIMER) {
264                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
265                                                  nano_per_micro);
266                 if (cmd->convert_src == TRIG_TIMER)
267                         err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
268                                         cmd->convert_arg * cmd->chanlist_len);
269         }
270
271         err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
272         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
273
274         if (cmd->stop_src == TRIG_COUNT)
275                 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
276         else    /* TRIG_NONE */
277                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
278
279         if (err)
280                 return 3;
281
282         /* step 4: fix up any arguments */
283
284         if (cmd->scan_begin_src == TRIG_TIMER) {
285                 tmp = cmd->scan_begin_arg;
286                 /* round to nearest microsec */
287                 cmd->scan_begin_arg =
288                     nano_per_micro * ((tmp +
289                                        (nano_per_micro / 2)) / nano_per_micro);
290                 if (tmp != cmd->scan_begin_arg)
291                         err++;
292         }
293         if (cmd->convert_src == TRIG_TIMER) {
294                 tmp = cmd->convert_arg;
295                 /* round to nearest microsec */
296                 cmd->convert_arg =
297                     nano_per_micro * ((tmp +
298                                        (nano_per_micro / 2)) / nano_per_micro);
299                 if (tmp != cmd->convert_arg)
300                         err++;
301         }
302
303         if (err)
304                 return 4;
305
306         return 0;
307 }
308
309 static int waveform_ai_cmd(struct comedi_device *dev,
310                            struct comedi_subdevice *s)
311 {
312         struct waveform_private *devpriv = dev->private;
313         struct comedi_cmd *cmd = &s->async->cmd;
314
315         if (cmd->flags & TRIG_RT) {
316                 comedi_error(dev,
317                              "commands at RT priority not supported in this driver");
318                 return -1;
319         }
320
321         devpriv->ai_count = 0;
322         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
323
324         if (cmd->convert_src == TRIG_NOW)
325                 devpriv->convert_period = 0;
326         else if (cmd->convert_src == TRIG_TIMER)
327                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
328         else {
329                 comedi_error(dev, "bug setting conversion period");
330                 return -1;
331         }
332
333         do_gettimeofday(&devpriv->last);
334         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
335         devpriv->usec_remainder = 0;
336
337         devpriv->timer.expires = jiffies + 1;
338         add_timer(&devpriv->timer);
339         return 0;
340 }
341
342 static int waveform_ai_cancel(struct comedi_device *dev,
343                               struct comedi_subdevice *s)
344 {
345         struct waveform_private *devpriv = dev->private;
346
347         del_timer_sync(&devpriv->timer);
348         return 0;
349 }
350
351 static int waveform_ai_insn_read(struct comedi_device *dev,
352                                  struct comedi_subdevice *s,
353                                  struct comedi_insn *insn, unsigned int *data)
354 {
355         struct waveform_private *devpriv = dev->private;
356         int i, chan = CR_CHAN(insn->chanspec);
357
358         for (i = 0; i < insn->n; i++)
359                 data[i] = devpriv->ao_loopbacks[chan];
360
361         return insn->n;
362 }
363
364 static int waveform_ao_insn_write(struct comedi_device *dev,
365                                   struct comedi_subdevice *s,
366                                   struct comedi_insn *insn, unsigned int *data)
367 {
368         struct waveform_private *devpriv = dev->private;
369         int i, chan = CR_CHAN(insn->chanspec);
370
371         for (i = 0; i < insn->n; i++)
372                 devpriv->ao_loopbacks[chan] = data[i];
373
374         return insn->n;
375 }
376
377 static int waveform_attach(struct comedi_device *dev,
378                            struct comedi_devconfig *it)
379 {
380         struct waveform_private *devpriv;
381         struct comedi_subdevice *s;
382         int amplitude = it->options[0];
383         int period = it->options[1];
384         int i;
385         int ret;
386
387         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
388         if (!devpriv)
389                 return -ENOMEM;
390         dev->private = devpriv;
391
392         /* set default amplitude and period */
393         if (amplitude <= 0)
394                 amplitude = 1000000;    /* 1 volt */
395         if (period <= 0)
396                 period = 100000;        /* 0.1 sec */
397
398         devpriv->uvolt_amplitude = amplitude;
399         devpriv->usec_period = period;
400
401         ret = comedi_alloc_subdevices(dev, 2);
402         if (ret)
403                 return ret;
404
405         s = &dev->subdevices[0];
406         dev->read_subdev = s;
407         /* analog input subdevice */
408         s->type = COMEDI_SUBD_AI;
409         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
410         s->n_chan = N_CHANS;
411         s->maxdata = 0xffff;
412         s->range_table = &waveform_ai_ranges;
413         s->len_chanlist = s->n_chan * 2;
414         s->insn_read = waveform_ai_insn_read;
415         s->do_cmd = waveform_ai_cmd;
416         s->do_cmdtest = waveform_ai_cmdtest;
417         s->cancel = waveform_ai_cancel;
418
419         s = &dev->subdevices[1];
420         dev->write_subdev = s;
421         /* analog output subdevice (loopback) */
422         s->type = COMEDI_SUBD_AO;
423         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
424         s->n_chan = N_CHANS;
425         s->maxdata = 0xffff;
426         s->range_table = &waveform_ai_ranges;
427         s->len_chanlist = s->n_chan * 2;
428         s->insn_write = waveform_ao_insn_write;
429         s->do_cmd = NULL;
430         s->do_cmdtest = NULL;
431         s->cancel = NULL;
432
433         /* Our default loopback value is just a 0V flatline */
434         for (i = 0; i < s->n_chan; i++)
435                 devpriv->ao_loopbacks[i] = s->maxdata / 2;
436
437         init_timer(&(devpriv->timer));
438         devpriv->timer.function = waveform_ai_interrupt;
439         devpriv->timer.data = (unsigned long)dev;
440
441         dev_info(dev->class_dev,
442                 "%s: %i microvolt, %li microsecond waveform attached\n",
443                 dev->board_name,
444                 devpriv->uvolt_amplitude, devpriv->usec_period);
445
446         return 0;
447 }
448
449 static void waveform_detach(struct comedi_device *dev)
450 {
451         struct waveform_private *devpriv = dev->private;
452
453         if (devpriv)
454                 waveform_ai_cancel(dev, dev->read_subdev);
455 }
456
457 static struct comedi_driver waveform_driver = {
458         .driver_name    = "comedi_test",
459         .module         = THIS_MODULE,
460         .attach         = waveform_attach,
461         .detach         = waveform_detach,
462 };
463 module_comedi_driver(waveform_driver);
464
465 MODULE_AUTHOR("Comedi http://www.comedi.org");
466 MODULE_DESCRIPTION("Comedi low-level driver");
467 MODULE_LICENSE("GPL");