root/drivers/comedi/drivers/usbduxsigma.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * usbduxsigma.c
 * Copyright (C) 2011-2015 Bernd Porr, mail@berndporr.me.uk
 */

/*
 * Driver: usbduxsigma
 * Description: University of Stirling USB DAQ & INCITE Technology Limited
 * Devices: [ITL] USB-DUX-SIGMA (usbduxsigma)
 * Author: Bernd Porr <mail@berndporr.me.uk>
 * Updated: 20 July 2015
 * Status: stable
 */

/*
 * 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
 *   0.7: rewrite code that urb->interval is always 1
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/fcntl.h>
#include <linux/compiler.h>
#include <linux/unaligned.h>
#include <linux/comedi/comedi_usb.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

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

/*
 * 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(u8) + 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              9
#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.5 * 0x800000 / 0x780000 / 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 */
        u8 pwm_delay;
        /* size of the PWM buffer which holds the bit pattern */
        int pwm_buf_sz;
        /* input buffer for the ISO-transfer */
        __be32 *in_buf;
        /* input buffer for single insn */
        u8 *insn_buf;

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

        /* 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 */
        u8 *dux_commands;
        struct mutex mut;
};

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;

        mutex_lock(&devpriv->mut);
        /* unlink only if it is really running */
        usbduxsigma_ai_stop(dev, devpriv->ai_cmd_running);
        mutex_unlock(&devpriv->mut);

        return 0;
}

static void usbduxsigma_ai_handle_urb(struct comedi_device *dev,
                                      struct comedi_subdevice *s,
                                      struct urb *urb)
{
        struct usbduxsigma_private *devpriv = dev->private;
        struct comedi_async *async = s->async;
        struct comedi_cmd *cmd = &async->cmd;
        u32 val;
        int ret;
        int i;

        if ((urb->actual_length > 0) && (urb->status != -EXDEV)) {
                devpriv->ai_counter--;
                if (devpriv->ai_counter == 0) {
                        devpriv->ai_counter = devpriv->ai_timer;

                        /*
                         * Get the data from the USB bus and hand it over
                         * to comedi. Note, first byte is the DIO state.
                         */
                        for (i = 0; i < cmd->chanlist_len; i++) {
                                val = be32_to_cpu(devpriv->in_buf[i + 1]);
                                val &= 0x00ffffff; /* strip status byte */
                                val = comedi_offset_munge(s, val);
                                if (!comedi_buf_write_samples(s, &val, 1))
                                        return;
                        }

                        if (cmd->stop_src == TRIG_COUNT &&
                            async->scans_done >= cmd->stop_arg)
                                async->events |= COMEDI_CB_EOA;
                }
        }

        /* if command is still running, resubmit urb */
        if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
                urb->dev = comedi_to_usb_dev(dev);
                ret = usb_submit_urb(urb, GFP_ATOMIC);
                if (ret < 0) {
                        dev_err(dev->class_dev, "urb resubmit failed (%d)\n",
                                ret);
                        if (ret == -EL2NSYNC)
                                dev_err(dev->class_dev,
                                        "buggy USB host controller or bug in IRQ handler\n");
                        async->events |= COMEDI_CB_ERROR;
                }
        }
}

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;
        struct comedi_async *async = s->async;

        /* exit if not running a command, do not resubmit urb */
        if (!devpriv->ai_cmd_running)
                return;

        switch (urb->status) {
        case 0:
                /* copy the result in the transfer buffer */
                memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF);
                usbduxsigma_ai_handle_urb(dev, s, urb);
                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");
                usbduxsigma_ai_handle_urb(dev, s, urb);
                break;

        case -ECONNRESET:
        case -ENOENT:
        case -ESHUTDOWN:
        case -ECONNABORTED:
                /* happens after an unlink command */
                async->events |= COMEDI_CB_ERROR;
                break;

        default:
                /* a real error */
                dev_err(dev->class_dev, "non-zero urb status (%d)\n",
                        urb->status);
                async->events |= COMEDI_CB_ERROR;
                break;
        }

        /*
         * comedi_handle_events() cannot be used in this driver. The (*cancel)
         * operation would unlink the urb.
         */
        if (async->events & COMEDI_CB_CANCEL_MASK)
                usbduxsigma_ai_stop(dev, 0);

        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;

        mutex_lock(&devpriv->mut);
        /* unlink only if it is really running */
        usbduxsigma_ao_stop(dev, devpriv->ao_cmd_running);
        mutex_unlock(&devpriv->mut);

        return 0;
}

static void usbduxsigma_ao_handle_urb(struct comedi_device *dev,
                                      struct comedi_subdevice *s,
                                      struct urb *urb)
{
        struct usbduxsigma_private *devpriv = dev->private;
        struct comedi_async *async = s->async;
        struct comedi_cmd *cmd = &async->cmd;
        u8 *datap;
        int ret;
        int i;

        devpriv->ao_counter--;
        if (devpriv->ao_counter == 0) {
                devpriv->ao_counter = devpriv->ao_timer;

                if (cmd->stop_src == TRIG_COUNT &&
                    async->scans_done >= cmd->stop_arg) {
                        async->events |= COMEDI_CB_EOA;
                        return;
                }

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

                        if (!comedi_buf_read_samples(s, &val, 1)) {
                                dev_err(dev->class_dev, "buffer underflow\n");
                                async->events |= COMEDI_CB_OVERFLOW;
                                return;
                        }

                        *datap++ = val;
                        *datap++ = chan;
                        s->readback[chan] = val;
                }
        }

        /* if command is still running, resubmit urb */
        if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
                urb->transfer_buffer_length = SIZEOUTBUF;
                urb->dev = comedi_to_usb_dev(dev);
                urb->status = 0;
                urb->interval = 1;      /* (u)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, "urb resubmit failed (%d)\n",
                                ret);
                        if (ret == -EL2NSYNC)
                                dev_err(dev->class_dev,
                                        "buggy USB host controller or bug in IRQ handler\n");
                        async->events |= COMEDI_CB_ERROR;
                }
        }
}

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;
        struct comedi_async *async = s->async;

        /* exit if not running a command, do not resubmit urb */
        if (!devpriv->ao_cmd_running)
                return;

        switch (urb->status) {
        case 0:
                usbduxsigma_ao_handle_urb(dev, s, urb);
                break;

        case -ECONNRESET:
        case -ENOENT:
        case -ESHUTDOWN:
        case -ECONNABORTED:
                /* happens after an unlink command */
                async->events |= COMEDI_CB_ERROR;
                break;

        default:
                /* a real error */
                dev_err(dev->class_dev, "non-zero urb status (%d)\n",
                        urb->status);
                async->events |= COMEDI_CB_ERROR;
                break;
        }

        /*
         * comedi_handle_events() cannot be used in this driver. The (*cancel)
         * operation would unlink the urb.
         */
        if (async->events & COMEDI_CB_CANCEL_MASK)
                usbduxsigma_ao_stop(dev, 0);

        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 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 = 1;
                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);
        unsigned int tmp;
        int err = 0;

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

        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);

        if (err)
                return 1;

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

        err |= comedi_check_trigger_is_unique(cmd->start_src);
        err |= comedi_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 |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);

        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 |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
                                                    (125000 * interval));
        } else {
                /* full speed */
                /* 1kHz scans every USB frame */
                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
                                                    1000000);
        }

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

        if (cmd->stop_src == TRIG_COUNT)
                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
        else    /* TRIG_NONE */
                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);

        if (err)
                return 3;

        /* Step 4: fix up any arguments */

        tmp = rounddown(cmd->scan_begin_arg, high_speed ? 125000 : 1000000);
        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);

        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,
                               u8 *muxsg0, u8 *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 trig_num)
{
        struct usbduxsigma_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        int ret;

        if (trig_num != cmd->start_arg)
                return -EINVAL;

        mutex_lock(&devpriv->mut);
        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;
                        mutex_unlock(&devpriv->mut);
                        return ret;
                }
                s->async->inttrig = NULL;
        }
        mutex_unlock(&devpriv->mut);

        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;
        u8 muxsg0 = 0;
        u8 muxsg1 = 0;
        u8 sysred = 0;
        int ret;
        int i;

        mutex_lock(&devpriv->mut);

        if (devpriv->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
                 */
                unsigned int interval = usbduxsigma_chans_to_interval(len);

                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;
        }

        for (i = 0; i < len; i++) {
                unsigned int chan  = CR_CHAN(cmd->chanlist[i]);

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

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

        ret = usbbuxsigma_send_cmd(dev, USBBUXSIGMA_AD_CMD);
        if (ret < 0) {
                mutex_unlock(&devpriv->mut);
                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;
                        mutex_unlock(&devpriv->mut);
                        return ret;
                }
                s->async->inttrig = NULL;
        } else {        /* TRIG_INT */
                s->async->inttrig = usbduxsigma_ai_inttrig;
        }

        mutex_unlock(&devpriv->mut);

        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);
        u8 muxsg0 = 0;
        u8 muxsg1 = 0;
        u8 sysred = 0;
        int ret;
        int i;

        mutex_lock(&devpriv->mut);
        if (devpriv->ai_cmd_running) {
                mutex_unlock(&devpriv->mut);
                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) {
                mutex_unlock(&devpriv->mut);
                return ret;
        }

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

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

                /* 32 bits big endian from the A/D converter */
                val = be32_to_cpu(get_unaligned((__be32
                                                 *)(devpriv->insn_buf + 1)));
                val &= 0x00ffffff;      /* strip status byte */
                data[i] = comedi_offset_munge(s, val);
        }
        mutex_unlock(&devpriv->mut);

        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;
        int ret;

        mutex_lock(&devpriv->mut);
        ret = comedi_readback_insn_read(dev, s, insn, data);
        mutex_unlock(&devpriv->mut);

        return ret;
}

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;

        mutex_lock(&devpriv->mut);
        if (devpriv->ao_cmd_running) {
                mutex_unlock(&devpriv->mut);
                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) {
                        mutex_unlock(&devpriv->mut);
                        return ret;
                }
                s->readback[chan] = data[i];
        }
        mutex_unlock(&devpriv->mut);

        return insn->n;
}

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

        if (trig_num != cmd->start_arg)
                return -EINVAL;

        mutex_lock(&devpriv->mut);
        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;
                        mutex_unlock(&devpriv->mut);
                        return ret;
                }
                s->async->inttrig = NULL;
        }
        mutex_unlock(&devpriv->mut);

        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;
        unsigned int tmp;
        int err = 0;

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

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

        /*
         * For now, always use "scan" timing with all channels updated at once
         * (cmd->scan_begin_src == TRIG_TIMER, cmd->convert_src == TRIG_NOW).
         *
         * In a future version, "convert" timing with channels updated
         * indivually may be supported in high speed mode
         * (cmd->scan_begin_src == TRIG_FOLLOW, cmd->convert_src == TRIG_TIMER).
         */
        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);

        if (err) {
                mutex_unlock(&devpriv->mut);
                return 1;
        }

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

        err |= comedi_check_trigger_is_unique(cmd->start_src);
        err |= comedi_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 |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);

        err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 1000000);

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

        if (cmd->stop_src == TRIG_COUNT)
                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
        else    /* TRIG_NONE */
                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);

        if (err)
                return 3;

        /* Step 4: fix up any arguments */

        tmp = rounddown(cmd->scan_begin_arg, 1000000);
        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);

        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;

        mutex_lock(&devpriv->mut);

        /*
         * For now, only "scan" timing is supported.  A future version may
         * support "convert" timing in high speed mode.
         *
         * Timing of the scan: every 1ms all channels updated at once.
         */
        devpriv->ao_timer = cmd->scan_begin_arg / 1000000;

        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;
                        mutex_unlock(&devpriv->mut);
                        return ret;
                }
                s->async->inttrig = NULL;
        } else {        /* TRIG_INT */
                s->async->inttrig = usbduxsigma_ao_inttrig;
        }

        mutex_unlock(&devpriv->mut);

        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;

        mutex_lock(&devpriv->mut);

        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:
        mutex_unlock(&devpriv->mut);

        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, "non-zero urb status (%d)\n",
                                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, "urb resubmit failed (%d)\n", 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;

        if (period < MIN_PWM_PERIOD)
                return -EAGAIN;

        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 comedi_subdevice *s = dev->read_subdev;
        struct usbduxsigma_private *devpriv = dev->private;
        u8 sysred;
        u32 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(get_unaligned((__be32 *)(devpriv->insn_buf + 1)));
        val &= 0x00ffffff;      /* strip status byte */

        return (int)comedi_offset_munge(s, 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);
        u8 *buf;
        u8 *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 = kzalloc_objs(urb, devpriv->n_ai_urbs);
        devpriv->ao_urbs = kzalloc_objs(urb, devpriv->n_ao_urbs);
        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;
                urb->interval = 1;      /* (u)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;

        mutex_init(&devpriv->mut);

        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)
                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       = 4;
        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;

        ret = comedi_alloc_subdev_readback(s);
        if (ret)
                return ret;

        /* 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");
                return offset;
        }

        dev_info(dev->class_dev, "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;

        mutex_lock(&devpriv->mut);

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

        usbduxsigma_free_usb_buffers(dev);

        mutex_unlock(&devpriv->mut);

        mutex_destroy(&devpriv->mut);
}

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, mail@berndporr.me.uk");
MODULE_DESCRIPTION("Stirling/ITL USB-DUX SIGMA -- mail@berndporr.me.uk");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(FIRMWARE);