]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/comedi_test.c
ARM: delete struct sys_timer
[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     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 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: check if arguments are trivially valid */
256
257         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
258
259         if (cmd->convert_src == TRIG_NOW)
260                 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
261
262         if (cmd->scan_begin_src == TRIG_TIMER) {
263                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
264                                                  nano_per_micro);
265                 if (cmd->convert_src == TRIG_TIMER)
266                         err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
267                                         cmd->convert_arg * cmd->chanlist_len);
268         }
269
270         err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
271         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
272
273         if (cmd->stop_src == TRIG_COUNT)
274                 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
275         else    /* TRIG_NONE */
276                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
277
278         if (err)
279                 return 3;
280
281         /* step 4: fix up any arguments */
282
283         if (cmd->scan_begin_src == TRIG_TIMER) {
284                 tmp = cmd->scan_begin_arg;
285                 /* round to nearest microsec */
286                 cmd->scan_begin_arg =
287                     nano_per_micro * ((tmp +
288                                        (nano_per_micro / 2)) / nano_per_micro);
289                 if (tmp != cmd->scan_begin_arg)
290                         err++;
291         }
292         if (cmd->convert_src == TRIG_TIMER) {
293                 tmp = cmd->convert_arg;
294                 /* round to nearest microsec */
295                 cmd->convert_arg =
296                     nano_per_micro * ((tmp +
297                                        (nano_per_micro / 2)) / nano_per_micro);
298                 if (tmp != cmd->convert_arg)
299                         err++;
300         }
301
302         if (err)
303                 return 4;
304
305         return 0;
306 }
307
308 static int waveform_ai_cmd(struct comedi_device *dev,
309                            struct comedi_subdevice *s)
310 {
311         struct waveform_private *devpriv = dev->private;
312         struct comedi_cmd *cmd = &s->async->cmd;
313
314         if (cmd->flags & TRIG_RT) {
315                 comedi_error(dev,
316                              "commands at RT priority not supported in this driver");
317                 return -1;
318         }
319
320         devpriv->timer_running = 1;
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         devpriv->timer_running = 0;
348         del_timer(&devpriv->timer);
349         return 0;
350 }
351
352 static int waveform_ai_insn_read(struct comedi_device *dev,
353                                  struct comedi_subdevice *s,
354                                  struct comedi_insn *insn, unsigned int *data)
355 {
356         struct waveform_private *devpriv = dev->private;
357         int i, chan = CR_CHAN(insn->chanspec);
358
359         for (i = 0; i < insn->n; i++)
360                 data[i] = devpriv->ao_loopbacks[chan];
361
362         return insn->n;
363 }
364
365 static int waveform_ao_insn_write(struct comedi_device *dev,
366                                   struct comedi_subdevice *s,
367                                   struct comedi_insn *insn, unsigned int *data)
368 {
369         struct waveform_private *devpriv = dev->private;
370         int i, chan = CR_CHAN(insn->chanspec);
371
372         for (i = 0; i < insn->n; i++)
373                 devpriv->ao_loopbacks[chan] = data[i];
374
375         return insn->n;
376 }
377
378 static int waveform_attach(struct comedi_device *dev,
379                            struct comedi_devconfig *it)
380 {
381         struct waveform_private *devpriv;
382         struct comedi_subdevice *s;
383         int amplitude = it->options[0];
384         int period = it->options[1];
385         int i;
386         int ret;
387
388         dev->board_name = dev->driver->driver_name;
389
390         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
391         if (!devpriv)
392                 return -ENOMEM;
393         dev->private = devpriv;
394
395         /* set default amplitude and period */
396         if (amplitude <= 0)
397                 amplitude = 1000000;    /* 1 volt */
398         if (period <= 0)
399                 period = 100000;        /* 0.1 sec */
400
401         devpriv->uvolt_amplitude = amplitude;
402         devpriv->usec_period = period;
403
404         ret = comedi_alloc_subdevices(dev, 2);
405         if (ret)
406                 return ret;
407
408         s = &dev->subdevices[0];
409         dev->read_subdev = s;
410         /* analog input subdevice */
411         s->type = COMEDI_SUBD_AI;
412         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
413         s->n_chan = N_CHANS;
414         s->maxdata = 0xffff;
415         s->range_table = &waveform_ai_ranges;
416         s->len_chanlist = s->n_chan * 2;
417         s->insn_read = waveform_ai_insn_read;
418         s->do_cmd = waveform_ai_cmd;
419         s->do_cmdtest = waveform_ai_cmdtest;
420         s->cancel = waveform_ai_cancel;
421
422         s = &dev->subdevices[1];
423         dev->write_subdev = s;
424         /* analog output subdevice (loopback) */
425         s->type = COMEDI_SUBD_AO;
426         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
427         s->n_chan = N_CHANS;
428         s->maxdata = 0xffff;
429         s->range_table = &waveform_ai_ranges;
430         s->len_chanlist = s->n_chan * 2;
431         s->insn_write = waveform_ao_insn_write;
432         s->do_cmd = NULL;
433         s->do_cmdtest = NULL;
434         s->cancel = NULL;
435
436         /* Our default loopback value is just a 0V flatline */
437         for (i = 0; i < s->n_chan; i++)
438                 devpriv->ao_loopbacks[i] = s->maxdata / 2;
439
440         init_timer(&(devpriv->timer));
441         devpriv->timer.function = waveform_ai_interrupt;
442         devpriv->timer.data = (unsigned long)dev;
443
444         dev_info(dev->class_dev,
445                 "%s: %i microvolt, %li microsecond waveform attached\n",
446                 dev->board_name,
447                 devpriv->uvolt_amplitude, devpriv->usec_period);
448
449         return 0;
450 }
451
452 static void waveform_detach(struct comedi_device *dev)
453 {
454         struct waveform_private *devpriv = dev->private;
455
456         if (devpriv)
457                 waveform_ai_cancel(dev, dev->read_subdev);
458 }
459
460 static struct comedi_driver waveform_driver = {
461         .driver_name    = "comedi_test",
462         .module         = THIS_MODULE,
463         .attach         = waveform_attach,
464         .detach         = waveform_detach,
465 };
466 module_comedi_driver(waveform_driver);
467
468 MODULE_AUTHOR("Comedi http://www.comedi.org");
469 MODULE_DESCRIPTION("Comedi low-level driver");
470 MODULE_LICENSE("GPL");