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