18ffdff6aSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
28ffdff6aSGreg Kroah-Hartman /*
38ffdff6aSGreg Kroah-Hartman * comedi/drivers/comedi_test.c
48ffdff6aSGreg Kroah-Hartman *
58ffdff6aSGreg Kroah-Hartman * Generates fake waveform signals that can be read through
68ffdff6aSGreg Kroah-Hartman * the command interface. It does _not_ read from any board;
78ffdff6aSGreg Kroah-Hartman * it just generates deterministic waveforms.
88ffdff6aSGreg Kroah-Hartman * Useful for various testing purposes.
98ffdff6aSGreg Kroah-Hartman *
108ffdff6aSGreg Kroah-Hartman * Copyright (C) 2002 Joachim Wuttke <[email protected]>
118ffdff6aSGreg Kroah-Hartman * Copyright (C) 2002 Frank Mori Hess <[email protected]>
128ffdff6aSGreg Kroah-Hartman *
138ffdff6aSGreg Kroah-Hartman * COMEDI - Linux Control and Measurement Device Interface
148ffdff6aSGreg Kroah-Hartman * Copyright (C) 2000 David A. Schleef <[email protected]>
158ffdff6aSGreg Kroah-Hartman */
168ffdff6aSGreg Kroah-Hartman
178ffdff6aSGreg Kroah-Hartman /*
188ffdff6aSGreg Kroah-Hartman * Driver: comedi_test
198ffdff6aSGreg Kroah-Hartman * Description: generates fake waveforms
208ffdff6aSGreg Kroah-Hartman * Author: Joachim Wuttke <[email protected]>, Frank Mori Hess
218ffdff6aSGreg Kroah-Hartman * <[email protected]>, ds
228ffdff6aSGreg Kroah-Hartman * Devices:
238ffdff6aSGreg Kroah-Hartman * Status: works
248ffdff6aSGreg Kroah-Hartman * Updated: Sat, 16 Mar 2002 17:34:48 -0800
258ffdff6aSGreg Kroah-Hartman *
268ffdff6aSGreg Kroah-Hartman * This driver is mainly for testing purposes, but can also be used to
278ffdff6aSGreg Kroah-Hartman * generate sample waveforms on systems that don't have data acquisition
288ffdff6aSGreg Kroah-Hartman * hardware.
298ffdff6aSGreg Kroah-Hartman *
308ffdff6aSGreg Kroah-Hartman * Auto-configuration is the default mode if no parameter is supplied during
318ffdff6aSGreg Kroah-Hartman * module loading. Manual configuration requires COMEDI userspace tool.
328ffdff6aSGreg Kroah-Hartman * To disable auto-configuration mode, pass "noauto=1" parameter for module
338ffdff6aSGreg Kroah-Hartman * loading. Refer modinfo or MODULE_PARM_DESC description below for details.
348ffdff6aSGreg Kroah-Hartman *
358ffdff6aSGreg Kroah-Hartman * Auto-configuration options:
368ffdff6aSGreg Kroah-Hartman * Refer modinfo or MODULE_PARM_DESC description below for details.
378ffdff6aSGreg Kroah-Hartman *
388ffdff6aSGreg Kroah-Hartman * Manual configuration options:
398ffdff6aSGreg Kroah-Hartman * [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
408ffdff6aSGreg Kroah-Hartman * [1] - Period in microseconds for fake waveforms (default 0.1 sec)
418ffdff6aSGreg Kroah-Hartman *
428ffdff6aSGreg Kroah-Hartman * Generates a sawtooth wave on channel 0, square wave on channel 1, additional
438ffdff6aSGreg Kroah-Hartman * waveforms could be added to other channels (currently they return flatline
448ffdff6aSGreg Kroah-Hartman * zero volts).
458ffdff6aSGreg Kroah-Hartman */
468ffdff6aSGreg Kroah-Hartman
478ffdff6aSGreg Kroah-Hartman #include <linux/module.h>
48df0e68c1SIan Abbott #include <linux/comedi/comedidev.h>
498ffdff6aSGreg Kroah-Hartman #include <asm/div64.h>
508ffdff6aSGreg Kroah-Hartman #include <linux/timer.h>
518ffdff6aSGreg Kroah-Hartman #include <linux/ktime.h>
528ffdff6aSGreg Kroah-Hartman #include <linux/jiffies.h>
538ffdff6aSGreg Kroah-Hartman #include <linux/device.h>
548ffdff6aSGreg Kroah-Hartman #include <linux/kdev_t.h>
558ffdff6aSGreg Kroah-Hartman
568ffdff6aSGreg Kroah-Hartman #define N_CHANS 8
578ffdff6aSGreg Kroah-Hartman #define DEV_NAME "comedi_testd"
588ffdff6aSGreg Kroah-Hartman #define CLASS_NAME "comedi_test"
598ffdff6aSGreg Kroah-Hartman
608ffdff6aSGreg Kroah-Hartman static bool config_mode;
618ffdff6aSGreg Kroah-Hartman static unsigned int set_amplitude;
628ffdff6aSGreg Kroah-Hartman static unsigned int set_period;
633b7a628dSIvan Orlov static const struct class ctcls = {
643b7a628dSIvan Orlov .name = CLASS_NAME,
653b7a628dSIvan Orlov };
668ffdff6aSGreg Kroah-Hartman static struct device *ctdev;
678ffdff6aSGreg Kroah-Hartman
688ffdff6aSGreg Kroah-Hartman module_param_named(noauto, config_mode, bool, 0444);
698ffdff6aSGreg Kroah-Hartman MODULE_PARM_DESC(noauto, "Disable auto-configuration: (1=disable [defaults to enable])");
708ffdff6aSGreg Kroah-Hartman
718ffdff6aSGreg Kroah-Hartman module_param_named(amplitude, set_amplitude, uint, 0444);
728ffdff6aSGreg Kroah-Hartman MODULE_PARM_DESC(amplitude, "Set auto mode wave amplitude in microvolts: (defaults to 1 volt)");
738ffdff6aSGreg Kroah-Hartman
748ffdff6aSGreg Kroah-Hartman module_param_named(period, set_period, uint, 0444);
758ffdff6aSGreg Kroah-Hartman MODULE_PARM_DESC(period, "Set auto mode wave period in microseconds: (defaults to 0.1 sec)");
768ffdff6aSGreg Kroah-Hartman
778ffdff6aSGreg Kroah-Hartman /* Data unique to this driver */
788ffdff6aSGreg Kroah-Hartman struct waveform_private {
798ffdff6aSGreg Kroah-Hartman struct timer_list ai_timer; /* timer for AI commands */
808ffdff6aSGreg Kroah-Hartman u64 ai_convert_time; /* time of next AI conversion in usec */
818ffdff6aSGreg Kroah-Hartman unsigned int wf_amplitude; /* waveform amplitude in microvolts */
828ffdff6aSGreg Kroah-Hartman unsigned int wf_period; /* waveform period in microseconds */
838ffdff6aSGreg Kroah-Hartman unsigned int wf_current; /* current time in waveform period */
848ffdff6aSGreg Kroah-Hartman unsigned int ai_scan_period; /* AI scan period in usec */
858ffdff6aSGreg Kroah-Hartman unsigned int ai_convert_period; /* AI conversion period in usec */
868ffdff6aSGreg Kroah-Hartman struct timer_list ao_timer; /* timer for AO commands */
878ffdff6aSGreg Kroah-Hartman struct comedi_device *dev; /* parent comedi device */
888ffdff6aSGreg Kroah-Hartman u64 ao_last_scan_time; /* time of previous AO scan in usec */
898ffdff6aSGreg Kroah-Hartman unsigned int ao_scan_period; /* AO scan period in usec */
90f53641a6SIan Abbott bool ai_timer_enable:1; /* should AI timer be running? */
91f53641a6SIan Abbott bool ao_timer_enable:1; /* should AO timer be running? */
928ffdff6aSGreg Kroah-Hartman unsigned short ao_loopbacks[N_CHANS];
938ffdff6aSGreg Kroah-Hartman };
948ffdff6aSGreg Kroah-Hartman
958ffdff6aSGreg Kroah-Hartman /* fake analog input ranges */
968ffdff6aSGreg Kroah-Hartman static const struct comedi_lrange waveform_ai_ranges = {
978ffdff6aSGreg Kroah-Hartman 2, {
988ffdff6aSGreg Kroah-Hartman BIP_RANGE(10),
998ffdff6aSGreg Kroah-Hartman BIP_RANGE(5)
1008ffdff6aSGreg Kroah-Hartman }
1018ffdff6aSGreg Kroah-Hartman };
1028ffdff6aSGreg Kroah-Hartman
fake_sawtooth(struct comedi_device * dev,unsigned int range_index,unsigned int current_time)1038ffdff6aSGreg Kroah-Hartman static unsigned short fake_sawtooth(struct comedi_device *dev,
1048ffdff6aSGreg Kroah-Hartman unsigned int range_index,
1058ffdff6aSGreg Kroah-Hartman unsigned int current_time)
1068ffdff6aSGreg Kroah-Hartman {
1078ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
1088ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s = dev->read_subdev;
1098ffdff6aSGreg Kroah-Hartman unsigned int offset = s->maxdata / 2;
1108ffdff6aSGreg Kroah-Hartman u64 value;
1118ffdff6aSGreg Kroah-Hartman const struct comedi_krange *krange =
1128ffdff6aSGreg Kroah-Hartman &s->range_table->range[range_index];
1138ffdff6aSGreg Kroah-Hartman u64 binary_amplitude;
1148ffdff6aSGreg Kroah-Hartman
1158ffdff6aSGreg Kroah-Hartman binary_amplitude = s->maxdata;
1168ffdff6aSGreg Kroah-Hartman binary_amplitude *= devpriv->wf_amplitude;
1178ffdff6aSGreg Kroah-Hartman do_div(binary_amplitude, krange->max - krange->min);
1188ffdff6aSGreg Kroah-Hartman
1198ffdff6aSGreg Kroah-Hartman value = current_time;
1208ffdff6aSGreg Kroah-Hartman value *= binary_amplitude * 2;
1218ffdff6aSGreg Kroah-Hartman do_div(value, devpriv->wf_period);
1228ffdff6aSGreg Kroah-Hartman value += offset;
1238ffdff6aSGreg Kroah-Hartman /* get rid of sawtooth's dc offset and clamp value */
1248ffdff6aSGreg Kroah-Hartman if (value < binary_amplitude) {
1258ffdff6aSGreg Kroah-Hartman value = 0; /* negative saturation */
1268ffdff6aSGreg Kroah-Hartman } else {
1278ffdff6aSGreg Kroah-Hartman value -= binary_amplitude;
1288ffdff6aSGreg Kroah-Hartman if (value > s->maxdata)
1298ffdff6aSGreg Kroah-Hartman value = s->maxdata; /* positive saturation */
1308ffdff6aSGreg Kroah-Hartman }
1318ffdff6aSGreg Kroah-Hartman
1328ffdff6aSGreg Kroah-Hartman return value;
1338ffdff6aSGreg Kroah-Hartman }
1348ffdff6aSGreg Kroah-Hartman
fake_squarewave(struct comedi_device * dev,unsigned int range_index,unsigned int current_time)1358ffdff6aSGreg Kroah-Hartman static unsigned short fake_squarewave(struct comedi_device *dev,
1368ffdff6aSGreg Kroah-Hartman unsigned int range_index,
1378ffdff6aSGreg Kroah-Hartman unsigned int current_time)
1388ffdff6aSGreg Kroah-Hartman {
1398ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
1408ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s = dev->read_subdev;
1418ffdff6aSGreg Kroah-Hartman unsigned int offset = s->maxdata / 2;
1428ffdff6aSGreg Kroah-Hartman u64 value;
1438ffdff6aSGreg Kroah-Hartman const struct comedi_krange *krange =
1448ffdff6aSGreg Kroah-Hartman &s->range_table->range[range_index];
1458ffdff6aSGreg Kroah-Hartman
1468ffdff6aSGreg Kroah-Hartman value = s->maxdata;
1478ffdff6aSGreg Kroah-Hartman value *= devpriv->wf_amplitude;
1488ffdff6aSGreg Kroah-Hartman do_div(value, krange->max - krange->min);
1498ffdff6aSGreg Kroah-Hartman
1508ffdff6aSGreg Kroah-Hartman /* get one of two values for square-wave and clamp */
1518ffdff6aSGreg Kroah-Hartman if (current_time < devpriv->wf_period / 2) {
1528ffdff6aSGreg Kroah-Hartman if (offset < value)
1538ffdff6aSGreg Kroah-Hartman value = 0; /* negative saturation */
1548ffdff6aSGreg Kroah-Hartman else
1558ffdff6aSGreg Kroah-Hartman value = offset - value;
1568ffdff6aSGreg Kroah-Hartman } else {
1578ffdff6aSGreg Kroah-Hartman value += offset;
1588ffdff6aSGreg Kroah-Hartman if (value > s->maxdata)
1598ffdff6aSGreg Kroah-Hartman value = s->maxdata; /* positive saturation */
1608ffdff6aSGreg Kroah-Hartman }
1618ffdff6aSGreg Kroah-Hartman
1628ffdff6aSGreg Kroah-Hartman return value;
1638ffdff6aSGreg Kroah-Hartman }
1648ffdff6aSGreg Kroah-Hartman
fake_flatline(struct comedi_device * dev,unsigned int range_index,unsigned int current_time)1658ffdff6aSGreg Kroah-Hartman static unsigned short fake_flatline(struct comedi_device *dev,
1668ffdff6aSGreg Kroah-Hartman unsigned int range_index,
1678ffdff6aSGreg Kroah-Hartman unsigned int current_time)
1688ffdff6aSGreg Kroah-Hartman {
1698ffdff6aSGreg Kroah-Hartman return dev->read_subdev->maxdata / 2;
1708ffdff6aSGreg Kroah-Hartman }
1718ffdff6aSGreg Kroah-Hartman
1728ffdff6aSGreg Kroah-Hartman /* generates a different waveform depending on what channel is read */
fake_waveform(struct comedi_device * dev,unsigned int channel,unsigned int range,unsigned int current_time)1738ffdff6aSGreg Kroah-Hartman static unsigned short fake_waveform(struct comedi_device *dev,
1748ffdff6aSGreg Kroah-Hartman unsigned int channel, unsigned int range,
1758ffdff6aSGreg Kroah-Hartman unsigned int current_time)
1768ffdff6aSGreg Kroah-Hartman {
1778ffdff6aSGreg Kroah-Hartman enum {
1788ffdff6aSGreg Kroah-Hartman SAWTOOTH_CHAN,
1798ffdff6aSGreg Kroah-Hartman SQUARE_CHAN,
1808ffdff6aSGreg Kroah-Hartman };
1818ffdff6aSGreg Kroah-Hartman switch (channel) {
1828ffdff6aSGreg Kroah-Hartman case SAWTOOTH_CHAN:
1838ffdff6aSGreg Kroah-Hartman return fake_sawtooth(dev, range, current_time);
1848ffdff6aSGreg Kroah-Hartman case SQUARE_CHAN:
1858ffdff6aSGreg Kroah-Hartman return fake_squarewave(dev, range, current_time);
1868ffdff6aSGreg Kroah-Hartman default:
1878ffdff6aSGreg Kroah-Hartman break;
1888ffdff6aSGreg Kroah-Hartman }
1898ffdff6aSGreg Kroah-Hartman
1908ffdff6aSGreg Kroah-Hartman return fake_flatline(dev, range, current_time);
1918ffdff6aSGreg Kroah-Hartman }
1928ffdff6aSGreg Kroah-Hartman
1938ffdff6aSGreg Kroah-Hartman /*
1948ffdff6aSGreg Kroah-Hartman * This is the background routine used to generate arbitrary data.
1958ffdff6aSGreg Kroah-Hartman * It should run in the background; therefore it is scheduled by
1968ffdff6aSGreg Kroah-Hartman * a timer mechanism.
1978ffdff6aSGreg Kroah-Hartman */
waveform_ai_timer(struct timer_list * t)1988ffdff6aSGreg Kroah-Hartman static void waveform_ai_timer(struct timer_list *t)
1998ffdff6aSGreg Kroah-Hartman {
2008ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = from_timer(devpriv, t, ai_timer);
2018ffdff6aSGreg Kroah-Hartman struct comedi_device *dev = devpriv->dev;
2028ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s = dev->read_subdev;
2038ffdff6aSGreg Kroah-Hartman struct comedi_async *async = s->async;
2048ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd = &async->cmd;
2058ffdff6aSGreg Kroah-Hartman u64 now;
2068ffdff6aSGreg Kroah-Hartman unsigned int nsamples;
2078ffdff6aSGreg Kroah-Hartman unsigned int time_increment;
2088ffdff6aSGreg Kroah-Hartman
2098ffdff6aSGreg Kroah-Hartman now = ktime_to_us(ktime_get());
2108ffdff6aSGreg Kroah-Hartman nsamples = comedi_nsamples_left(s, UINT_MAX);
2118ffdff6aSGreg Kroah-Hartman
2128ffdff6aSGreg Kroah-Hartman while (nsamples && devpriv->ai_convert_time < now) {
2138ffdff6aSGreg Kroah-Hartman unsigned int chanspec = cmd->chanlist[async->cur_chan];
2148ffdff6aSGreg Kroah-Hartman unsigned short sample;
2158ffdff6aSGreg Kroah-Hartman
2168ffdff6aSGreg Kroah-Hartman sample = fake_waveform(dev, CR_CHAN(chanspec),
2178ffdff6aSGreg Kroah-Hartman CR_RANGE(chanspec), devpriv->wf_current);
2188ffdff6aSGreg Kroah-Hartman if (comedi_buf_write_samples(s, &sample, 1) == 0)
2198ffdff6aSGreg Kroah-Hartman goto overrun;
2208ffdff6aSGreg Kroah-Hartman time_increment = devpriv->ai_convert_period;
2218ffdff6aSGreg Kroah-Hartman if (async->scan_progress == 0) {
2228ffdff6aSGreg Kroah-Hartman /* done last conversion in scan, so add dead time */
2238ffdff6aSGreg Kroah-Hartman time_increment += devpriv->ai_scan_period -
2248ffdff6aSGreg Kroah-Hartman devpriv->ai_convert_period *
2258ffdff6aSGreg Kroah-Hartman cmd->scan_end_arg;
2268ffdff6aSGreg Kroah-Hartman }
2278ffdff6aSGreg Kroah-Hartman devpriv->wf_current += time_increment;
2288ffdff6aSGreg Kroah-Hartman if (devpriv->wf_current >= devpriv->wf_period)
2298ffdff6aSGreg Kroah-Hartman devpriv->wf_current %= devpriv->wf_period;
2308ffdff6aSGreg Kroah-Hartman devpriv->ai_convert_time += time_increment;
2318ffdff6aSGreg Kroah-Hartman nsamples--;
2328ffdff6aSGreg Kroah-Hartman }
2338ffdff6aSGreg Kroah-Hartman
2348ffdff6aSGreg Kroah-Hartman if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) {
2358ffdff6aSGreg Kroah-Hartman async->events |= COMEDI_CB_EOA;
2368ffdff6aSGreg Kroah-Hartman } else {
2378ffdff6aSGreg Kroah-Hartman if (devpriv->ai_convert_time > now)
2388ffdff6aSGreg Kroah-Hartman time_increment = devpriv->ai_convert_time - now;
2398ffdff6aSGreg Kroah-Hartman else
2408ffdff6aSGreg Kroah-Hartman time_increment = 1;
241f53641a6SIan Abbott spin_lock(&dev->spinlock);
242f53641a6SIan Abbott if (devpriv->ai_timer_enable) {
2438ffdff6aSGreg Kroah-Hartman mod_timer(&devpriv->ai_timer,
2448ffdff6aSGreg Kroah-Hartman jiffies + usecs_to_jiffies(time_increment));
2458ffdff6aSGreg Kroah-Hartman }
246f53641a6SIan Abbott spin_unlock(&dev->spinlock);
247f53641a6SIan Abbott }
2488ffdff6aSGreg Kroah-Hartman
2498ffdff6aSGreg Kroah-Hartman overrun:
2508ffdff6aSGreg Kroah-Hartman comedi_handle_events(dev, s);
2518ffdff6aSGreg Kroah-Hartman }
2528ffdff6aSGreg Kroah-Hartman
waveform_ai_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)2538ffdff6aSGreg Kroah-Hartman static int waveform_ai_cmdtest(struct comedi_device *dev,
2548ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
2558ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd)
2568ffdff6aSGreg Kroah-Hartman {
2578ffdff6aSGreg Kroah-Hartman int err = 0;
2588ffdff6aSGreg Kroah-Hartman unsigned int arg, limit;
2598ffdff6aSGreg Kroah-Hartman
2608ffdff6aSGreg Kroah-Hartman /* Step 1 : check if triggers are trivially valid */
2618ffdff6aSGreg Kroah-Hartman
2628ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
2638ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->scan_begin_src,
2648ffdff6aSGreg Kroah-Hartman TRIG_FOLLOW | TRIG_TIMER);
2658ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->convert_src,
2668ffdff6aSGreg Kroah-Hartman TRIG_NOW | TRIG_TIMER);
2678ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
2688ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
2698ffdff6aSGreg Kroah-Hartman
2708ffdff6aSGreg Kroah-Hartman if (err)
2718ffdff6aSGreg Kroah-Hartman return 1;
2728ffdff6aSGreg Kroah-Hartman
2738ffdff6aSGreg Kroah-Hartman /* Step 2a : make sure trigger sources are unique */
2748ffdff6aSGreg Kroah-Hartman
2758ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_is_unique(cmd->convert_src);
2768ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_is_unique(cmd->stop_src);
2778ffdff6aSGreg Kroah-Hartman
2788ffdff6aSGreg Kroah-Hartman /* Step 2b : and mutually compatible */
2798ffdff6aSGreg Kroah-Hartman
2808ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
2818ffdff6aSGreg Kroah-Hartman err |= -EINVAL; /* scan period would be 0 */
2828ffdff6aSGreg Kroah-Hartman
2838ffdff6aSGreg Kroah-Hartman if (err)
2848ffdff6aSGreg Kroah-Hartman return 2;
2858ffdff6aSGreg Kroah-Hartman
2868ffdff6aSGreg Kroah-Hartman /* Step 3: check if arguments are trivially valid */
2878ffdff6aSGreg Kroah-Hartman
2888ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
2898ffdff6aSGreg Kroah-Hartman
2908ffdff6aSGreg Kroah-Hartman if (cmd->convert_src == TRIG_NOW) {
2918ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
2928ffdff6aSGreg Kroah-Hartman } else { /* cmd->convert_src == TRIG_TIMER */
2938ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_FOLLOW) {
2948ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
2958ffdff6aSGreg Kroah-Hartman NSEC_PER_USEC);
2968ffdff6aSGreg Kroah-Hartman }
2978ffdff6aSGreg Kroah-Hartman }
2988ffdff6aSGreg Kroah-Hartman
2998ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_FOLLOW) {
3008ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
3018ffdff6aSGreg Kroah-Hartman } else { /* cmd->scan_begin_src == TRIG_TIMER */
3028ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
3038ffdff6aSGreg Kroah-Hartman NSEC_PER_USEC);
3048ffdff6aSGreg Kroah-Hartman }
3058ffdff6aSGreg Kroah-Hartman
3068ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
3078ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
3088ffdff6aSGreg Kroah-Hartman cmd->chanlist_len);
3098ffdff6aSGreg Kroah-Hartman
3108ffdff6aSGreg Kroah-Hartman if (cmd->stop_src == TRIG_COUNT)
3118ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
3128ffdff6aSGreg Kroah-Hartman else /* cmd->stop_src == TRIG_NONE */
3138ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
3148ffdff6aSGreg Kroah-Hartman
3158ffdff6aSGreg Kroah-Hartman if (err)
3168ffdff6aSGreg Kroah-Hartman return 3;
3178ffdff6aSGreg Kroah-Hartman
3188ffdff6aSGreg Kroah-Hartman /* step 4: fix up any arguments */
3198ffdff6aSGreg Kroah-Hartman
3208ffdff6aSGreg Kroah-Hartman if (cmd->convert_src == TRIG_TIMER) {
3218ffdff6aSGreg Kroah-Hartman /* round convert_arg to nearest microsecond */
3228ffdff6aSGreg Kroah-Hartman arg = cmd->convert_arg;
3238ffdff6aSGreg Kroah-Hartman arg = min(arg,
3248ffdff6aSGreg Kroah-Hartman rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC));
3258ffdff6aSGreg Kroah-Hartman arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC);
3268ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_arg == TRIG_TIMER) {
3278ffdff6aSGreg Kroah-Hartman /* limit convert_arg to keep scan_begin_arg in range */
3288ffdff6aSGreg Kroah-Hartman limit = UINT_MAX / cmd->scan_end_arg;
3298ffdff6aSGreg Kroah-Hartman limit = rounddown(limit, (unsigned int)NSEC_PER_SEC);
3308ffdff6aSGreg Kroah-Hartman arg = min(arg, limit);
3318ffdff6aSGreg Kroah-Hartman }
3328ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
3338ffdff6aSGreg Kroah-Hartman }
3348ffdff6aSGreg Kroah-Hartman
3358ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_TIMER) {
3368ffdff6aSGreg Kroah-Hartman /* round scan_begin_arg to nearest microsecond */
3378ffdff6aSGreg Kroah-Hartman arg = cmd->scan_begin_arg;
3388ffdff6aSGreg Kroah-Hartman arg = min(arg,
3398ffdff6aSGreg Kroah-Hartman rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC));
3408ffdff6aSGreg Kroah-Hartman arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC);
3418ffdff6aSGreg Kroah-Hartman if (cmd->convert_src == TRIG_TIMER) {
3428ffdff6aSGreg Kroah-Hartman /* but ensure scan_begin_arg is large enough */
3438ffdff6aSGreg Kroah-Hartman arg = max(arg, cmd->convert_arg * cmd->scan_end_arg);
3448ffdff6aSGreg Kroah-Hartman }
3458ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
3468ffdff6aSGreg Kroah-Hartman }
3478ffdff6aSGreg Kroah-Hartman
3488ffdff6aSGreg Kroah-Hartman if (err)
3498ffdff6aSGreg Kroah-Hartman return 4;
3508ffdff6aSGreg Kroah-Hartman
3518ffdff6aSGreg Kroah-Hartman return 0;
3528ffdff6aSGreg Kroah-Hartman }
3538ffdff6aSGreg Kroah-Hartman
waveform_ai_cmd(struct comedi_device * dev,struct comedi_subdevice * s)3548ffdff6aSGreg Kroah-Hartman static int waveform_ai_cmd(struct comedi_device *dev,
3558ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s)
3568ffdff6aSGreg Kroah-Hartman {
3578ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
3588ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd = &s->async->cmd;
3598ffdff6aSGreg Kroah-Hartman unsigned int first_convert_time;
3608ffdff6aSGreg Kroah-Hartman u64 wf_current;
3618ffdff6aSGreg Kroah-Hartman
3628ffdff6aSGreg Kroah-Hartman if (cmd->flags & CMDF_PRIORITY) {
3638ffdff6aSGreg Kroah-Hartman dev_err(dev->class_dev,
3648ffdff6aSGreg Kroah-Hartman "commands at RT priority not supported in this driver\n");
3658ffdff6aSGreg Kroah-Hartman return -1;
3668ffdff6aSGreg Kroah-Hartman }
3678ffdff6aSGreg Kroah-Hartman
3688ffdff6aSGreg Kroah-Hartman if (cmd->convert_src == TRIG_NOW)
3698ffdff6aSGreg Kroah-Hartman devpriv->ai_convert_period = 0;
3708ffdff6aSGreg Kroah-Hartman else /* cmd->convert_src == TRIG_TIMER */
3718ffdff6aSGreg Kroah-Hartman devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC;
3728ffdff6aSGreg Kroah-Hartman
3738ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_FOLLOW) {
3748ffdff6aSGreg Kroah-Hartman devpriv->ai_scan_period = devpriv->ai_convert_period *
3758ffdff6aSGreg Kroah-Hartman cmd->scan_end_arg;
3768ffdff6aSGreg Kroah-Hartman } else { /* cmd->scan_begin_src == TRIG_TIMER */
3778ffdff6aSGreg Kroah-Hartman devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC;
3788ffdff6aSGreg Kroah-Hartman }
3798ffdff6aSGreg Kroah-Hartman
3808ffdff6aSGreg Kroah-Hartman /*
3818ffdff6aSGreg Kroah-Hartman * Simulate first conversion to occur at convert period after
3828ffdff6aSGreg Kroah-Hartman * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume
3838ffdff6aSGreg Kroah-Hartman * the conversion timer starts immediately. If scan_begin_src is
3848ffdff6aSGreg Kroah-Hartman * TRIG_TIMER, assume the conversion timer starts after the scan
3858ffdff6aSGreg Kroah-Hartman * period.
3868ffdff6aSGreg Kroah-Hartman */
3878ffdff6aSGreg Kroah-Hartman first_convert_time = devpriv->ai_convert_period;
3888ffdff6aSGreg Kroah-Hartman if (cmd->scan_begin_src == TRIG_TIMER)
3898ffdff6aSGreg Kroah-Hartman first_convert_time += devpriv->ai_scan_period;
3908ffdff6aSGreg Kroah-Hartman devpriv->ai_convert_time = ktime_to_us(ktime_get()) +
3918ffdff6aSGreg Kroah-Hartman first_convert_time;
3928ffdff6aSGreg Kroah-Hartman
3938ffdff6aSGreg Kroah-Hartman /* Determine time within waveform period at time of conversion. */
3948ffdff6aSGreg Kroah-Hartman wf_current = devpriv->ai_convert_time;
3958ffdff6aSGreg Kroah-Hartman devpriv->wf_current = do_div(wf_current, devpriv->wf_period);
3968ffdff6aSGreg Kroah-Hartman
3978ffdff6aSGreg Kroah-Hartman /*
3988ffdff6aSGreg Kroah-Hartman * Schedule timer to expire just after first conversion time.
3998ffdff6aSGreg Kroah-Hartman * Seem to need an extra jiffy here, otherwise timer expires slightly
4008ffdff6aSGreg Kroah-Hartman * early!
4018ffdff6aSGreg Kroah-Hartman */
402f53641a6SIan Abbott spin_lock_bh(&dev->spinlock);
403f53641a6SIan Abbott devpriv->ai_timer_enable = true;
4048ffdff6aSGreg Kroah-Hartman devpriv->ai_timer.expires =
4058ffdff6aSGreg Kroah-Hartman jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1;
4068ffdff6aSGreg Kroah-Hartman add_timer(&devpriv->ai_timer);
407f53641a6SIan Abbott spin_unlock_bh(&dev->spinlock);
4088ffdff6aSGreg Kroah-Hartman return 0;
4098ffdff6aSGreg Kroah-Hartman }
4108ffdff6aSGreg Kroah-Hartman
waveform_ai_cancel(struct comedi_device * dev,struct comedi_subdevice * s)4118ffdff6aSGreg Kroah-Hartman static int waveform_ai_cancel(struct comedi_device *dev,
4128ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s)
4138ffdff6aSGreg Kroah-Hartman {
4148ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
4158ffdff6aSGreg Kroah-Hartman
416f53641a6SIan Abbott spin_lock_bh(&dev->spinlock);
417f53641a6SIan Abbott devpriv->ai_timer_enable = false;
418f53641a6SIan Abbott spin_unlock_bh(&dev->spinlock);
4198ffdff6aSGreg Kroah-Hartman if (in_softirq()) {
4208ffdff6aSGreg Kroah-Hartman /* Assume we were called from the timer routine itself. */
421*8fa7292fSThomas Gleixner timer_delete(&devpriv->ai_timer);
4228ffdff6aSGreg Kroah-Hartman } else {
423*8fa7292fSThomas Gleixner timer_delete_sync(&devpriv->ai_timer);
4248ffdff6aSGreg Kroah-Hartman }
4258ffdff6aSGreg Kroah-Hartman return 0;
4268ffdff6aSGreg Kroah-Hartman }
4278ffdff6aSGreg Kroah-Hartman
waveform_ai_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)4288ffdff6aSGreg Kroah-Hartman static int waveform_ai_insn_read(struct comedi_device *dev,
4298ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
4308ffdff6aSGreg Kroah-Hartman struct comedi_insn *insn, unsigned int *data)
4318ffdff6aSGreg Kroah-Hartman {
4328ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
4338ffdff6aSGreg Kroah-Hartman int i, chan = CR_CHAN(insn->chanspec);
4348ffdff6aSGreg Kroah-Hartman
4358ffdff6aSGreg Kroah-Hartman for (i = 0; i < insn->n; i++)
4368ffdff6aSGreg Kroah-Hartman data[i] = devpriv->ao_loopbacks[chan];
4378ffdff6aSGreg Kroah-Hartman
4388ffdff6aSGreg Kroah-Hartman return insn->n;
4398ffdff6aSGreg Kroah-Hartman }
4408ffdff6aSGreg Kroah-Hartman
4418ffdff6aSGreg Kroah-Hartman /*
4428ffdff6aSGreg Kroah-Hartman * This is the background routine to handle AO commands, scheduled by
4438ffdff6aSGreg Kroah-Hartman * a timer mechanism.
4448ffdff6aSGreg Kroah-Hartman */
waveform_ao_timer(struct timer_list * t)4458ffdff6aSGreg Kroah-Hartman static void waveform_ao_timer(struct timer_list *t)
4468ffdff6aSGreg Kroah-Hartman {
4478ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = from_timer(devpriv, t, ao_timer);
4488ffdff6aSGreg Kroah-Hartman struct comedi_device *dev = devpriv->dev;
4498ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s = dev->write_subdev;
4508ffdff6aSGreg Kroah-Hartman struct comedi_async *async = s->async;
4518ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd = &async->cmd;
4528ffdff6aSGreg Kroah-Hartman u64 now;
4538ffdff6aSGreg Kroah-Hartman u64 scans_since;
4548ffdff6aSGreg Kroah-Hartman unsigned int scans_avail = 0;
4558ffdff6aSGreg Kroah-Hartman
4568ffdff6aSGreg Kroah-Hartman /* determine number of scan periods since last time */
4578ffdff6aSGreg Kroah-Hartman now = ktime_to_us(ktime_get());
4588ffdff6aSGreg Kroah-Hartman scans_since = now - devpriv->ao_last_scan_time;
4598ffdff6aSGreg Kroah-Hartman do_div(scans_since, devpriv->ao_scan_period);
4608ffdff6aSGreg Kroah-Hartman if (scans_since) {
4618ffdff6aSGreg Kroah-Hartman unsigned int i;
4628ffdff6aSGreg Kroah-Hartman
4638ffdff6aSGreg Kroah-Hartman /* determine scans in buffer, limit to scans to do this time */
4648ffdff6aSGreg Kroah-Hartman scans_avail = comedi_nscans_left(s, 0);
4658ffdff6aSGreg Kroah-Hartman if (scans_avail > scans_since)
4668ffdff6aSGreg Kroah-Hartman scans_avail = scans_since;
4678ffdff6aSGreg Kroah-Hartman if (scans_avail) {
4688ffdff6aSGreg Kroah-Hartman /* skip all but the last scan to save processing time */
4698ffdff6aSGreg Kroah-Hartman if (scans_avail > 1) {
4708ffdff6aSGreg Kroah-Hartman unsigned int skip_bytes, nbytes;
4718ffdff6aSGreg Kroah-Hartman
4728ffdff6aSGreg Kroah-Hartman skip_bytes =
4738ffdff6aSGreg Kroah-Hartman comedi_samples_to_bytes(s, cmd->scan_end_arg *
4748ffdff6aSGreg Kroah-Hartman (scans_avail - 1));
4758ffdff6aSGreg Kroah-Hartman nbytes = comedi_buf_read_alloc(s, skip_bytes);
4768ffdff6aSGreg Kroah-Hartman comedi_buf_read_free(s, nbytes);
4778ffdff6aSGreg Kroah-Hartman comedi_inc_scan_progress(s, nbytes);
4788ffdff6aSGreg Kroah-Hartman if (nbytes < skip_bytes) {
4798ffdff6aSGreg Kroah-Hartman /* unexpected underrun! (cancelled?) */
4808ffdff6aSGreg Kroah-Hartman async->events |= COMEDI_CB_OVERFLOW;
4818ffdff6aSGreg Kroah-Hartman goto underrun;
4828ffdff6aSGreg Kroah-Hartman }
4838ffdff6aSGreg Kroah-Hartman }
4848ffdff6aSGreg Kroah-Hartman /* output the last scan */
4858ffdff6aSGreg Kroah-Hartman for (i = 0; i < cmd->scan_end_arg; i++) {
4868ffdff6aSGreg Kroah-Hartman unsigned int chan = CR_CHAN(cmd->chanlist[i]);
4878ffdff6aSGreg Kroah-Hartman unsigned short *pd;
4888ffdff6aSGreg Kroah-Hartman
4898ffdff6aSGreg Kroah-Hartman pd = &devpriv->ao_loopbacks[chan];
4908ffdff6aSGreg Kroah-Hartman
4918ffdff6aSGreg Kroah-Hartman if (!comedi_buf_read_samples(s, pd, 1)) {
4928ffdff6aSGreg Kroah-Hartman /* unexpected underrun! (cancelled?) */
4938ffdff6aSGreg Kroah-Hartman async->events |= COMEDI_CB_OVERFLOW;
4948ffdff6aSGreg Kroah-Hartman goto underrun;
4958ffdff6aSGreg Kroah-Hartman }
4968ffdff6aSGreg Kroah-Hartman }
4978ffdff6aSGreg Kroah-Hartman /* advance time of last scan */
4988ffdff6aSGreg Kroah-Hartman devpriv->ao_last_scan_time +=
4998ffdff6aSGreg Kroah-Hartman (u64)scans_avail * devpriv->ao_scan_period;
5008ffdff6aSGreg Kroah-Hartman }
5018ffdff6aSGreg Kroah-Hartman }
5028ffdff6aSGreg Kroah-Hartman if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) {
5038ffdff6aSGreg Kroah-Hartman async->events |= COMEDI_CB_EOA;
5048ffdff6aSGreg Kroah-Hartman } else if (scans_avail < scans_since) {
5058ffdff6aSGreg Kroah-Hartman async->events |= COMEDI_CB_OVERFLOW;
5068ffdff6aSGreg Kroah-Hartman } else {
5078ffdff6aSGreg Kroah-Hartman unsigned int time_inc = devpriv->ao_last_scan_time +
5088ffdff6aSGreg Kroah-Hartman devpriv->ao_scan_period - now;
5098ffdff6aSGreg Kroah-Hartman
510f53641a6SIan Abbott spin_lock(&dev->spinlock);
511f53641a6SIan Abbott if (devpriv->ao_timer_enable) {
5128ffdff6aSGreg Kroah-Hartman mod_timer(&devpriv->ao_timer,
5138ffdff6aSGreg Kroah-Hartman jiffies + usecs_to_jiffies(time_inc));
5148ffdff6aSGreg Kroah-Hartman }
515f53641a6SIan Abbott spin_unlock(&dev->spinlock);
516f53641a6SIan Abbott }
5178ffdff6aSGreg Kroah-Hartman
5188ffdff6aSGreg Kroah-Hartman underrun:
5198ffdff6aSGreg Kroah-Hartman comedi_handle_events(dev, s);
5208ffdff6aSGreg Kroah-Hartman }
5218ffdff6aSGreg Kroah-Hartman
waveform_ao_inttrig_start(struct comedi_device * dev,struct comedi_subdevice * s,unsigned int trig_num)5228ffdff6aSGreg Kroah-Hartman static int waveform_ao_inttrig_start(struct comedi_device *dev,
5238ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
5248ffdff6aSGreg Kroah-Hartman unsigned int trig_num)
5258ffdff6aSGreg Kroah-Hartman {
5268ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
5278ffdff6aSGreg Kroah-Hartman struct comedi_async *async = s->async;
5288ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd = &async->cmd;
5298ffdff6aSGreg Kroah-Hartman
5308ffdff6aSGreg Kroah-Hartman if (trig_num != cmd->start_arg)
5318ffdff6aSGreg Kroah-Hartman return -EINVAL;
5328ffdff6aSGreg Kroah-Hartman
5338ffdff6aSGreg Kroah-Hartman async->inttrig = NULL;
5348ffdff6aSGreg Kroah-Hartman
5358ffdff6aSGreg Kroah-Hartman devpriv->ao_last_scan_time = ktime_to_us(ktime_get());
536f53641a6SIan Abbott spin_lock_bh(&dev->spinlock);
537f53641a6SIan Abbott devpriv->ao_timer_enable = true;
5388ffdff6aSGreg Kroah-Hartman devpriv->ao_timer.expires =
5398ffdff6aSGreg Kroah-Hartman jiffies + usecs_to_jiffies(devpriv->ao_scan_period);
5408ffdff6aSGreg Kroah-Hartman add_timer(&devpriv->ao_timer);
541f53641a6SIan Abbott spin_unlock_bh(&dev->spinlock);
5428ffdff6aSGreg Kroah-Hartman
5438ffdff6aSGreg Kroah-Hartman return 1;
5448ffdff6aSGreg Kroah-Hartman }
5458ffdff6aSGreg Kroah-Hartman
waveform_ao_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)5468ffdff6aSGreg Kroah-Hartman static int waveform_ao_cmdtest(struct comedi_device *dev,
5478ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
5488ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd)
5498ffdff6aSGreg Kroah-Hartman {
5508ffdff6aSGreg Kroah-Hartman int err = 0;
5518ffdff6aSGreg Kroah-Hartman unsigned int arg;
5528ffdff6aSGreg Kroah-Hartman
5538ffdff6aSGreg Kroah-Hartman /* Step 1 : check if triggers are trivially valid */
5548ffdff6aSGreg Kroah-Hartman
5558ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
5568ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
5578ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
5588ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
5598ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
5608ffdff6aSGreg Kroah-Hartman
5618ffdff6aSGreg Kroah-Hartman if (err)
5628ffdff6aSGreg Kroah-Hartman return 1;
5638ffdff6aSGreg Kroah-Hartman
5648ffdff6aSGreg Kroah-Hartman /* Step 2a : make sure trigger sources are unique */
5658ffdff6aSGreg Kroah-Hartman
5668ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_is_unique(cmd->stop_src);
5678ffdff6aSGreg Kroah-Hartman
5688ffdff6aSGreg Kroah-Hartman /* Step 2b : and mutually compatible */
5698ffdff6aSGreg Kroah-Hartman
5708ffdff6aSGreg Kroah-Hartman if (err)
5718ffdff6aSGreg Kroah-Hartman return 2;
5728ffdff6aSGreg Kroah-Hartman
5738ffdff6aSGreg Kroah-Hartman /* Step 3: check if arguments are trivially valid */
5748ffdff6aSGreg Kroah-Hartman
5758ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
5768ffdff6aSGreg Kroah-Hartman NSEC_PER_USEC);
5778ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
5788ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
5798ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
5808ffdff6aSGreg Kroah-Hartman cmd->chanlist_len);
5818ffdff6aSGreg Kroah-Hartman if (cmd->stop_src == TRIG_COUNT)
5828ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
5838ffdff6aSGreg Kroah-Hartman else /* cmd->stop_src == TRIG_NONE */
5848ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
5858ffdff6aSGreg Kroah-Hartman
5868ffdff6aSGreg Kroah-Hartman if (err)
5878ffdff6aSGreg Kroah-Hartman return 3;
5888ffdff6aSGreg Kroah-Hartman
5898ffdff6aSGreg Kroah-Hartman /* step 4: fix up any arguments */
5908ffdff6aSGreg Kroah-Hartman
5918ffdff6aSGreg Kroah-Hartman /* round scan_begin_arg to nearest microsecond */
5928ffdff6aSGreg Kroah-Hartman arg = cmd->scan_begin_arg;
5938ffdff6aSGreg Kroah-Hartman arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC));
5948ffdff6aSGreg Kroah-Hartman arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC);
5958ffdff6aSGreg Kroah-Hartman err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
5968ffdff6aSGreg Kroah-Hartman
5978ffdff6aSGreg Kroah-Hartman if (err)
5988ffdff6aSGreg Kroah-Hartman return 4;
5998ffdff6aSGreg Kroah-Hartman
6008ffdff6aSGreg Kroah-Hartman return 0;
6018ffdff6aSGreg Kroah-Hartman }
6028ffdff6aSGreg Kroah-Hartman
waveform_ao_cmd(struct comedi_device * dev,struct comedi_subdevice * s)6038ffdff6aSGreg Kroah-Hartman static int waveform_ao_cmd(struct comedi_device *dev,
6048ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s)
6058ffdff6aSGreg Kroah-Hartman {
6068ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
6078ffdff6aSGreg Kroah-Hartman struct comedi_cmd *cmd = &s->async->cmd;
6088ffdff6aSGreg Kroah-Hartman
6098ffdff6aSGreg Kroah-Hartman if (cmd->flags & CMDF_PRIORITY) {
6108ffdff6aSGreg Kroah-Hartman dev_err(dev->class_dev,
6118ffdff6aSGreg Kroah-Hartman "commands at RT priority not supported in this driver\n");
6128ffdff6aSGreg Kroah-Hartman return -1;
6138ffdff6aSGreg Kroah-Hartman }
6148ffdff6aSGreg Kroah-Hartman
6158ffdff6aSGreg Kroah-Hartman devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC;
6168ffdff6aSGreg Kroah-Hartman s->async->inttrig = waveform_ao_inttrig_start;
6178ffdff6aSGreg Kroah-Hartman return 0;
6188ffdff6aSGreg Kroah-Hartman }
6198ffdff6aSGreg Kroah-Hartman
waveform_ao_cancel(struct comedi_device * dev,struct comedi_subdevice * s)6208ffdff6aSGreg Kroah-Hartman static int waveform_ao_cancel(struct comedi_device *dev,
6218ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s)
6228ffdff6aSGreg Kroah-Hartman {
6238ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
6248ffdff6aSGreg Kroah-Hartman
6258ffdff6aSGreg Kroah-Hartman s->async->inttrig = NULL;
626f53641a6SIan Abbott spin_lock_bh(&dev->spinlock);
627f53641a6SIan Abbott devpriv->ao_timer_enable = false;
628f53641a6SIan Abbott spin_unlock_bh(&dev->spinlock);
6298ffdff6aSGreg Kroah-Hartman if (in_softirq()) {
6308ffdff6aSGreg Kroah-Hartman /* Assume we were called from the timer routine itself. */
631*8fa7292fSThomas Gleixner timer_delete(&devpriv->ao_timer);
6328ffdff6aSGreg Kroah-Hartman } else {
633*8fa7292fSThomas Gleixner timer_delete_sync(&devpriv->ao_timer);
6348ffdff6aSGreg Kroah-Hartman }
6358ffdff6aSGreg Kroah-Hartman return 0;
6368ffdff6aSGreg Kroah-Hartman }
6378ffdff6aSGreg Kroah-Hartman
waveform_ao_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)6388ffdff6aSGreg Kroah-Hartman static int waveform_ao_insn_write(struct comedi_device *dev,
6398ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
6408ffdff6aSGreg Kroah-Hartman struct comedi_insn *insn, unsigned int *data)
6418ffdff6aSGreg Kroah-Hartman {
6428ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
6438ffdff6aSGreg Kroah-Hartman int i, chan = CR_CHAN(insn->chanspec);
6448ffdff6aSGreg Kroah-Hartman
6458ffdff6aSGreg Kroah-Hartman for (i = 0; i < insn->n; i++)
6468ffdff6aSGreg Kroah-Hartman devpriv->ao_loopbacks[chan] = data[i];
6478ffdff6aSGreg Kroah-Hartman
6488ffdff6aSGreg Kroah-Hartman return insn->n;
6498ffdff6aSGreg Kroah-Hartman }
6508ffdff6aSGreg Kroah-Hartman
waveform_ai_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)6518ffdff6aSGreg Kroah-Hartman static int waveform_ai_insn_config(struct comedi_device *dev,
6528ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
6538ffdff6aSGreg Kroah-Hartman struct comedi_insn *insn,
6548ffdff6aSGreg Kroah-Hartman unsigned int *data)
6558ffdff6aSGreg Kroah-Hartman {
6568ffdff6aSGreg Kroah-Hartman if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) {
6578ffdff6aSGreg Kroah-Hartman /*
6588ffdff6aSGreg Kroah-Hartman * input: data[1], data[2] : scan_begin_src, convert_src
6598ffdff6aSGreg Kroah-Hartman * output: data[1], data[2] : scan_begin_min, convert_min
6608ffdff6aSGreg Kroah-Hartman */
6618ffdff6aSGreg Kroah-Hartman if (data[1] == TRIG_FOLLOW) {
6628ffdff6aSGreg Kroah-Hartman /* exactly TRIG_FOLLOW case */
6638ffdff6aSGreg Kroah-Hartman data[1] = 0;
6648ffdff6aSGreg Kroah-Hartman data[2] = NSEC_PER_USEC;
6658ffdff6aSGreg Kroah-Hartman } else {
6668ffdff6aSGreg Kroah-Hartman data[1] = NSEC_PER_USEC;
6678ffdff6aSGreg Kroah-Hartman if (data[2] & TRIG_TIMER)
6688ffdff6aSGreg Kroah-Hartman data[2] = NSEC_PER_USEC;
6698ffdff6aSGreg Kroah-Hartman else
6708ffdff6aSGreg Kroah-Hartman data[2] = 0;
6718ffdff6aSGreg Kroah-Hartman }
6728ffdff6aSGreg Kroah-Hartman return 0;
6738ffdff6aSGreg Kroah-Hartman }
6748ffdff6aSGreg Kroah-Hartman
6758ffdff6aSGreg Kroah-Hartman return -EINVAL;
6768ffdff6aSGreg Kroah-Hartman }
6778ffdff6aSGreg Kroah-Hartman
waveform_ao_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)6788ffdff6aSGreg Kroah-Hartman static int waveform_ao_insn_config(struct comedi_device *dev,
6798ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s,
6808ffdff6aSGreg Kroah-Hartman struct comedi_insn *insn,
6818ffdff6aSGreg Kroah-Hartman unsigned int *data)
6828ffdff6aSGreg Kroah-Hartman {
6838ffdff6aSGreg Kroah-Hartman if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) {
6848ffdff6aSGreg Kroah-Hartman /* we don't care about actual channels */
6858ffdff6aSGreg Kroah-Hartman data[1] = NSEC_PER_USEC; /* scan_begin_min */
6868ffdff6aSGreg Kroah-Hartman data[2] = 0; /* convert_min */
6878ffdff6aSGreg Kroah-Hartman return 0;
6888ffdff6aSGreg Kroah-Hartman }
6898ffdff6aSGreg Kroah-Hartman
6908ffdff6aSGreg Kroah-Hartman return -EINVAL;
6918ffdff6aSGreg Kroah-Hartman }
6928ffdff6aSGreg Kroah-Hartman
waveform_common_attach(struct comedi_device * dev,int amplitude,int period)6938ffdff6aSGreg Kroah-Hartman static int waveform_common_attach(struct comedi_device *dev,
6948ffdff6aSGreg Kroah-Hartman int amplitude, int period)
6958ffdff6aSGreg Kroah-Hartman {
6968ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv;
6978ffdff6aSGreg Kroah-Hartman struct comedi_subdevice *s;
6988ffdff6aSGreg Kroah-Hartman int i;
6998ffdff6aSGreg Kroah-Hartman int ret;
7008ffdff6aSGreg Kroah-Hartman
7018ffdff6aSGreg Kroah-Hartman devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
7028ffdff6aSGreg Kroah-Hartman if (!devpriv)
7038ffdff6aSGreg Kroah-Hartman return -ENOMEM;
7048ffdff6aSGreg Kroah-Hartman
7058ffdff6aSGreg Kroah-Hartman devpriv->wf_amplitude = amplitude;
7068ffdff6aSGreg Kroah-Hartman devpriv->wf_period = period;
7078ffdff6aSGreg Kroah-Hartman
7088ffdff6aSGreg Kroah-Hartman ret = comedi_alloc_subdevices(dev, 2);
7098ffdff6aSGreg Kroah-Hartman if (ret)
7108ffdff6aSGreg Kroah-Hartman return ret;
7118ffdff6aSGreg Kroah-Hartman
7128ffdff6aSGreg Kroah-Hartman s = &dev->subdevices[0];
7138ffdff6aSGreg Kroah-Hartman dev->read_subdev = s;
7148ffdff6aSGreg Kroah-Hartman /* analog input subdevice */
7158ffdff6aSGreg Kroah-Hartman s->type = COMEDI_SUBD_AI;
7168ffdff6aSGreg Kroah-Hartman s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
7178ffdff6aSGreg Kroah-Hartman s->n_chan = N_CHANS;
7188ffdff6aSGreg Kroah-Hartman s->maxdata = 0xffff;
7198ffdff6aSGreg Kroah-Hartman s->range_table = &waveform_ai_ranges;
7208ffdff6aSGreg Kroah-Hartman s->len_chanlist = s->n_chan * 2;
7218ffdff6aSGreg Kroah-Hartman s->insn_read = waveform_ai_insn_read;
7228ffdff6aSGreg Kroah-Hartman s->do_cmd = waveform_ai_cmd;
7238ffdff6aSGreg Kroah-Hartman s->do_cmdtest = waveform_ai_cmdtest;
7248ffdff6aSGreg Kroah-Hartman s->cancel = waveform_ai_cancel;
7258ffdff6aSGreg Kroah-Hartman s->insn_config = waveform_ai_insn_config;
7268ffdff6aSGreg Kroah-Hartman
7278ffdff6aSGreg Kroah-Hartman s = &dev->subdevices[1];
7288ffdff6aSGreg Kroah-Hartman dev->write_subdev = s;
7298ffdff6aSGreg Kroah-Hartman /* analog output subdevice (loopback) */
7308ffdff6aSGreg Kroah-Hartman s->type = COMEDI_SUBD_AO;
7318ffdff6aSGreg Kroah-Hartman s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
7328ffdff6aSGreg Kroah-Hartman s->n_chan = N_CHANS;
7338ffdff6aSGreg Kroah-Hartman s->maxdata = 0xffff;
7348ffdff6aSGreg Kroah-Hartman s->range_table = &waveform_ai_ranges;
7358ffdff6aSGreg Kroah-Hartman s->len_chanlist = s->n_chan;
7368ffdff6aSGreg Kroah-Hartman s->insn_write = waveform_ao_insn_write;
7378ffdff6aSGreg Kroah-Hartman s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */
7388ffdff6aSGreg Kroah-Hartman s->do_cmd = waveform_ao_cmd;
7398ffdff6aSGreg Kroah-Hartman s->do_cmdtest = waveform_ao_cmdtest;
7408ffdff6aSGreg Kroah-Hartman s->cancel = waveform_ao_cancel;
7418ffdff6aSGreg Kroah-Hartman s->insn_config = waveform_ao_insn_config;
7428ffdff6aSGreg Kroah-Hartman
7438ffdff6aSGreg Kroah-Hartman /* Our default loopback value is just a 0V flatline */
7448ffdff6aSGreg Kroah-Hartman for (i = 0; i < s->n_chan; i++)
7458ffdff6aSGreg Kroah-Hartman devpriv->ao_loopbacks[i] = s->maxdata / 2;
7468ffdff6aSGreg Kroah-Hartman
7478ffdff6aSGreg Kroah-Hartman devpriv->dev = dev;
7488ffdff6aSGreg Kroah-Hartman timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0);
7498ffdff6aSGreg Kroah-Hartman timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0);
7508ffdff6aSGreg Kroah-Hartman
7518ffdff6aSGreg Kroah-Hartman dev_info(dev->class_dev,
7528ffdff6aSGreg Kroah-Hartman "%s: %u microvolt, %u microsecond waveform attached\n",
7538ffdff6aSGreg Kroah-Hartman dev->board_name,
7548ffdff6aSGreg Kroah-Hartman devpriv->wf_amplitude, devpriv->wf_period);
7558ffdff6aSGreg Kroah-Hartman
7568ffdff6aSGreg Kroah-Hartman return 0;
7578ffdff6aSGreg Kroah-Hartman }
7588ffdff6aSGreg Kroah-Hartman
waveform_attach(struct comedi_device * dev,struct comedi_devconfig * it)7598ffdff6aSGreg Kroah-Hartman static int waveform_attach(struct comedi_device *dev,
7608ffdff6aSGreg Kroah-Hartman struct comedi_devconfig *it)
7618ffdff6aSGreg Kroah-Hartman {
7628ffdff6aSGreg Kroah-Hartman int amplitude = it->options[0];
7638ffdff6aSGreg Kroah-Hartman int period = it->options[1];
7648ffdff6aSGreg Kroah-Hartman
7658ffdff6aSGreg Kroah-Hartman /* set default amplitude and period */
7668ffdff6aSGreg Kroah-Hartman if (amplitude <= 0)
7678ffdff6aSGreg Kroah-Hartman amplitude = 1000000; /* 1 volt */
7688ffdff6aSGreg Kroah-Hartman if (period <= 0)
7698ffdff6aSGreg Kroah-Hartman period = 100000; /* 0.1 sec */
7708ffdff6aSGreg Kroah-Hartman
7718ffdff6aSGreg Kroah-Hartman return waveform_common_attach(dev, amplitude, period);
7728ffdff6aSGreg Kroah-Hartman }
7738ffdff6aSGreg Kroah-Hartman
waveform_auto_attach(struct comedi_device * dev,unsigned long context_unused)7748ffdff6aSGreg Kroah-Hartman static int waveform_auto_attach(struct comedi_device *dev,
7758ffdff6aSGreg Kroah-Hartman unsigned long context_unused)
7768ffdff6aSGreg Kroah-Hartman {
7778ffdff6aSGreg Kroah-Hartman int amplitude = set_amplitude;
7788ffdff6aSGreg Kroah-Hartman int period = set_period;
7798ffdff6aSGreg Kroah-Hartman
7808ffdff6aSGreg Kroah-Hartman /* set default amplitude and period */
7818ffdff6aSGreg Kroah-Hartman if (!amplitude)
7828ffdff6aSGreg Kroah-Hartman amplitude = 1000000; /* 1 volt */
7838ffdff6aSGreg Kroah-Hartman if (!period)
7848ffdff6aSGreg Kroah-Hartman period = 100000; /* 0.1 sec */
7858ffdff6aSGreg Kroah-Hartman
7868ffdff6aSGreg Kroah-Hartman return waveform_common_attach(dev, amplitude, period);
7878ffdff6aSGreg Kroah-Hartman }
7888ffdff6aSGreg Kroah-Hartman
waveform_detach(struct comedi_device * dev)7898ffdff6aSGreg Kroah-Hartman static void waveform_detach(struct comedi_device *dev)
7908ffdff6aSGreg Kroah-Hartman {
7918ffdff6aSGreg Kroah-Hartman struct waveform_private *devpriv = dev->private;
7928ffdff6aSGreg Kroah-Hartman
7938ffdff6aSGreg Kroah-Hartman if (devpriv) {
794*8fa7292fSThomas Gleixner timer_delete_sync(&devpriv->ai_timer);
795*8fa7292fSThomas Gleixner timer_delete_sync(&devpriv->ao_timer);
7968ffdff6aSGreg Kroah-Hartman }
7978ffdff6aSGreg Kroah-Hartman }
7988ffdff6aSGreg Kroah-Hartman
7998ffdff6aSGreg Kroah-Hartman static struct comedi_driver waveform_driver = {
8008ffdff6aSGreg Kroah-Hartman .driver_name = "comedi_test",
8018ffdff6aSGreg Kroah-Hartman .module = THIS_MODULE,
8028ffdff6aSGreg Kroah-Hartman .attach = waveform_attach,
8038ffdff6aSGreg Kroah-Hartman .auto_attach = waveform_auto_attach,
8048ffdff6aSGreg Kroah-Hartman .detach = waveform_detach,
8058ffdff6aSGreg Kroah-Hartman };
8068ffdff6aSGreg Kroah-Hartman
8078ffdff6aSGreg Kroah-Hartman /*
8088ffdff6aSGreg Kroah-Hartman * For auto-configuration, a device is created to stand in for a
8098ffdff6aSGreg Kroah-Hartman * real hardware device.
8108ffdff6aSGreg Kroah-Hartman */
comedi_test_init(void)8118ffdff6aSGreg Kroah-Hartman static int __init comedi_test_init(void)
8128ffdff6aSGreg Kroah-Hartman {
8138ffdff6aSGreg Kroah-Hartman int ret;
8148ffdff6aSGreg Kroah-Hartman
8158ffdff6aSGreg Kroah-Hartman ret = comedi_driver_register(&waveform_driver);
8168ffdff6aSGreg Kroah-Hartman if (ret) {
8178ffdff6aSGreg Kroah-Hartman pr_err("comedi_test: unable to register driver\n");
8188ffdff6aSGreg Kroah-Hartman return ret;
8198ffdff6aSGreg Kroah-Hartman }
8208ffdff6aSGreg Kroah-Hartman
8218ffdff6aSGreg Kroah-Hartman if (!config_mode) {
8223b7a628dSIvan Orlov ret = class_register(&ctcls);
8233b7a628dSIvan Orlov if (ret) {
8248ffdff6aSGreg Kroah-Hartman pr_warn("comedi_test: unable to create class\n");
8258ffdff6aSGreg Kroah-Hartman goto clean3;
8268ffdff6aSGreg Kroah-Hartman }
8278ffdff6aSGreg Kroah-Hartman
8283b7a628dSIvan Orlov ctdev = device_create(&ctcls, NULL, MKDEV(0, 0), NULL, DEV_NAME);
8298ffdff6aSGreg Kroah-Hartman if (IS_ERR(ctdev)) {
8308ffdff6aSGreg Kroah-Hartman pr_warn("comedi_test: unable to create device\n");
8318ffdff6aSGreg Kroah-Hartman goto clean2;
8328ffdff6aSGreg Kroah-Hartman }
8338ffdff6aSGreg Kroah-Hartman
8348ffdff6aSGreg Kroah-Hartman ret = comedi_auto_config(ctdev, &waveform_driver, 0);
8358ffdff6aSGreg Kroah-Hartman if (ret) {
8368ffdff6aSGreg Kroah-Hartman pr_warn("comedi_test: unable to auto-configure device\n");
8378ffdff6aSGreg Kroah-Hartman goto clean;
8388ffdff6aSGreg Kroah-Hartman }
8398ffdff6aSGreg Kroah-Hartman }
8408ffdff6aSGreg Kroah-Hartman
8418ffdff6aSGreg Kroah-Hartman return 0;
8428ffdff6aSGreg Kroah-Hartman
8438ffdff6aSGreg Kroah-Hartman clean:
8443b7a628dSIvan Orlov device_destroy(&ctcls, MKDEV(0, 0));
8458ffdff6aSGreg Kroah-Hartman clean2:
8463b7a628dSIvan Orlov class_unregister(&ctcls);
8478ffdff6aSGreg Kroah-Hartman clean3:
8488ffdff6aSGreg Kroah-Hartman return 0;
8498ffdff6aSGreg Kroah-Hartman }
8508ffdff6aSGreg Kroah-Hartman module_init(comedi_test_init);
8518ffdff6aSGreg Kroah-Hartman
comedi_test_exit(void)8528ffdff6aSGreg Kroah-Hartman static void __exit comedi_test_exit(void)
8538ffdff6aSGreg Kroah-Hartman {
8548ffdff6aSGreg Kroah-Hartman if (ctdev)
8558ffdff6aSGreg Kroah-Hartman comedi_auto_unconfig(ctdev);
8568ffdff6aSGreg Kroah-Hartman
8573b7a628dSIvan Orlov if (class_is_registered(&ctcls)) {
8583b7a628dSIvan Orlov device_destroy(&ctcls, MKDEV(0, 0));
8593b7a628dSIvan Orlov class_unregister(&ctcls);
8608ffdff6aSGreg Kroah-Hartman }
8618ffdff6aSGreg Kroah-Hartman
8628ffdff6aSGreg Kroah-Hartman comedi_driver_unregister(&waveform_driver);
8638ffdff6aSGreg Kroah-Hartman }
8648ffdff6aSGreg Kroah-Hartman module_exit(comedi_test_exit);
8658ffdff6aSGreg Kroah-Hartman
8668ffdff6aSGreg Kroah-Hartman MODULE_AUTHOR("Comedi https://www.comedi.org");
8678ffdff6aSGreg Kroah-Hartman MODULE_DESCRIPTION("Comedi low-level driver");
8688ffdff6aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
869