]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/comedi_test.c
Merge remote-tracking branch 'spi/fix/s3c64xx' into spi-linus
[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 "../comedidev.h"
49
50 #include <asm/div64.h>
51
52 #include "comedi_fc.h"
53 #include <linux/timer.h>
54
55 #define N_CHANS 8
56
57 /* Data unique to this driver */
58 struct waveform_private {
59         struct timer_list timer;
60         struct timeval last;            /* time last timer interrupt occurred */
61         unsigned int uvolt_amplitude;   /* waveform amplitude in microvolts */
62         unsigned long usec_period;      /* waveform period in microseconds */
63         unsigned long usec_current;     /* current time (mod waveform period) */
64         unsigned long usec_remainder;   /* usec since last scan */
65         unsigned long ai_count;         /* number of conversions remaining */
66         unsigned int scan_period;       /* scan period in usec */
67         unsigned int convert_period;    /* conversion period in usec */
68         unsigned int ao_loopbacks[N_CHANS];
69 };
70
71 /* 1000 nanosec in a microsec */
72 static const int nano_per_micro = 1000;
73
74 /* fake analog input ranges */
75 static const struct comedi_lrange waveform_ai_ranges = {
76         2,
77         {
78          BIP_RANGE(10),
79          BIP_RANGE(5),
80          }
81 };
82
83 static unsigned short fake_sawtooth(struct comedi_device *dev,
84                                     unsigned int range_index,
85                                     unsigned long current_time)
86 {
87         struct waveform_private *devpriv = dev->private;
88         struct comedi_subdevice *s = dev->read_subdev;
89         unsigned int offset = s->maxdata / 2;
90         u64 value;
91         const struct comedi_krange *krange =
92             &s->range_table->range[range_index];
93         u64 binary_amplitude;
94
95         binary_amplitude = s->maxdata;
96         binary_amplitude *= devpriv->uvolt_amplitude;
97         do_div(binary_amplitude, krange->max - krange->min);
98
99         current_time %= devpriv->usec_period;
100         value = current_time;
101         value *= binary_amplitude * 2;
102         do_div(value, devpriv->usec_period);
103         value -= binary_amplitude;      /* get rid of sawtooth's dc offset */
104
105         return offset + value;
106 }
107
108 static unsigned short fake_squarewave(struct comedi_device *dev,
109                                       unsigned int range_index,
110                                       unsigned long current_time)
111 {
112         struct waveform_private *devpriv = dev->private;
113         struct comedi_subdevice *s = dev->read_subdev;
114         unsigned int offset = s->maxdata / 2;
115         u64 value;
116         const struct comedi_krange *krange =
117             &s->range_table->range[range_index];
118         current_time %= devpriv->usec_period;
119
120         value = s->maxdata;
121         value *= devpriv->uvolt_amplitude;
122         do_div(value, krange->max - krange->min);
123
124         if (current_time < devpriv->usec_period / 2)
125                 value *= -1;
126
127         return offset + value;
128 }
129
130 static unsigned short fake_flatline(struct comedi_device *dev,
131                                     unsigned int range_index,
132                                     unsigned long current_time)
133 {
134         return dev->read_subdev->maxdata / 2;
135 }
136
137 /* generates a different waveform depending on what channel is read */
138 static unsigned short fake_waveform(struct comedi_device *dev,
139                                     unsigned int channel, unsigned int range,
140                                     unsigned long current_time)
141 {
142         enum {
143                 SAWTOOTH_CHAN,
144                 SQUARE_CHAN,
145         };
146         switch (channel) {
147         case SAWTOOTH_CHAN:
148                 return fake_sawtooth(dev, range, current_time);
149                 break;
150         case SQUARE_CHAN:
151                 return fake_squarewave(dev, range, current_time);
152                 break;
153         default:
154                 break;
155         }
156
157         return fake_flatline(dev, range, current_time);
158 }
159
160 /*
161    This is the background routine used to generate arbitrary data.
162    It should run in the background; therefore it is scheduled by
163    a timer mechanism.
164 */
165 static void waveform_ai_interrupt(unsigned long arg)
166 {
167         struct comedi_device *dev = (struct comedi_device *)arg;
168         struct waveform_private *devpriv = dev->private;
169         struct comedi_async *async = dev->read_subdev->async;
170         struct comedi_cmd *cmd = &async->cmd;
171         unsigned int i, j;
172         /* all times in microsec */
173         unsigned long elapsed_time;
174         unsigned int num_scans;
175         struct timeval now;
176         bool stopping = false;
177
178         do_gettimeofday(&now);
179
180         elapsed_time =
181             1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
182             devpriv->last.tv_usec;
183         devpriv->last = now;
184         num_scans =
185             (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
186         devpriv->usec_remainder =
187             (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
188         async->events = 0;
189
190         if (cmd->stop_src == TRIG_COUNT) {
191                 unsigned int remaining = cmd->stop_arg - devpriv->ai_count;
192                 if (num_scans >= remaining) {
193                         /* about to finish */
194                         num_scans = remaining;
195                         stopping = true;
196                 }
197         }
198
199         for (i = 0; i < num_scans; i++) {
200                 for (j = 0; j < cmd->chanlist_len; j++) {
201                         unsigned short sample;
202                         sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
203                                                CR_RANGE(cmd->chanlist[j]),
204                                                devpriv->usec_current +
205                                                    i * devpriv->scan_period +
206                                                    j * devpriv->convert_period);
207                         cfc_write_to_buffer(dev->read_subdev, sample);
208                 }
209         }
210
211         devpriv->ai_count += i;
212         devpriv->usec_current += elapsed_time;
213         devpriv->usec_current %= devpriv->usec_period;
214
215         if (stopping)
216                 async->events |= COMEDI_CB_EOA;
217         else
218                 mod_timer(&devpriv->timer, jiffies + 1);
219
220         comedi_event(dev, dev->read_subdev);
221 }
222
223 static int waveform_ai_cmdtest(struct comedi_device *dev,
224                                struct comedi_subdevice *s,
225                                struct comedi_cmd *cmd)
226 {
227         int err = 0;
228         int tmp;
229
230         /* Step 1 : check if triggers are trivially valid */
231
232         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
233         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
234         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER);
235         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
236         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
237
238         if (err)
239                 return 1;
240
241         /* Step 2a : make sure trigger sources are unique */
242
243         err |= cfc_check_trigger_is_unique(cmd->convert_src);
244         err |= cfc_check_trigger_is_unique(cmd->stop_src);
245
246         /* Step 2b : and mutually compatible */
247
248         if (err)
249                 return 2;
250
251         /* Step 3: check if arguments are trivially valid */
252
253         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
254
255         if (cmd->convert_src == TRIG_NOW)
256                 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
257
258         if (cmd->scan_begin_src == TRIG_TIMER) {
259                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
260                                                  nano_per_micro);
261                 if (cmd->convert_src == TRIG_TIMER)
262                         err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
263                                         cmd->convert_arg * cmd->chanlist_len);
264         }
265
266         err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
267         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
268
269         if (cmd->stop_src == TRIG_COUNT)
270                 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
271         else    /* TRIG_NONE */
272                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
273
274         if (err)
275                 return 3;
276
277         /* step 4: fix up any arguments */
278
279         if (cmd->scan_begin_src == TRIG_TIMER) {
280                 tmp = cmd->scan_begin_arg;
281                 /* round to nearest microsec */
282                 cmd->scan_begin_arg =
283                     nano_per_micro * ((tmp +
284                                        (nano_per_micro / 2)) / nano_per_micro);
285                 if (tmp != cmd->scan_begin_arg)
286                         err++;
287         }
288         if (cmd->convert_src == TRIG_TIMER) {
289                 tmp = cmd->convert_arg;
290                 /* round to nearest microsec */
291                 cmd->convert_arg =
292                     nano_per_micro * ((tmp +
293                                        (nano_per_micro / 2)) / nano_per_micro);
294                 if (tmp != cmd->convert_arg)
295                         err++;
296         }
297
298         if (err)
299                 return 4;
300
301         return 0;
302 }
303
304 static int waveform_ai_cmd(struct comedi_device *dev,
305                            struct comedi_subdevice *s)
306 {
307         struct waveform_private *devpriv = dev->private;
308         struct comedi_cmd *cmd = &s->async->cmd;
309
310         if (cmd->flags & TRIG_RT) {
311                 comedi_error(dev,
312                              "commands at RT priority not supported in this driver");
313                 return -1;
314         }
315
316         devpriv->ai_count = 0;
317         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
318
319         if (cmd->convert_src == TRIG_NOW)
320                 devpriv->convert_period = 0;
321         else if (cmd->convert_src == TRIG_TIMER)
322                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
323         else {
324                 comedi_error(dev, "bug setting conversion period");
325                 return -1;
326         }
327
328         do_gettimeofday(&devpriv->last);
329         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
330         devpriv->usec_remainder = 0;
331
332         devpriv->timer.expires = jiffies + 1;
333         add_timer(&devpriv->timer);
334         return 0;
335 }
336
337 static int waveform_ai_cancel(struct comedi_device *dev,
338                               struct comedi_subdevice *s)
339 {
340         struct waveform_private *devpriv = dev->private;
341
342         del_timer_sync(&devpriv->timer);
343         return 0;
344 }
345
346 static int waveform_ai_insn_read(struct comedi_device *dev,
347                                  struct comedi_subdevice *s,
348                                  struct comedi_insn *insn, unsigned int *data)
349 {
350         struct waveform_private *devpriv = dev->private;
351         int i, chan = CR_CHAN(insn->chanspec);
352
353         for (i = 0; i < insn->n; i++)
354                 data[i] = devpriv->ao_loopbacks[chan];
355
356         return insn->n;
357 }
358
359 static int waveform_ao_insn_write(struct comedi_device *dev,
360                                   struct comedi_subdevice *s,
361                                   struct comedi_insn *insn, unsigned int *data)
362 {
363         struct waveform_private *devpriv = dev->private;
364         int i, chan = CR_CHAN(insn->chanspec);
365
366         for (i = 0; i < insn->n; i++)
367                 devpriv->ao_loopbacks[chan] = data[i];
368
369         return insn->n;
370 }
371
372 static int waveform_attach(struct comedi_device *dev,
373                            struct comedi_devconfig *it)
374 {
375         struct waveform_private *devpriv;
376         struct comedi_subdevice *s;
377         int amplitude = it->options[0];
378         int period = it->options[1];
379         int i;
380         int ret;
381
382         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
383         if (!devpriv)
384                 return -ENOMEM;
385         dev->private = devpriv;
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");