Re: staging: comedi: USB devs not working / some comedi core reorganization

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I've been trying to track down the issue that comedi_command_test produces random errors.

I've put printf's into the cmd.c in comedilib and put printk in the usbduxsigma comedi_command_test function.

I've run a modified cmd.c from the demo directory from comedilib:

bp1@bp1-Dimension-9100:~/usbduxsigma_demo$ ./cmd
No mask
comedi_command_test in comedi_get_cmd_src_mask, ret=1
Mask set
comedi_get_cmd_generic_timed: test 1 returned -1
comedi_get_cmd_generic_timed failed
bp1@bp1-Dimension-9100:~/usbduxsigma_demo$

dmesg:
[ 4784.639010] cfc_check_trigger_src: orig_src=ffffffff, *src=82
[ 4784.639021] TRIG_NOW | TRIG_INT err=-22, cmd->start_src=82
[ 4784.639025] cfc_check_trigger_src: orig_src=ffffffff, *src=10
[ 4784.639029] TRIG_TIMER err=-22, cmd->scan_begin_src=10
[ 4784.639032] cfc_check_trigger_src: orig_src=ffffffff, *src=2
[ 4784.639036] TRIG_NOW err=-22, cmd->convert_src=2
[ 4784.639040] cfc_check_trigger_src: orig_src=ffffffff, *src=20
[ 4784.639043] TRIG_COUNT err=-22, cmd->scan_end_src=20
[ 4784.639045] cfc_check_trigger_src: orig_src=ffffffff, *src=21
[ 4784.639048] TRIG_COUNT | TRIG_NONE err=-22, cmd->stop_src=21
[ 4784.639050] Wrong TRIG source

Looking at comedilib then there should be two calls to comedi_command_test but there is only one and the return value is -1 for the second call which shouldn't be the case.

/Bernd

On 10/12/13 21:36, Ian Abbott wrote:
On 2013-12-10 21:07, Bernd Porr wrote:
Hi all,

here is the patch to fix the original bug. That was easier than I
expected. That's against the latest RC kernel.

However there are a couple other issues now.

There seems to be an issue with comedi generic timed and the commands
correcting the TRIG bit. It ANDs the right bit values first in the
"comedi_get_cmd_generic_timed" but then "comedi_command_test" gets just
0 and then cmd fails:

[ 9525.967397] usbcore: registered new interface driver usbduxsigma
[ 9539.981072] cfc_check_trigger_src: orig_src=ffffffff, *src=82
[ 9539.981079] TRIG_NOW | TRIG_INT err=-22, cmd->start_src=82
[ 9539.981082] cfc_check_trigger_src: orig_src=ffffffff, *src=10
[ 9539.981085] TRIG_TIMER err=-22, cmd->scan_begin_src=10
[ 9539.981088] cfc_check_trigger_src: orig_src=ffffffff, *src=2
[ 9539.981091] TRIG_NOW err=-22, cmd->convert_src=2
[ 9539.981094] cfc_check_trigger_src: orig_src=ffffffff, *src=20
[ 9539.981096] TRIG_COUNT err=-22, cmd->scan_end_src=20
[ 9539.981099] cfc_check_trigger_src: orig_src=ffffffff, *src=21
[ 9539.981102] TRIG_COUNT | TRIG_NONE err=-22, cmd->stop_src=21
[ 9539.981104] Wrong TRIG source
[ 9539.981167] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981171] TRIG_NOW | TRIG_INT err=-22, cmd->start_src=0
[ 9539.981173] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981176] TRIG_TIMER err=-22, cmd->scan_begin_src=0
[ 9539.981178] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981181] TRIG_NOW err=-22, cmd->convert_src=0
[ 9539.981183] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981185] TRIG_COUNT err=-22, cmd->scan_end_src=0
[ 9539.981187] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981190] TRIG_COUNT | TRIG_NONE err=-22, cmd->stop_src=0
[ 9539.981192] Wrong TRIG source
[ 9539.981196] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981199] TRIG_NOW | TRIG_INT err=-22, cmd->start_src=0
[ 9539.981201] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981203] TRIG_TIMER err=-22, cmd->scan_begin_src=0
[ 9539.981205] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981208] TRIG_NOW err=-22, cmd->convert_src=0
[ 9539.981210] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981212] TRIG_COUNT err=-22, cmd->scan_end_src=0
[ 9539.981214] cfc_check_trigger_src: orig_src=0, *src=0
[ 9539.981216] TRIG_COUNT | TRIG_NONE err=-22, cmd->stop_src=0
[ 9539.981218] Wrong TRIG source

Userspace program (cmd.c):

          ret = comedi_command_test(dev, cmd);
          if(ret < 0){
                  comedi_perror("comedi_command_test");
                  if(errno == EIO){
                          fprintf(stderr,"Ummm... this subdevice doesn't
support commands\n");
                  }
                  exit(1);
          }
          ret = comedi_command_test(dev, cmd);
          if(ret < 0){
                  comedi_perror("comedi_command_test");
                  exit(1);
          }
          fprintf(stderr,"second test returned %d (%s)\n", ret,
                          cmdtest_messages[ret]);
^^^^^^ stops here


hacked cfc_check_trigger_src:
-----------------------------

static inline int cfc_check_trigger_src(unsigned int *src, unsigned int
flags)
{
          unsigned int orig_src = *src;

          *src = orig_src & flags;
          printk("cfc_check_trigger_src: orig_src=%x, *src=%x
\n",orig_src,*src );
          if (*src == TRIG_INVALID || *src != orig_src)
                  return -EINVAL;
          return 0;
}


Any ideas?

I don't know off-hand, but I'll take a look when I get the chance.


There is another issue which has to the with the subdevices. This
happens when I unplug the DUX board (all boards cause this):

[ 4972.978379] ------------[ cut here ]------------
[ 4972.978386] WARNING: CPU: 0 PID: 33 at fs/sysfs/group.c:214
sysfs_remove_group+0xc6/0xd0()
[ 4972.978389] sysfs group ffffffff81caa2e0 not found for kobject
'comedi0_subd1'
[ 4972.978392] Modules linked in: usbdux(O) usbduxsigma(O) comedi_fc(O)
comedi(O) radeon bnep rfcomm bluetooth snd_hda_codec_idt snd_hda_intel
snd_hda_codec parport_pc ppdev rc_hauppauge snd_bt87x ir_kbd_i2c
snd_hwdep tuner snd_pcm msp3400 snd_page_alloc snd_seq_midi bttv
snd_seq_midi_event snd_rawmidi pcmcia hid_generic snd_seq pcmcia_core
ttm usbhid hid drm_kms_helper snd_seq_device drm btcx_risc snd_timer
tveeprom videobuf_dma_sg rc_core snd v4l2_common psmouse videobuf_core
gpio_ich videodev dcdbas microcode serio_raw lpc_ich i2c_algo_bit
soundcore mac_hid lp parport e100 ahci mii libahci [last unloaded:
comedi]
[ 4972.978454] CPU: 0 PID: 33 Comm: khubd Tainted: G        WC O
3.13.0-rc3+ #1
[ 4972.978457] Hardware name: Dell Inc.                 Dimension 9100
               /0X8582, BIOS A01 05/25/2005
[ 4972.978460]  0000000000000009 ffff88005c801a38 ffffffff81721083
ffff88005c801a80
[ 4972.978466]  ffff88005c801a70 ffffffff810645fd 0000000000000000
ffffffff81caa2e0
[ 4972.978472]  ffff8800560ed810 ffff88005a615000 0000000000000000
ffff88005c801ad0
[ 4972.978478] Call Trace:
[ 4972.978484]  [<ffffffff81721083>] dump_stack+0x45/0x56
[ 4972.978489]  [<ffffffff810645fd>] warn_slowpath_common+0x7d/0xa0
[ 4972.978494]  [<ffffffff8106466c>] warn_slowpath_fmt+0x4c/0x50
[ 4972.978499]  [<ffffffff8122fb7e>] ? sysfs_get_dirent_ns+0x4e/0x70
[ 4972.978504]  [<ffffffff81230e56>] sysfs_remove_group+0xc6/0xd0
[ 4972.978509]  [<ffffffff814a5793>] dpm_sysfs_remove+0x43/0x50
[ 4972.978513]  [<ffffffff8149b1c5>] device_del+0x45/0x1c0
[ 4972.978518]  [<ffffffff8149b35e>] device_unregister+0x1e/0x60
[ 4972.978522]  [<ffffffff8149b41c>] device_destroy+0x3c/0x50
[ 4972.978530]  [<ffffffffa0221525>]
comedi_free_subdevice_minor+0x75/0xa0 [comedi]
[ 4972.978539]  [<ffffffffa0221f68>] comedi_device_detach+0x48/0x160
[comedi]
[ 4972.978546]  [<ffffffffa021e2b3>] comedi_device_cleanup+0x33/0x90
[comedi]
[ 4972.978552]  [<ffffffffa021e346>] comedi_free_board_dev+0x36/0x50
[comedi]
[ 4972.978558]  [<ffffffffa02213b0>]
comedi_release_hardware_device+0x80/0x90 [comedi]
[ 4972.978565]  [<ffffffffa0221a63>] comedi_auto_unconfig+0x13/0x20
[comedi]
[ 4972.978572]  [<ffffffffa0223172>] comedi_usb_auto_unconfig+0x12/0x20
[comedi]
[ 4972.978578]  [<ffffffff815520f4>] usb_unbind_interface+0x64/0x1c0
[ 4972.978583]  [<ffffffff8149eedf>] __device_release_driver+0x7f/0xf0
[ 4972.978587]  [<ffffffff8149ef73>] device_release_driver+0x23/0x30
[ 4972.978592]  [<ffffffff8149e7f8>] bus_remove_device+0x108/0x180
[ 4972.978596]  [<ffffffff8149b2a9>] device_del+0x129/0x1c0
[ 4972.978601]  [<ffffffff8154faa0>] usb_disable_device+0xb0/0x290
[ 4972.978605]  [<ffffffff815442bd>] usb_disconnect+0xad/0x200
[ 4972.978609]  [<ffffffff8154763d>] hub_thread+0x70d/0x1750
[ 4972.978614]  [<ffffffff8109a4c8>] ? sched_clock_cpu+0xa8/0x100
[ 4972.978619]  [<ffffffff810a88c0>] ? prepare_to_wait_event+0x100/0x100
[ 4972.978623]  [<ffffffff81546f30>] ? usb_reset_device+0x1d0/0x1d0
[ 4972.978627]  [<ffffffff81088152>] kthread+0xd2/0xf0
[ 4972.978632]  [<ffffffff81088080>] ? kthread_create_on_node+0x190/0x190
[ 4972.978637]  [<ffffffff81731d3c>] ret_from_fork+0x7c/0xb0
[ 4972.978641]  [<ffffffff81088080>] ? kthread_create_on_node+0x190/0x190
[ 4972.978644] ---[ end trace 69c2b5c4559cdf1b ]---

I guess that subdevice no longer exists at this point?

/Bernd

I'm hoping that will be fixed by other changes I made to the comedi core
recently that aren't in the RC kernel yet.


--
www:    http://www.berndporr.me.uk/
        http://www.linux-usb-daq.co.uk/
	http://www.imdb.com/name/nm3293421/
Mobile: +44 (0)7840 340069
Work:   +44 (0)141 330 5237
        University of Glasgow
        School of Engineering
        Rankine Building, Oakfield Avenue,
        Glasgow, G12 8LT
/*
 * Example of using commands - asynchronous input
 * Part of Comedilib
 *
 * Copyright (c) 1999,2000,2001 David A. Schleef <ds@xxxxxxxxxxx>
 *               2008 Bernd Porr <berndporr@xxxxxxx>
 *
 * This file may be freely modified, distributed, and combined with
 * other software, as long as proper attribution is given in the
 * source code.
 */

/*
 * The program is used to test the usbdux sigma board
 */

#include <stdio.h>
#include <comedilib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

extern comedi_t *device;

struct parsed_options
{
	char *filename;
	double value;
	int subdevice;
	int channel;
	int aref;
	int range;
	int physical;
	int verbose;
	int n_chan;
	int n_scan;
	double freq;
};



#define BUFSZ 10000
char buf[BUFSZ];

#define N_CHANS 256
static unsigned int chanlist[N_CHANS];
static comedi_range * range_info[N_CHANS];
static lsampl_t maxdata[N_CHANS];


void do_cmd(comedi_t *dev,comedi_cmd *cmd);

void print_datum(lsampl_t raw, int channel_index, short physical);

char *cmdtest_messages[]={
	"success",
	"invalid source",
	"source conflict",
	"invalid argument",
	"argument conflict",
	"invalid chanlist",
};

int main(int argc, char *argv[])
{
	comedi_t *dev;
	comedi_cmd cmd;
	int ret;
	int total=0;
	int i;
	struct timeval start,end;
	int subdev_flags;
	lsampl_t raw;

	struct parsed_options options;

	/* The following variables used in this demo
	 * can be modified by command line
	 * options.  When modifying this demo, you may want to
	 * change them here. */
	options.filename = "/dev/comedi0";
	options.subdevice = 0;
	options.channel = 0;
	options.range = 0;
	options.aref = AREF_GROUND;
	options.n_chan = 20;
	options.n_scan = 10000;
	options.freq = 1000.0;

	/* open the device */
	dev = comedi_open(options.filename);
	if(!dev){
		comedi_perror(options.filename);
		exit(1);
	}

	// Print numbers for clipped inputs
	comedi_set_global_oor_behavior(COMEDI_OOR_NUMBER);

	/* Set up channel list */
	for(i = 0; i < options.n_chan; i++){
		chanlist[i] = CR_PACK(options.channel + i, options.range, options.aref);
		range_info[i] = comedi_get_range(dev, options.subdevice, options.channel, options.range);
		maxdata[i] = comedi_get_maxdata(dev, options.subdevice, options.channel);
	}

        ret = comedi_get_cmd_generic_timed(dev, options.subdevice, &cmd, options.n_chan, 1e9 / options.freq);
        if(ret<0){
                printf("comedi_get_cmd_generic_timed failed\n");
                return ret;
        }

	//prepare_cmd(dev, options.subdevice, options.n_scan, options.n_chan, 1e9 / options.freq, &cmd);

	/* comedi_command_test() tests a command to see if the
	 * trigger sources and arguments are valid for the subdevice.
	 * If a trigger source is invalid, it will be logically ANDed
	 * with valid values (trigger sources are actually bitmasks),
	 * which may or may not result in a valid trigger source.
	 * If an argument is invalid, it will be adjusted to the
	 * nearest valid value.  In this way, for many commands, you
	 * can test it multiple times until it passes.  Typically,
	 * if you can't get a valid command in two tests, the original
	 * command wasn't specified very well. */
	ret = comedi_command_test(dev, &cmd);
	if(ret < 0){
		comedi_perror("comedi_command_test");
		if(errno == EIO){
			fprintf(stderr,"Ummm... this subdevice doesn't support commands\n");
		}
		exit(1);
	}
	ret = comedi_command_test(dev, &cmd);
	if(ret < 0){
		comedi_perror("comedi_command_test");
		exit(1);
	}
	fprintf(stderr,"second test returned %d (%s)\n", ret,
			cmdtest_messages[ret]);
	if(ret!=0){
		fprintf(stderr, "Error preparing command\n");
		exit(1);
	}

	/* this is only for informational purposes */
	gettimeofday(&start, NULL);
	fprintf(stderr,"start time: %ld.%06ld\n", start.tv_sec, start.tv_usec);

	/* start the command */
	ret = comedi_command(dev, &cmd);
	if(ret < 0){
		comedi_perror("comedi_command");
		exit(1);
	}
	subdev_flags = comedi_get_subdevice_flags(dev, options.subdevice);
	while(1){
		ret = read(comedi_fileno(dev),buf,BUFSZ);
		if(ret < 0){
			/* some error occurred */
			perror("read");
			break;
		}else if(ret == 0){
			/* reached stop condition */
			break;
		}else{
			static int col = 0;
			int bytes_per_sample;
			total += ret;
			if(options.verbose)fprintf(stderr, "read %d %d\n", ret, total);
			if(subdev_flags & SDF_LSAMPL)
				bytes_per_sample = sizeof(lsampl_t);
			else
				bytes_per_sample = sizeof(sampl_t);
			for(i = 0; i < ret / bytes_per_sample; i++){
				if(subdev_flags & SDF_LSAMPL) {
					raw = ((lsampl_t *)buf)[i];
				} else {
					raw = ((sampl_t *)buf)[i];
				}
				print_datum(raw, col, options.physical);
				col++;
				if(col == options.n_chan){
					printf("\n");
					col=0;
				}
			}
		}
	}

	/* this is only for informational purposes */
	gettimeofday(&end,NULL);
	fprintf(stderr,"end time: %ld.%06ld\n", end.tv_sec, end.tv_usec);

	end.tv_sec -= start.tv_sec;
	if(end.tv_usec < start.tv_usec){
		end.tv_sec--;
		end.tv_usec += 1000000;
	}
	end.tv_usec -= start.tv_usec;
	fprintf(stderr,"time: %ld.%06ld\n", end.tv_sec, end.tv_usec);

	return 0;
}

void print_datum(lsampl_t raw, int channel_index, short physical) {
	double physical_value;
	if(!physical) {
		printf("%d ",raw);
	} else {
		physical_value = comedi_to_phys(raw, range_info[channel_index], maxdata[channel_index]);
		printf("%#8.6g ",physical_value);
	}
}
/*
 * usbduxsigma.c
 * Copyright (C) 2011 Bernd Porr, Bernd.Porr@xxxxxxx
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

/*
 * Driver: usbduxsigma
 * Description: University of Stirling USB DAQ & INCITE Technology Limited
 * Devices: (ITL) USB-DUX [usbduxsigma]
 * Author: Bernd Porr <BerndPorr@xxxxxxx>
 * Updated: 8 Nov 2011
 * Status: testing
 */

/*
 * I must give credit here to Chris Baugher who
 * wrote the driver for AT-MIO-16d. I used some parts of this
 * driver. I also must give credits to David Brownell
 * who supported me with the USB development.
 *
 * Note: the raw data from the A/D converter is 24 bit big endian
 * anything else is little endian to/from the dux board
 *
 *
 * Revision history:
 *   0.1: initial version
 *   0.2: all basic functions implemented, digital I/O only for one port
 *   0.3: proper vendor ID and driver name
 *   0.4: fixed D/A voltage range
 *   0.5: various bug fixes, health check at startup
 *   0.6: corrected wrong input range
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/fcntl.h>
#include <linux/compiler.h>

#include "comedi_fc.h"
#include "../comedidev.h"

/* timeout for the USB-transfer in ms*/
#define BULK_TIMEOUT 1000

/* constants for "firmware" upload and download */
#define FIRMWARE		"usbduxsigma_firmware.bin"
#define FIRMWARE_MAX_LEN	0x4000
#define USBDUXSUB_FIRMWARE	0xa0
#define VENDOR_DIR_IN		0xc0
#define VENDOR_DIR_OUT		0x40

/* internal addresses of the 8051 processor */
#define USBDUXSUB_CPUCS 0xE600

/* 300Hz max frequ under PWM */
#define MIN_PWM_PERIOD  ((long)(1E9/300))

/* Default PWM frequency */
#define PWM_DEFAULT_PERIOD ((long)(1E9/100))

/* Number of channels (16 AD and offset)*/
#define NUMCHANNELS 16

#define USBDUXSIGMA_NUM_AO_CHAN		4

/* Size of one A/D value */
#define SIZEADIN          ((sizeof(uint32_t)))

/*
 * Size of the async input-buffer IN BYTES, the DIO state is transmitted
 * as the first byte.
 */
#define SIZEINBUF         (((NUMCHANNELS+1)*SIZEADIN))

/* 16 bytes. */
#define SIZEINSNBUF       16

/* Number of DA channels */
#define NUMOUTCHANNELS    8

/* size of one value for the D/A converter: channel and value */
#define SIZEDAOUT          ((sizeof(uint8_t)+sizeof(uint16_t)))

/*
 * Size of the output-buffer in bytes
 * Actually only the first 4 triplets are used but for the
 * high speed mode we need to pad it to 8 (microframes).
 */
#define SIZEOUTBUF         ((8*SIZEDAOUT))

/*
 * Size of the buffer for the dux commands: just now max size is determined
 * by the analogue out + command byte + panic bytes...
 */
#define SIZEOFDUXBUFFER    ((8*SIZEDAOUT+2))

/* Number of in-URBs which receive the data: min=2 */
#define NUMOFINBUFFERSFULL     5

/* Number of out-URBs which send the data: min=2 */
#define NUMOFOUTBUFFERSFULL    5

/* Number of in-URBs which receive the data: min=5 */
/* must have more buffers due to buggy USB ctr */
#define NUMOFINBUFFERSHIGH     10

/* Number of out-URBs which send the data: min=5 */
/* must have more buffers due to buggy USB ctr */
#define NUMOFOUTBUFFERSHIGH    10

/* number of retries to get the right dux command */
#define RETRIES 10

/* bulk transfer commands to usbduxsigma */
#define USBBUXSIGMA_AD_CMD		0
#define USBDUXSIGMA_DA_CMD		1
#define USBDUXSIGMA_DIO_CFG_CMD		2
#define USBDUXSIGMA_DIO_BITS_CMD	3
#define USBDUXSIGMA_SINGLE_AD_CMD	4
#define USBDUXSIGMA_PWM_ON_CMD		7
#define USBDUXSIGMA_PWM_OFF_CMD		8

static const struct comedi_lrange usbduxsigma_ai_range = {
	1, {
		BIP_RANGE(2.65 / 2.0)
	}
};

struct usbduxsigma_private {
	/* actual number of in-buffers */
	int n_ai_urbs;
	/* actual number of out-buffers */
	int n_ao_urbs;
	/* ISO-transfer handling: buffers */
	struct urb **ai_urbs;
	struct urb **ao_urbs;
	/* pwm-transfer handling */
	struct urb *pwm_urb;
	/* PWM period */
	unsigned int pwm_period;
	/* PWM internal delay for the GPIF in the FX2 */
	uint8_t pwm_delay;
	/* size of the PWM buffer which holds the bit pattern */
	int pwm_buf_sz;
	/* input buffer for the ISO-transfer */
	uint32_t *in_buf;
	/* input buffer for single insn */
	uint8_t *insn_buf;

	uint8_t ao_chanlist[USBDUXSIGMA_NUM_AO_CHAN];
	unsigned int ao_readback[USBDUXSIGMA_NUM_AO_CHAN];

	unsigned high_speed:1;
	unsigned ai_cmd_running:1;
	unsigned ai_continuous:1;
	unsigned ao_cmd_running:1;
	unsigned ao_continuous:1;
	unsigned pwm_cmd_running:1;

	/* number of samples to acquire */
	int ai_sample_count;
	int ao_sample_count;
	/* time between samples in units of the timer */
	unsigned int ai_timer;
	unsigned int ao_timer;
	/* counter between acquisitions */
	unsigned int ai_counter;
	unsigned int ao_counter;
	/* interval in frames/uframes */
	unsigned int ai_interval;
	/* commands */
	uint8_t *dux_commands;
	struct semaphore sem;
};

static void usbduxsigma_unlink_urbs(struct urb **urbs, int num_urbs)
{
	int i;

	for (i = 0; i < num_urbs; i++)
		usb_kill_urb(urbs[i]);
}

static void usbduxsigma_ai_stop(struct comedi_device *dev, int do_unlink)
{
	struct usbduxsigma_private *devpriv = dev->private;

	if (do_unlink && devpriv->ai_urbs)
		usbduxsigma_unlink_urbs(devpriv->ai_urbs, devpriv->n_ai_urbs);

	devpriv->ai_cmd_running = 0;
}

static int usbduxsigma_ai_cancel(struct comedi_device *dev,
				 struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;

	down(&devpriv->sem);
	/* unlink only if it is really running */
	usbduxsigma_ai_stop(dev, devpriv->ai_cmd_running);
	up(&devpriv->sem);

	return 0;
}

static void usbduxsigma_ai_urb_complete(struct urb *urb)
{
	struct comedi_device *dev = urb->context;
	struct usbduxsigma_private *devpriv = dev->private;
	struct comedi_subdevice *s = dev->read_subdev;
	unsigned int dio_state;
	uint32_t val;
	int ret;
	int i;

	/* first we test if something unusual has just happened */
	switch (urb->status) {
	case 0:
		/* copy the result in the transfer buffer */
		memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF);
		break;
	case -EILSEQ:
		/*
		 * error in the ISOchronous data
		 * we don't copy the data into the transfer buffer
		 * and recycle the last data byte
		 */
		dev_dbg(dev->class_dev, "CRC error in ISO IN stream\n");

		break;

	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
	case -ECONNABORTED:
		/* happens after an unlink command */
		if (devpriv->ai_cmd_running) {
			usbduxsigma_ai_stop(dev, 0);	/* w/o unlink */
			/* we are still running a command, tell comedi */
			s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
			comedi_event(dev, s);
		}
		return;

	default:
		/*
		 * a real error on the bus
		 * pass error to comedi if we are really running a command
		 */
		if (devpriv->ai_cmd_running) {
			dev_err(dev->class_dev,
				"%s: non-zero urb status (%d)\n",
				__func__, urb->status);
			usbduxsigma_ai_stop(dev, 0);	/* w/o unlink */
			s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
			comedi_event(dev, s);
		}
		return;
	}

	if (unlikely(!devpriv->ai_cmd_running))
		return;

	urb->dev = comedi_to_usb_dev(dev);

	ret = usb_submit_urb(urb, GFP_ATOMIC);
	if (unlikely(ret < 0)) {
		dev_err(dev->class_dev, "%s: urb resubmit failed (%d)\n",
			__func__, ret);
		if (ret == -EL2NSYNC)
			dev_err(dev->class_dev,
				"buggy USB host controller or bug in IRQ handler\n");
		usbduxsigma_ai_stop(dev, 0);	/* w/o unlink */
		s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
		comedi_event(dev, s);
		return;
	}

	/* get the state of the dio pins to allow external trigger */
	dio_state = be32_to_cpu(devpriv->in_buf[0]);

	devpriv->ai_counter--;
	if (likely(devpriv->ai_counter > 0))
		return;

	/* timer zero, transfer measurements to comedi */
	devpriv->ai_counter = devpriv->ai_timer;

	if (!devpriv->ai_continuous) {
		/* not continuous, fixed number of samples */
		devpriv->ai_sample_count--;
		if (devpriv->ai_sample_count < 0) {
			usbduxsigma_ai_stop(dev, 0);	/* w/o unlink */
			/* acquistion is over, tell comedi */
			s->async->events |= COMEDI_CB_EOA;
			comedi_event(dev, s);
			return;
		}
	}

	/* get the data from the USB bus and hand it over to comedi */
	for (i = 0; i < s->async->cmd.chanlist_len; i++) {
		/* transfer data, note first byte is the DIO state */
		val = be32_to_cpu(devpriv->in_buf[i+1]);
		val &= 0x00ffffff;	/* strip status byte */
		val ^= 0x00800000;	/* convert to unsigned */

		ret = cfc_write_array_to_buffer(s, &val, sizeof(uint32_t));
		if (unlikely(ret == 0)) {
			/* buffer overflow */
			usbduxsigma_ai_stop(dev, 0);	/* w/o unlink */
			return;
		}
	}
	/* tell comedi that data is there */
	s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS);
	comedi_event(dev, s);
}

static void usbduxsigma_ao_stop(struct comedi_device *dev, int do_unlink)
{
	struct usbduxsigma_private *devpriv = dev->private;

	if (do_unlink && devpriv->ao_urbs)
		usbduxsigma_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs);

	devpriv->ao_cmd_running = 0;
}

static int usbduxsigma_ao_cancel(struct comedi_device *dev,
				 struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;

	down(&devpriv->sem);
	/* unlink only if it is really running */
	usbduxsigma_ao_stop(dev, devpriv->ao_cmd_running);
	up(&devpriv->sem);

	return 0;
}

static void usbduxsigma_ao_urb_complete(struct urb *urb)
{
	struct comedi_device *dev = urb->context;
	struct usbduxsigma_private *devpriv = dev->private;
	struct comedi_subdevice *s = dev->write_subdev;
	uint8_t *datap;
	int len;
	int ret;
	int i;

	switch (urb->status) {
	case 0:
		/* success */
		break;

	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
	case -ECONNABORTED:
		/* happens after an unlink command */
		if (devpriv->ao_cmd_running) {
			usbduxsigma_ao_stop(dev, 0);	/* w/o unlink */
			s->async->events |= COMEDI_CB_EOA;
			comedi_event(dev, s);
		}
		return;

	default:
		/* a real error */
		if (devpriv->ao_cmd_running) {
			dev_err(dev->class_dev,
				"%s: non-zero urb status (%d)\n",
				__func__, urb->status);
			usbduxsigma_ao_stop(dev, 0);	/* w/o unlink */
			s->async->events |= (COMEDI_CB_ERROR | COMEDI_CB_EOA);
			comedi_event(dev, s);
		}
		return;
	}

	if (!devpriv->ao_cmd_running)
		return;

	devpriv->ao_counter--;
	if ((int)devpriv->ao_counter <= 0) {
		/* timer zero, transfer from comedi */
		devpriv->ao_counter = devpriv->ao_timer;

		if (!devpriv->ao_continuous) {
			/* not continuous, fixed number of samples */
			devpriv->ao_sample_count--;
			if (devpriv->ao_sample_count < 0) {
				usbduxsigma_ao_stop(dev, 0);	/* w/o unlink */
				/* acquistion is over, tell comedi */
				s->async->events |= COMEDI_CB_EOA;
				comedi_event(dev, s);
				return;
			}
		}

		/* transmit data to the USB bus */
		datap = urb->transfer_buffer;
		len = s->async->cmd.chanlist_len;
		*datap++ = len;
		for (i = 0; i < len; i++) {
			unsigned int chan = devpriv->ao_chanlist[i];
			unsigned short val;

			ret = comedi_buf_get(s->async, &val);
			if (ret < 0) {
				dev_err(dev->class_dev, "buffer underflow\n");
				s->async->events |= (COMEDI_CB_EOA |
						     COMEDI_CB_OVERFLOW);
			}
			*datap++ = val;
			*datap++ = chan;
			devpriv->ao_readback[chan] = val;

			s->async->events |= COMEDI_CB_BLOCK;
			comedi_event(dev, s);
		}
	}

	urb->transfer_buffer_length = SIZEOUTBUF;
	urb->dev = comedi_to_usb_dev(dev);
	urb->status = 0;
	if (devpriv->high_speed)
		urb->interval = 8;	/* uframes */
	else
		urb->interval = 1;	/* frames */
	urb->number_of_packets = 1;
	urb->iso_frame_desc[0].offset = 0;
	urb->iso_frame_desc[0].length = SIZEOUTBUF;
	urb->iso_frame_desc[0].status = 0;
	ret = usb_submit_urb(urb, GFP_ATOMIC);
	if (ret < 0) {
		dev_err(dev->class_dev,
			"%s: urb resubmit failed (%d)\n",
			__func__, ret);
		if (ret == EL2NSYNC)
			dev_err(dev->class_dev,
				"buggy USB host controller or bug in IRQ handler\n");
		usbduxsigma_ao_stop(dev, 0);	/* w/o unlink */
		s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
		comedi_event(dev, s);
	}
}

static int usbduxsigma_submit_urbs(struct comedi_device *dev,
				   struct urb **urbs, int num_urbs,
				   int input_urb)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv = dev->private;
	struct urb *urb;
	int ret;
	int i;

	/* Submit all URBs and start the transfer on the bus */
	for (i = 0; i < num_urbs; i++) {
		urb = urbs[i];

		/* in case of a resubmission after an unlink... */
		if (input_urb)
			urb->interval = devpriv->ai_interval;
		urb->context = dev;
		urb->dev = usb;
		urb->status = 0;
		urb->transfer_flags = URB_ISO_ASAP;

		ret = usb_submit_urb(urb, GFP_ATOMIC);
		if (ret)
			return ret;
	}
	return 0;
}

static int usbduxsigma_chans_to_interval(int num_chan)
{
	if (num_chan <= 2)
		return 2;	/* 4kHz */
	if (num_chan <= 8)
		return 4;	/* 2kHz */
	return 8;		/* 1kHz */
}

static int usbduxsigma_ai_cmdtest(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  struct comedi_cmd *cmd)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int high_speed = devpriv->high_speed;
	int interval = usbduxsigma_chans_to_interval(cmd->chanlist_len);
	int err = 0;

	/* Step 1 : check if triggers are trivially valid */

	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
	printk("TRIG_NOW | TRIG_INT err=%d, cmd->start_src=%x\n",err,cmd->start_src);
	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
	printk("TRIG_TIMER err=%d, cmd->scan_begin_src=%x\n",err,cmd->scan_begin_src);
	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
	printk("TRIG_NOW err=%d, cmd->convert_src=%x\n",err,cmd->convert_src);
	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
	printk("TRIG_COUNT err=%d, cmd->scan_end_src=%x\n",err,cmd->scan_end_src);
	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
	printk("TRIG_COUNT | TRIG_NONE err=%d, cmd->stop_src=%x\n",err,cmd->stop_src);

	if (err)
	  {
	    printk("Wrong TRIG source\n");
		return 1;
	  }

	/* Step 2a : make sure trigger sources are unique */

	err |= cfc_check_trigger_is_unique(cmd->start_src);
	err |= cfc_check_trigger_is_unique(cmd->stop_src);

	/* Step 2b : and mutually compatible */

	if (err)
	  {
	    printk("Not mutually compatible\n");
		return 2;
	  }

	/* Step 3: check if arguments are trivially valid */

	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);

	if (cmd->scan_begin_src == TRIG_FOLLOW)	/* internal trigger */
		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);

	if (cmd->scan_begin_src == TRIG_TIMER) {
		unsigned int tmp;

		if (high_speed) {
			/*
			 * In high speed mode microframes are possible.
			 * However, during one microframe we can roughly
			 * sample two channels. Thus, the more channels
			 * are in the channel list the more time we need.
			 */
			err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
						(1000000 / 8 * interval));

			tmp = (cmd->scan_begin_arg / 125000) * 125000;
		} else {
			/* full speed */
			/* 1kHz scans every USB frame */
			err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
							 1000000);

			tmp = (cmd->scan_begin_arg / 1000000) * 1000000;
		}
		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);
	}

	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);

	if (cmd->stop_src == TRIG_COUNT) {
		/* any count is allowed */
	} else {
		/* TRIG_NONE */
		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
	}

	if (err)
		return 3;

	/* Step 4: fix up any arguments */

	if (high_speed) {
		/*
		 * every 2 channels get a time window of 125us. Thus, if we
		 * sample all 16 channels we need 1ms. If we sample only one
		 * channel we need only 125us
		 */
		devpriv->ai_interval = interval;
		devpriv->ai_timer = cmd->scan_begin_arg / (125000 * interval);
	} else {
		/* interval always 1ms */
		devpriv->ai_interval = 1;
		devpriv->ai_timer = cmd->scan_begin_arg / 1000000;
	}
	if (devpriv->ai_timer < 1)
	  {
	    printk("ai timer < 1\n");
		err |= -EINVAL;
	  }

	if (cmd->stop_src == TRIG_COUNT) {
		/* data arrives as one packet */
		devpriv->ai_sample_count = cmd->stop_arg;
		devpriv->ai_continuous = 0;
	} else {
		/* continuous acquisition */
		devpriv->ai_continuous = 1;
		devpriv->ai_sample_count = 0;
	}

	if (err)
		return 4;

	return 0;
}

/*
 * creates the ADC command for the MAX1271
 * range is the range value from comedi
 */
static void create_adc_command(unsigned int chan,
			       uint8_t *muxsg0,
			       uint8_t *muxsg1)
{
	if (chan < 8)
		(*muxsg0) = (*muxsg0) | (1 << chan);
	else if (chan < 16)
		(*muxsg1) = (*muxsg1) | (1 << (chan-8));
}

static int usbbuxsigma_send_cmd(struct comedi_device *dev, int cmd_type)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv = dev->private;
	int nsent;

	devpriv->dux_commands[0] = cmd_type;

	return usb_bulk_msg(usb, usb_sndbulkpipe(usb, 1),
			    devpriv->dux_commands, SIZEOFDUXBUFFER,
			    &nsent, BULK_TIMEOUT);
}

static int usbduxsigma_receive_cmd(struct comedi_device *dev, int command)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv = dev->private;
	int nrec;
	int ret;
	int i;

	for (i = 0; i < RETRIES; i++) {
		ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, 8),
				   devpriv->insn_buf, SIZEINSNBUF,
				   &nrec, BULK_TIMEOUT);
		if (ret < 0)
			return ret;

		if (devpriv->insn_buf[0] == command)
			return 0;
	}
	/*
	 * This is only reached if the data has been requested a
	 * couple of times and the command was not received.
	 */
	return -EFAULT;
}

static int usbduxsigma_ai_inttrig(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  unsigned int trignum)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int ret;

	if (trignum != 0)
		return -EINVAL;

	down(&devpriv->sem);
	if (!devpriv->ai_cmd_running) {
		devpriv->ai_cmd_running = 1;
		ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
					      devpriv->n_ai_urbs, 1);
		if (ret < 0) {
			devpriv->ai_cmd_running = 0;
			up(&devpriv->sem);
			return ret;
		}
		s->async->inttrig = NULL;
	}
	up(&devpriv->sem);

	return 1;
}

static int usbduxsigma_ai_cmd(struct comedi_device *dev,
			      struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int len = cmd->chanlist_len;
	uint8_t muxsg0 = 0;
	uint8_t muxsg1 = 0;
	uint8_t sysred = 0;
	int ret;
	int i;

	down(&devpriv->sem);

	/* set current channel of the running acquisition to zero */
	s->async->cur_chan = 0;
	for (i = 0; i < len; i++) {
		unsigned int chan  = CR_CHAN(cmd->chanlist[i]);

		create_adc_command(chan, &muxsg0, &muxsg1);
	}

	devpriv->dux_commands[1] = len;  /* num channels per time step */
	devpriv->dux_commands[2] = 0x12; /* CONFIG0 */
	devpriv->dux_commands[3] = 0x03; /* CONFIG1: 23kHz sample, delay 0us */
	devpriv->dux_commands[4] = 0x00; /* CONFIG3: diff. channels off */
	devpriv->dux_commands[5] = muxsg0;
	devpriv->dux_commands[6] = muxsg1;
	devpriv->dux_commands[7] = sysred;

	ret = usbbuxsigma_send_cmd(dev, USBBUXSIGMA_AD_CMD);
	if (ret < 0) {
		up(&devpriv->sem);
		return ret;
	}

	devpriv->ai_counter = devpriv->ai_timer;

	if (cmd->start_src == TRIG_NOW) {
		/* enable this acquisition operation */
		devpriv->ai_cmd_running = 1;
		ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
					      devpriv->n_ai_urbs, 1);
		if (ret < 0) {
			devpriv->ai_cmd_running = 0;
			up(&devpriv->sem);
			return ret;
		}
		s->async->inttrig = NULL;
	} else {	/* TRIG_INT */
		/* wait for an internal signal and submit the urbs later */
		s->async->inttrig = usbduxsigma_ai_inttrig;
	}

	up(&devpriv->sem);

	return 0;
}

static int usbduxsigma_ai_insn_read(struct comedi_device *dev,
				    struct comedi_subdevice *s,
				    struct comedi_insn *insn,
				    unsigned int *data)
{
	struct usbduxsigma_private *devpriv = dev->private;
	unsigned int chan = CR_CHAN(insn->chanspec);
	uint8_t muxsg0 = 0;
	uint8_t muxsg1 = 0;
	uint8_t sysred = 0;
	int ret;
	int i;

	down(&devpriv->sem);
	if (devpriv->ai_cmd_running) {
		up(&devpriv->sem);
		return -EBUSY;
	}

	create_adc_command(chan, &muxsg0, &muxsg1);

	/* Mode 0 is used to get a single conversion on demand */
	devpriv->dux_commands[1] = 0x16; /* CONFIG0: chopper on */
	devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
	devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
	devpriv->dux_commands[4] = muxsg0;
	devpriv->dux_commands[5] = muxsg1;
	devpriv->dux_commands[6] = sysred;

	/* adc commands */
	ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
	if (ret < 0) {
		up(&devpriv->sem);
		return ret;
	}

	for (i = 0; i < insn->n; i++) {
		uint32_t val;

		ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
		if (ret < 0) {
			up(&devpriv->sem);
			return ret;
		}

		/* 32 bits big endian from the A/D converter */
		val = be32_to_cpu(*((uint32_t *)((devpriv->insn_buf) + 1)));
		val &= 0x00ffffff;	/* strip status byte */
		val ^= 0x00800000;	/* convert to unsigned */

		data[i] = val;
	}
	up(&devpriv->sem);

	return insn->n;
}

static int usbduxsigma_ao_insn_read(struct comedi_device *dev,
				    struct comedi_subdevice *s,
				    struct comedi_insn *insn,
				    unsigned int *data)
{
	struct usbduxsigma_private *devpriv = dev->private;
	unsigned int chan = CR_CHAN(insn->chanspec);
	int i;

	down(&devpriv->sem);
	for (i = 0; i < insn->n; i++)
		data[i] = devpriv->ao_readback[chan];
	up(&devpriv->sem);

	return insn->n;
}

static int usbduxsigma_ao_insn_write(struct comedi_device *dev,
				     struct comedi_subdevice *s,
				     struct comedi_insn *insn,
				     unsigned int *data)
{
	struct usbduxsigma_private *devpriv = dev->private;
	unsigned int chan = CR_CHAN(insn->chanspec);
	int ret;
	int i;

	down(&devpriv->sem);
	if (devpriv->ao_cmd_running) {
		up(&devpriv->sem);
		return -EBUSY;
	}

	for (i = 0; i < insn->n; i++) {
		devpriv->dux_commands[1] = 1;		/* num channels */
		devpriv->dux_commands[2] = data[i];	/* value */
		devpriv->dux_commands[3] = chan;	/* channel number */
		ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DA_CMD);
		if (ret < 0) {
			up(&devpriv->sem);
			return ret;
		}
		devpriv->ao_readback[chan] = data[i];
	}
	up(&devpriv->sem);

	return insn->n;
}

static int usbduxsigma_ao_inttrig(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  unsigned int trignum)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int ret;

	if (trignum != 0)
		return -EINVAL;

	down(&devpriv->sem);
	if (!devpriv->ao_cmd_running) {
		devpriv->ao_cmd_running = 1;
		ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
					      devpriv->n_ao_urbs, 0);
		if (ret < 0) {
			devpriv->ao_cmd_running = 0;
			up(&devpriv->sem);
			return ret;
		}
		s->async->inttrig = NULL;
	}
	up(&devpriv->sem);

	return 1;
}

static int usbduxsigma_ao_cmdtest(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  struct comedi_cmd *cmd)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int err = 0;
	int high_speed;
	unsigned int flags;

	/* high speed conversions are not used yet */
	high_speed = 0;		/* (devpriv->high_speed) */

	/* Step 1 : check if triggers are trivially valid */

	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);

	if (high_speed) {
		/*
		 * start immediately a new scan
		 * the sampling rate is set by the coversion rate
		 */
		flags = TRIG_FOLLOW;
	} else {
		/* start a new scan (output at once) with a timer */
		flags = TRIG_TIMER;
	}
	err |= cfc_check_trigger_src(&cmd->scan_begin_src, flags);

	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);

	if (err) {
		up(&devpriv->sem);
		return 1;
	}

	/* Step 2a : make sure trigger sources are unique */

	err |= cfc_check_trigger_is_unique(cmd->start_src);
	err |= cfc_check_trigger_is_unique(cmd->stop_src);

	/* Step 2b : and mutually compatible */

	if (err)
		return 2;

	/* Step 3: check if arguments are trivially valid */

	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);

	if (cmd->scan_begin_src == TRIG_FOLLOW)	/* internal trigger */
		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);

	if (cmd->scan_begin_src == TRIG_TIMER)
		err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
						 1000000);

	/* not used now, is for later use */
	if (cmd->convert_src == TRIG_TIMER)
		err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 125000);

	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);

	if (cmd->stop_src == TRIG_COUNT) {
		/* any count is allowed */
	} else {
		/* TRIG_NONE */
		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
	}

	if (err)
		return 3;

	/* Step 4: fix up any arguments */

	/* we count in timer steps */
	if (high_speed) {
		/* timing of the conversion itself: every 125 us */
		devpriv->ao_timer = cmd->convert_arg / 125000;
	} else {
		/*
		 * timing of the scan: every 1ms
		 * we get all channels at once
		 */
		devpriv->ao_timer = cmd->scan_begin_arg / 1000000;
	}
	if (devpriv->ao_timer < 1)
		err |= -EINVAL;

	if (cmd->stop_src == TRIG_COUNT) {
		/* not continuous, use counter */
		if (high_speed) {
			/* high speed also scans everything at once */
			devpriv->ao_sample_count = cmd->stop_arg *
						   cmd->scan_end_arg;
		} else {
			/*
			 * There's no scan as the scan has been
			 * handled inside the FX2. Data arrives as
			 * one packet.
			 */
			devpriv->ao_sample_count = cmd->stop_arg;
		}
		devpriv->ao_continuous = 0;
	} else {
		/* continuous acquisition */
		devpriv->ao_continuous = 1;
		devpriv->ao_sample_count = 0;
	}

	if (err)
		return 4;

	return 0;
}

static int usbduxsigma_ao_cmd(struct comedi_device *dev,
			      struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	int ret;
	int i;

	down(&devpriv->sem);

	/* set current channel of the running acquisition to zero */
	s->async->cur_chan = 0;
	for (i = 0; i < cmd->chanlist_len; ++i)
		devpriv->ao_chanlist[i] = CR_CHAN(cmd->chanlist[i]);

	devpriv->ao_counter = devpriv->ao_timer;

	if (cmd->start_src == TRIG_NOW) {
		/* enable this acquisition operation */
		devpriv->ao_cmd_running = 1;
		ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
					      devpriv->n_ao_urbs, 0);
		if (ret < 0) {
			devpriv->ao_cmd_running = 0;
			up(&devpriv->sem);
			return ret;
		}
		s->async->inttrig = NULL;
	} else {	/* TRIG_INT */
		/* wait for an internal signal and submit the urbs later */
		s->async->inttrig = usbduxsigma_ao_inttrig;
	}

	up(&devpriv->sem);

	return 0;
}

static int usbduxsigma_dio_insn_config(struct comedi_device *dev,
				       struct comedi_subdevice *s,
				       struct comedi_insn *insn,
				       unsigned int *data)
{
	int ret;

	ret = comedi_dio_insn_config(dev, s, insn, data, 0);
	if (ret)
		return ret;

	/*
	 * We don't tell the firmware here as it would take 8 frames
	 * to submit the information. We do it in the (*insn_bits).
	 */
	return insn->n;
}

static int usbduxsigma_dio_insn_bits(struct comedi_device *dev,
				     struct comedi_subdevice *s,
				     struct comedi_insn *insn,
				     unsigned int *data)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int ret;

	down(&devpriv->sem);

	comedi_dio_update_state(s, data);

	/* Always update the hardware. See the (*insn_config). */
	devpriv->dux_commands[1] = s->io_bits & 0xff;
	devpriv->dux_commands[4] = s->state & 0xff;
	devpriv->dux_commands[2] = (s->io_bits >> 8) & 0xff;
	devpriv->dux_commands[5] = (s->state >> 8) & 0xff;
	devpriv->dux_commands[3] = (s->io_bits >> 16) & 0xff;
	devpriv->dux_commands[6] = (s->state >> 16) & 0xff;

	ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
	if (ret < 0)
		goto done;
	ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
	if (ret < 0)
		goto done;

	s->state = devpriv->insn_buf[1] |
		   (devpriv->insn_buf[2] << 8) |
		   (devpriv->insn_buf[3] << 16);

	data[1] = s->state;
	ret = insn->n;

done:
	up(&devpriv->sem);

	return ret;
}

static void usbduxsigma_pwm_stop(struct comedi_device *dev, int do_unlink)
{
	struct usbduxsigma_private *devpriv = dev->private;

	if (do_unlink) {
		if (devpriv->pwm_urb)
			usb_kill_urb(devpriv->pwm_urb);
	}

	devpriv->pwm_cmd_running = 0;
}

static int usbduxsigma_pwm_cancel(struct comedi_device *dev,
				  struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;

	/* unlink only if it is really running */
	usbduxsigma_pwm_stop(dev, devpriv->pwm_cmd_running);

	return usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_OFF_CMD);
}

static void usbduxsigma_pwm_urb_complete(struct urb *urb)
{
	struct comedi_device *dev = urb->context;
	struct usbduxsigma_private *devpriv = dev->private;
	int ret;

	switch (urb->status) {
	case 0:
		/* success */
		break;

	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
	case -ECONNABORTED:
		/* happens after an unlink command */
		if (devpriv->pwm_cmd_running)
			usbduxsigma_pwm_stop(dev, 0);	/* w/o unlink */
		return;

	default:
		/* a real error */
		if (devpriv->pwm_cmd_running) {
			dev_err(dev->class_dev,
				"%s: non-zero urb status (%d)\n",
				__func__, urb->status);
			usbduxsigma_pwm_stop(dev, 0);	/* w/o unlink */
		}
		return;
	}

	if (!devpriv->pwm_cmd_running)
		return;

	urb->transfer_buffer_length = devpriv->pwm_buf_sz;
	urb->dev = comedi_to_usb_dev(dev);
	urb->status = 0;
	ret = usb_submit_urb(urb, GFP_ATOMIC);
	if (ret < 0) {
		dev_err(dev->class_dev, "%s: urb resubmit failed (%d)\n",
			__func__, ret);
		if (ret == EL2NSYNC)
			dev_err(dev->class_dev,
				"buggy USB host controller or bug in IRQ handler\n");
		usbduxsigma_pwm_stop(dev, 0);	/* w/o unlink */
	}
}

static int usbduxsigma_submit_pwm_urb(struct comedi_device *dev)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv = dev->private;
	struct urb *urb = devpriv->pwm_urb;

	/* in case of a resubmission after an unlink... */
	usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4),
			  urb->transfer_buffer, devpriv->pwm_buf_sz,
			  usbduxsigma_pwm_urb_complete, dev);

	return usb_submit_urb(urb, GFP_ATOMIC);
}

static int usbduxsigma_pwm_period(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  unsigned int period)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int fx2delay = 255;

	if (period < MIN_PWM_PERIOD) {
		return -EAGAIN;
	} else {
		fx2delay = (period / (6 * 512 * 1000 / 33)) - 6;
		if (fx2delay > 255)
			return -EAGAIN;
	}
	devpriv->pwm_delay = fx2delay;
	devpriv->pwm_period = period;
	return 0;
}

static int usbduxsigma_pwm_start(struct comedi_device *dev,
				 struct comedi_subdevice *s)
{
	struct usbduxsigma_private *devpriv = dev->private;
	int ret;

	if (devpriv->pwm_cmd_running)
		return 0;

	devpriv->dux_commands[1] = devpriv->pwm_delay;
	ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_ON_CMD);
	if (ret < 0)
		return ret;

	memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz);

	devpriv->pwm_cmd_running = 1;
	ret = usbduxsigma_submit_pwm_urb(dev);
	if (ret < 0) {
		devpriv->pwm_cmd_running = 0;
		return ret;
	}

	return 0;
}

static void usbduxsigma_pwm_pattern(struct comedi_device *dev,
				    struct comedi_subdevice *s,
				    unsigned int chan,
				    unsigned int value,
				    unsigned int sign)
{
	struct usbduxsigma_private *devpriv = dev->private;
	char pwm_mask = (1 << chan);	/* DIO bit for the PWM data */
	char sgn_mask = (16 << chan);	/* DIO bit for the sign */
	char *buf = (char *)(devpriv->pwm_urb->transfer_buffer);
	int szbuf = devpriv->pwm_buf_sz;
	int i;

	for (i = 0; i < szbuf; i++) {
		char c = *buf;

		c &= ~pwm_mask;
		if (i < value)
			c |= pwm_mask;
		if (!sign)
			c &= ~sgn_mask;
		else
			c |= sgn_mask;
		*buf++ = c;
	}
}

static int usbduxsigma_pwm_write(struct comedi_device *dev,
				 struct comedi_subdevice *s,
				 struct comedi_insn *insn,
				 unsigned int *data)
{
	unsigned int chan = CR_CHAN(insn->chanspec);

	/*
	 * It doesn't make sense to support more than one value here
	 * because it would just overwrite the PWM buffer.
	 */
	if (insn->n != 1)
		return -EINVAL;

	/*
	 * The sign is set via a special INSN only, this gives us 8 bits
	 * for normal operation, sign is 0 by default.
	 */
	usbduxsigma_pwm_pattern(dev, s, chan, data[0], 0);

	return insn->n;
}

static int usbduxsigma_pwm_config(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  struct comedi_insn *insn,
				  unsigned int *data)
{
	struct usbduxsigma_private *devpriv = dev->private;
	unsigned int chan = CR_CHAN(insn->chanspec);

	switch (data[0]) {
	case INSN_CONFIG_ARM:
		/*
		 * if not zero the PWM is limited to a certain time which is
		 * not supported here
		 */
		if (data[1] != 0)
			return -EINVAL;
		return usbduxsigma_pwm_start(dev, s);
	case INSN_CONFIG_DISARM:
		return usbduxsigma_pwm_cancel(dev, s);
	case INSN_CONFIG_GET_PWM_STATUS:
		data[1] = devpriv->pwm_cmd_running;
		return 0;
	case INSN_CONFIG_PWM_SET_PERIOD:
		return usbduxsigma_pwm_period(dev, s, data[1]);
	case INSN_CONFIG_PWM_GET_PERIOD:
		data[1] = devpriv->pwm_period;
		return 0;
	case INSN_CONFIG_PWM_SET_H_BRIDGE:
		/*
		 * data[1] = value
		 * data[2] = sign (for a relay)
		 */
		usbduxsigma_pwm_pattern(dev, s, chan, data[1], (data[2] != 0));
		return 0;
	case INSN_CONFIG_PWM_GET_H_BRIDGE:
		/* values are not kept in this driver, nothing to return */
		return -EINVAL;
	}
	return -EINVAL;
}

static int usbduxsigma_getstatusinfo(struct comedi_device *dev, int chan)
{
	struct usbduxsigma_private *devpriv = dev->private;
	uint8_t sysred;
	uint32_t val;
	int ret;

	switch (chan) {
	default:
	case 0:
		sysred = 0;		/* ADC zero */
		break;
	case 1:
		sysred = 1;		/* ADC offset */
		break;
	case 2:
		sysred = 4;		/* VCC */
		break;
	case 3:
		sysred = 8;		/* temperature */
		break;
	case 4:
		sysred = 16;		/* gain */
		break;
	case 5:
		sysred =  32;		/* ref */
		break;
	}

	devpriv->dux_commands[1] = 0x12; /* CONFIG0 */
	devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
	devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
	devpriv->dux_commands[4] = 0;
	devpriv->dux_commands[5] = 0;
	devpriv->dux_commands[6] = sysred;
	ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
	if (ret < 0)
		return ret;

	ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
	if (ret < 0)
		return ret;

	/* 32 bits big endian from the A/D converter */
	val = be32_to_cpu(*((uint32_t *)((devpriv->insn_buf)+1)));
	val &= 0x00ffffff;	/* strip status byte */
	val ^= 0x00800000;	/* convert to unsigned */

	return (int)val;
}

static int usbduxsigma_firmware_upload(struct comedi_device *dev,
				       const u8 *data, size_t size,
				       unsigned long context)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	uint8_t *buf;
	uint8_t *tmp;
	int ret;

	if (!data)
		return 0;

	if (size > FIRMWARE_MAX_LEN) {
		dev_err(dev->class_dev, "firmware binary too large for FX2\n");
		return -ENOMEM;
	}

	/* we generate a local buffer for the firmware */
	buf = kmemdup(data, size, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	/* we need a malloc'ed buffer for usb_control_msg() */
	tmp = kmalloc(1, GFP_KERNEL);
	if (!tmp) {
		kfree(buf);
		return -ENOMEM;
	}

	/* stop the current firmware on the device */
	*tmp = 1;	/* 7f92 to one */
	ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
			      USBDUXSUB_FIRMWARE,
			      VENDOR_DIR_OUT,
			      USBDUXSUB_CPUCS, 0x0000,
			      tmp, 1,
			      BULK_TIMEOUT);
	if (ret < 0) {
		dev_err(dev->class_dev, "can not stop firmware\n");
		goto done;
	}

	/* upload the new firmware to the device */
	ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
			      USBDUXSUB_FIRMWARE,
			      VENDOR_DIR_OUT,
			      0, 0x0000,
			      buf, size,
			      BULK_TIMEOUT);
	if (ret < 0) {
		dev_err(dev->class_dev, "firmware upload failed\n");
		goto done;
	}

	/* start the new firmware on the device */
	*tmp = 0;	/* 7f92 to zero */
	ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
			      USBDUXSUB_FIRMWARE,
			      VENDOR_DIR_OUT,
			      USBDUXSUB_CPUCS, 0x0000,
			      tmp, 1,
			      BULK_TIMEOUT);
	if (ret < 0)
		dev_err(dev->class_dev, "can not start firmware\n");

done:
	kfree(tmp);
	kfree(buf);
	return ret;
}

static int usbduxsigma_alloc_usb_buffers(struct comedi_device *dev)
{
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv = dev->private;
	struct urb *urb;
	int i;

	devpriv->dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL);
	devpriv->in_buf = kzalloc(SIZEINBUF, GFP_KERNEL);
	devpriv->insn_buf = kzalloc(SIZEINSNBUF, GFP_KERNEL);
	devpriv->ai_urbs = kcalloc(devpriv->n_ai_urbs, sizeof(*urb),
				   GFP_KERNEL);
	devpriv->ao_urbs = kcalloc(devpriv->n_ao_urbs, sizeof(*urb),
				   GFP_KERNEL);
	if (!devpriv->dux_commands || !devpriv->in_buf || !devpriv->insn_buf ||
	    !devpriv->ai_urbs || !devpriv->ao_urbs)
		return -ENOMEM;

	for (i = 0; i < devpriv->n_ai_urbs; i++) {
		/* one frame: 1ms */
		urb = usb_alloc_urb(1, GFP_KERNEL);
		if (!urb)
			return -ENOMEM;
		devpriv->ai_urbs[i] = urb;
		urb->dev = usb;
		/* will be filled later with a pointer to the comedi-device */
		/* and ONLY then the urb should be submitted */
		urb->context = NULL;
		urb->pipe = usb_rcvisocpipe(usb, 6);
		urb->transfer_flags = URB_ISO_ASAP;
		urb->transfer_buffer = kzalloc(SIZEINBUF, GFP_KERNEL);
		if (!urb->transfer_buffer)
			return -ENOMEM;
		urb->complete = usbduxsigma_ai_urb_complete;
		urb->number_of_packets = 1;
		urb->transfer_buffer_length = SIZEINBUF;
		urb->iso_frame_desc[0].offset = 0;
		urb->iso_frame_desc[0].length = SIZEINBUF;
	}

	for (i = 0; i < devpriv->n_ao_urbs; i++) {
		/* one frame: 1ms */
		urb = usb_alloc_urb(1, GFP_KERNEL);
		if (!urb)
			return -ENOMEM;
		devpriv->ao_urbs[i] = urb;
		urb->dev = usb;
		/* will be filled later with a pointer to the comedi-device */
		/* and ONLY then the urb should be submitted */
		urb->context = NULL;
		urb->pipe = usb_sndisocpipe(usb, 2);
		urb->transfer_flags = URB_ISO_ASAP;
		urb->transfer_buffer = kzalloc(SIZEOUTBUF, GFP_KERNEL);
		if (!urb->transfer_buffer)
			return -ENOMEM;
		urb->complete = usbduxsigma_ao_urb_complete;
		urb->number_of_packets = 1;
		urb->transfer_buffer_length = SIZEOUTBUF;
		urb->iso_frame_desc[0].offset = 0;
		urb->iso_frame_desc[0].length = SIZEOUTBUF;
		if (devpriv->high_speed)
			urb->interval = 8;	/* uframes */
		else
			urb->interval = 1;	/* frames */
	}

	if (devpriv->pwm_buf_sz) {
		urb = usb_alloc_urb(0, GFP_KERNEL);
		if (!urb)
			return -ENOMEM;
		devpriv->pwm_urb = urb;

		urb->transfer_buffer = kzalloc(devpriv->pwm_buf_sz,
					       GFP_KERNEL);
		if (!urb->transfer_buffer)
			return -ENOMEM;
	}

	return 0;
}

static void usbduxsigma_free_usb_buffers(struct comedi_device *dev)
{
	struct usbduxsigma_private *devpriv = dev->private;
	struct urb *urb;
	int i;

	urb = devpriv->pwm_urb;
	if (urb) {
		kfree(urb->transfer_buffer);
		usb_free_urb(urb);
	}
	if (devpriv->ao_urbs) {
		for (i = 0; i < devpriv->n_ao_urbs; i++) {
			urb = devpriv->ao_urbs[i];
			if (urb) {
				kfree(urb->transfer_buffer);
				usb_free_urb(urb);
			}
		}
		kfree(devpriv->ao_urbs);
	}
	if (devpriv->ai_urbs) {
		for (i = 0; i < devpriv->n_ai_urbs; i++) {
			urb = devpriv->ai_urbs[i];
			if (urb) {
				kfree(urb->transfer_buffer);
				usb_free_urb(urb);
			}
		}
		kfree(devpriv->ai_urbs);
	}
	kfree(devpriv->insn_buf);
	kfree(devpriv->in_buf);
	kfree(devpriv->dux_commands);
}

static int usbduxsigma_auto_attach(struct comedi_device *dev,
				   unsigned long context_unused)
{
	struct usb_interface *intf = comedi_to_usb_interface(dev);
	struct usb_device *usb = comedi_to_usb_dev(dev);
	struct usbduxsigma_private *devpriv;
	struct comedi_subdevice *s;
	int offset;
	int ret;

	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
	if (!devpriv)
		return -ENOMEM;

	sema_init(&devpriv->sem, 1);

	usb_set_intfdata(intf, devpriv);

	devpriv->high_speed = (usb->speed == USB_SPEED_HIGH);
	if (devpriv->high_speed) {
		devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH;
		devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH;
		devpriv->pwm_buf_sz = 512;
	} else {
		devpriv->n_ai_urbs = NUMOFINBUFFERSFULL;
		devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL;
	}

	ret = usbduxsigma_alloc_usb_buffers(dev);
	if (ret)
		return ret;

	/* setting to alternate setting 3: enabling iso ep and bulk ep. */
	ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber,
				3);
	if (ret < 0) {
		dev_err(dev->class_dev,
			"could not set alternate setting 3 in high speed\n");
		return ret;
	}

	ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE,
				   usbduxsigma_firmware_upload, 0);
	if (ret < 0)
		return ret;

	ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 4 : 3);
	if (ret)
		return ret;

	/* Analog Input subdevice */
	s = &dev->subdevices[0];
	dev->read_subdev = s;
	s->type		= COMEDI_SUBD_AI;
	s->subdev_flags	= SDF_READABLE | SDF_GROUND | SDF_CMD_READ | SDF_LSAMPL;
	s->n_chan	= NUMCHANNELS;
	s->len_chanlist	= NUMCHANNELS;
	s->maxdata	= 0x00ffffff;
	s->range_table	= &usbduxsigma_ai_range;
	s->insn_read	= usbduxsigma_ai_insn_read;
	s->do_cmdtest	= usbduxsigma_ai_cmdtest;
	s->do_cmd	= usbduxsigma_ai_cmd;
	s->cancel	= usbduxsigma_ai_cancel;

	/* Analog Output subdevice */
	s = &dev->subdevices[1];
	dev->write_subdev = s;
	s->type		= COMEDI_SUBD_AO;
	s->subdev_flags	= SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
	s->n_chan	= USBDUXSIGMA_NUM_AO_CHAN;
	s->len_chanlist	= s->n_chan;
	s->maxdata	= 0x00ff;
	s->range_table	= &range_unipolar2_5;
	s->insn_write	= usbduxsigma_ao_insn_write;
	s->insn_read	= usbduxsigma_ao_insn_read;
	s->do_cmdtest	= usbduxsigma_ao_cmdtest;
	s->do_cmd	= usbduxsigma_ao_cmd;
	s->cancel	= usbduxsigma_ao_cancel;

	/* Digital I/O subdevice */
	s = &dev->subdevices[2];
	s->type		= COMEDI_SUBD_DIO;
	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
	s->n_chan	= 24;
	s->maxdata	= 1;
	s->range_table	= &range_digital;
	s->insn_bits	= usbduxsigma_dio_insn_bits;
	s->insn_config	= usbduxsigma_dio_insn_config;

	if (devpriv->high_speed) {
		/* Timer / pwm subdevice */
		s = &dev->subdevices[3];
		s->type		= COMEDI_SUBD_PWM;
		s->subdev_flags	= SDF_WRITABLE | SDF_PWM_HBRIDGE;
		s->n_chan	= 8;
		s->maxdata	= devpriv->pwm_buf_sz;
		s->insn_write	= usbduxsigma_pwm_write;
		s->insn_config	= usbduxsigma_pwm_config;

		usbduxsigma_pwm_period(dev, s, PWM_DEFAULT_PERIOD);
	}

	offset = usbduxsigma_getstatusinfo(dev, 0);
	if (offset < 0)
		dev_err(dev->class_dev,
			"Communication to USBDUXSIGMA failed! Check firmware and cabling\n");

	dev_info(dev->class_dev, "attached, ADC_zero = %x\n", offset);

	return 0;
}

static void usbduxsigma_detach(struct comedi_device *dev)
{
	struct usb_interface *intf = comedi_to_usb_interface(dev);
	struct usbduxsigma_private *devpriv = dev->private;

	usb_set_intfdata(intf, NULL);

	if (!devpriv)
		return;

	down(&devpriv->sem);

	/* force unlink all urbs */
	usbduxsigma_ai_stop(dev, 1);
	usbduxsigma_ao_stop(dev, 1);
	usbduxsigma_pwm_stop(dev, 1);

	usbduxsigma_free_usb_buffers(dev);

	up(&devpriv->sem);
}

static struct comedi_driver usbduxsigma_driver = {
	.driver_name	= "usbduxsigma",
	.module		= THIS_MODULE,
	.auto_attach	= usbduxsigma_auto_attach,
	.detach		= usbduxsigma_detach,
};

static int usbduxsigma_usb_probe(struct usb_interface *intf,
				 const struct usb_device_id *id)
{
	return comedi_usb_auto_config(intf, &usbduxsigma_driver, 0);
}

static const struct usb_device_id usbduxsigma_usb_table[] = {
	{ USB_DEVICE(0x13d8, 0x0020) },
	{ USB_DEVICE(0x13d8, 0x0021) },
	{ USB_DEVICE(0x13d8, 0x0022) },
	{ }
};
MODULE_DEVICE_TABLE(usb, usbduxsigma_usb_table);

static struct usb_driver usbduxsigma_usb_driver = {
	.name		= "usbduxsigma",
	.probe		= usbduxsigma_usb_probe,
	.disconnect	= comedi_usb_auto_unconfig,
	.id_table	= usbduxsigma_usb_table,
};
module_comedi_usb_driver(usbduxsigma_driver, usbduxsigma_usb_driver);

MODULE_AUTHOR("Bernd Porr, BerndPorr@xxxxxxx");
MODULE_DESCRIPTION("Stirling/ITL USB-DUX SIGMA -- Bernd.Porr@xxxxxxx");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(FIRMWARE);
/*
    lib/cmd.c
    support functions for commands

    COMEDILIB - Linux Control and Measurement Device Interface Library
    Copyright (C) 1997-2001 David A. Schleef <ds@xxxxxxxxxxx>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation, version 2.1
    of the License.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
    USA.
*/

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <math.h>
#include <sys/time.h>
#include <string.h>
#include <stdlib.h>

#include "libinternal.h"


EXPORT_ALIAS_DEFAULT(_comedi_get_cmd_src_mask,comedi_get_cmd_src_mask,0.7.18);
int _comedi_get_cmd_src_mask(comedi_t *it,unsigned int subd,comedi_cmd *cmd)
{
	subdevice *s;
	int ret;

	if(!valid_subd(it,subd))return -1;

	s=it->subdevices+subd;

	if(s->cmd_mask_errno){
	  printf("Mask error\n");
		errno = s->cmd_mask_errno;
		return -1;
	}

	if(!s->cmd_mask){
		comedi_cmd *mask;

		printf("No mask\n");

		mask = malloc(sizeof(comedi_cmd));
		if(!mask){
			libc_error();
			return -1;
		}

		memset(mask,0,sizeof(*cmd));

		mask->subdev = subd;
		mask->flags = 0;
		mask->start_src = TRIG_ANY;
		mask->scan_begin_src = TRIG_ANY;
		mask->convert_src = TRIG_ANY;
		mask->scan_end_src = TRIG_ANY;
		mask->stop_src = TRIG_ANY;

		s->cmd_mask = mask;

		ret = comedi_command_test(it,mask);
		printf("comedi_command_test in comedi_get_cmd_src_mask, ret=%d\n",ret);
		if(ret<0){
		  printf("comedi command test failed\n");
			s->cmd_mask_errno = errno;
			return -1;
		}
	}
	*cmd=*s->cmd_mask;
	return 0;
}

static int __generic_timed(comedi_t *it, unsigned s,
	comedi_cmd *cmd, unsigned chanlist_len, unsigned scan_period_ns)
{
	int ret;
	unsigned convert_period_ns;

	if(chanlist_len < 1) return -EINVAL;
	ret = comedi_get_cmd_src_mask(it,s,cmd);

	printf("Mask set\n");

	if(ret<0)return ret;

	__comedi_errno = ENOTSUPPORTED;

	if(cmd->start_src&TRIG_NOW){
		cmd->start_src=TRIG_NOW;
		cmd->start_arg=0;
	}else if(cmd->start_src&TRIG_INT){
		cmd->start_src=TRIG_INT;
		cmd->start_arg=0;
	}else{
		printf("can't find good start_src\n");
		return -1;
	}

	convert_period_ns = (scan_period_ns + chanlist_len / 2) / chanlist_len;
	if((cmd->convert_src & TRIG_TIMER) &&
		(cmd->scan_begin_src & TRIG_FOLLOW))
	{
		cmd->convert_src = TRIG_TIMER;
		cmd->convert_arg = convert_period_ns;
		cmd->scan_begin_src = TRIG_FOLLOW;
		cmd->scan_begin_arg = 0;
	}else if((cmd->convert_src & TRIG_NOW) &&
		(cmd->scan_begin_src & TRIG_TIMER))
	{
		cmd->convert_src = TRIG_NOW;
		cmd->convert_arg = 0;
		cmd->scan_begin_src = TRIG_TIMER;
		cmd->scan_begin_arg = scan_period_ns;
	}else if((cmd->convert_src & TRIG_TIMER) &&
		(cmd->scan_begin_src & TRIG_TIMER))
	{
		cmd->convert_src = TRIG_TIMER;
		cmd->convert_arg = convert_period_ns;
		cmd->scan_begin_src = TRIG_TIMER;
		cmd->scan_begin_arg = scan_period_ns;
	}else{
		printf("comedi_get_cmd_generic_timed: can't do timed?\n");
		return -1;
	}

	cmd->scan_end_src = TRIG_COUNT;
	cmd->scan_end_arg = chanlist_len;

	if(cmd->stop_src&TRIG_COUNT){
		cmd->stop_src=TRIG_COUNT;
		cmd->stop_arg=2;
	}else if(cmd->stop_src&TRIG_NONE){
		cmd->stop_src=TRIG_NONE;
		cmd->stop_arg=0;
	}else{
		printf("comedi_get_cmd_generic_timed: can't find a good stop_src\n");
		return -1;
	}

	cmd->chanlist_len = chanlist_len;

	ret=comedi_command_test(it,cmd);
	printf("comedi_get_cmd_generic_timed: test 1 returned %d\n",ret);
	if(ret==3){
		/* good */
		ret=comedi_command_test(it,cmd);
		printf("comedi_get_cmd_generic_timed: test 2 returned %d\n",ret);
	}
	if(ret==4 || ret==0){
		__comedi_errno = 0;
		return 0;
	}
	return -1;
}

EXPORT_ALIAS_VER(_comedi_get_cmd_generic_timed_obsolete,comedi_get_cmd_generic_timed,0.7.18);
int _comedi_get_cmd_generic_timed_obsolete(comedi_t *it,unsigned int subd,comedi_cmd *cmd,
	unsigned int ns)
{
	if(!valid_subd(it,subd))return -1;
	return __generic_timed(it, subd, cmd, 1, ns);
}

EXPORT_ALIAS_DEFAULT(_comedi_get_cmd_generic_timed,comedi_get_cmd_generic_timed,0.9.0);
int _comedi_get_cmd_generic_timed(comedi_t *it, unsigned subd, comedi_cmd *cmd,
	unsigned chanlist_len, unsigned scan_period_ns)
{
	if(!valid_subd(it,subd)) return -1;
	return __generic_timed(it, subd, cmd, chanlist_len, scan_period_ns);
}

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux