diff options
Diffstat (limited to 'drivers/comedi/drivers/usbduxfast.c')
-rw-r--r-- | drivers/comedi/drivers/usbduxfast.c | 1039 |
1 files changed, 1039 insertions, 0 deletions
diff --git a/drivers/comedi/drivers/usbduxfast.c b/drivers/comedi/drivers/usbduxfast.c new file mode 100644 index 000000000000..4af012968cb6 --- /dev/null +++ b/drivers/comedi/drivers/usbduxfast.c @@ -0,0 +1,1039 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2004-2019 Bernd Porr, mail@berndporr.me.uk + */ + +/* + * Driver: usbduxfast + * Description: University of Stirling USB DAQ & INCITE Technology Limited + * Devices: [ITL] USB-DUX-FAST (usbduxfast) + * Author: Bernd Porr <mail@berndporr.me.uk> + * Updated: 16 Nov 2019 + * 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. + * + * Bernd Porr + * + * + * Revision history: + * 1.0: Fixed a rounding error in usbduxfast_ai_cmdtest + * 0.9: Dropping the first data packet which seems to be from the last transfer. + * Buffer overflows in the FX2 are handed over to comedi. + * 0.92: Dropping now 4 packets. The quad buffer has to be emptied. + * Added insn command basically for testing. Sample rate is + * 1MHz/16ch=62.5kHz + * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks! + * 0.99a: added external trigger. + * 1.00: added firmware kernel request to the driver which fixed + * udev coldplug problem + */ + +#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 "../comedi_usb.h" + +/* + * timeout for the USB-transfer + */ +#define EZTIMEOUT 30 + +/* + * constants for "firmware" upload and download + */ +#define FIRMWARE "usbduxfast_firmware.bin" +#define FIRMWARE_MAX_LEN 0x2000 +#define USBDUXFASTSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* + * internal addresses of the 8051 processor + */ +#define USBDUXFASTSUB_CPUCS 0xE600 + +/* + * max length of the transfer-buffer for software upload + */ +#define TB_LEN 0x2000 + +/* + * input endpoint number + */ +#define BULKINEP 6 + +/* + * endpoint for the A/D channellist: bulk OUT + */ +#define CHANNELLISTEP 4 + +/* + * number of channels + */ +#define NUMCHANNELS 32 + +/* + * size of the waveform descriptor + */ +#define WAVESIZE 0x20 + +/* + * size of one A/D value + */ +#define SIZEADIN (sizeof(s16)) + +/* + * size of the input-buffer IN BYTES + */ +#define SIZEINBUF 512 + +/* + * 16 bytes + */ +#define SIZEINSNBUF 512 + +/* + * size of the buffer for the dux commands in bytes + */ +#define SIZEOFDUXBUF 256 + +/* + * number of in-URBs which receive the data: min=5 + */ +#define NUMOFINBUFFERSHIGH 10 + +/* + * min delay steps for more than one channel + * basically when the mux gives up ;-) + * + * steps at 30MHz in the FX2 + */ +#define MIN_SAMPLING_PERIOD 9 + +/* + * max number of 1/30MHz delay steps + */ +#define MAX_SAMPLING_PERIOD 500 + +/* + * number of received packets to ignore before we start handing data + * over to comedi, it's quad buffering and we have to ignore 4 packets + */ +#define PACKETS_TO_IGNORE 4 + +/* + * comedi constants + */ +static const struct comedi_lrange range_usbduxfast_ai_range = { + 2, { + BIP_RANGE(0.75), + BIP_RANGE(0.5) + } +}; + +/* + * private structure of one subdevice + * + * this is the structure which holds all the data of this driver + * one sub device just now: A/D + */ +struct usbduxfast_private { + struct urb *urb; /* BULK-transfer handling: urb */ + u8 *duxbuf; + s8 *inbuf; + short int ai_cmd_running; /* asynchronous command is running */ + int ignore; /* counter which ignores the first buffers */ + struct mutex mut; +}; + +/* + * bulk transfers to usbduxfast + */ +#define SENDADCOMMANDS 0 +#define SENDINITEP6 1 + +static int usbduxfast_send_cmd(struct comedi_device *dev, int cmd_type) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxfast_private *devpriv = dev->private; + int nsent; + int ret; + + devpriv->duxbuf[0] = cmd_type; + + ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, CHANNELLISTEP), + devpriv->duxbuf, SIZEOFDUXBUF, + &nsent, 10000); + if (ret < 0) + dev_err(dev->class_dev, + "could not transmit command to the usb-device, err=%d\n", + ret); + return ret; +} + +static void usbduxfast_cmd_data(struct comedi_device *dev, int index, + u8 len, u8 op, u8 out, u8 log) +{ + struct usbduxfast_private *devpriv = dev->private; + + /* Set the GPIF bytes, the first byte is the command byte */ + devpriv->duxbuf[1 + 0x00 + index] = len; + devpriv->duxbuf[1 + 0x08 + index] = op; + devpriv->duxbuf[1 + 0x10 + index] = out; + devpriv->duxbuf[1 + 0x18 + index] = log; +} + +static int usbduxfast_ai_stop(struct comedi_device *dev, int do_unlink) +{ + struct usbduxfast_private *devpriv = dev->private; + + /* stop aquistion */ + devpriv->ai_cmd_running = 0; + + if (do_unlink && devpriv->urb) { + /* kill the running transfer */ + usb_kill_urb(devpriv->urb); + } + + return 0; +} + +static int usbduxfast_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxfast_private *devpriv = dev->private; + int ret; + + mutex_lock(&devpriv->mut); + ret = usbduxfast_ai_stop(dev, 1); + mutex_unlock(&devpriv->mut); + + return ret; +} + +static void usbduxfast_ai_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbduxfast_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int ret; + + if (devpriv->ignore) { + devpriv->ignore--; + } else { + unsigned int nsamples; + + nsamples = comedi_bytes_to_samples(s, urb->actual_length); + nsamples = comedi_nsamples_left(s, nsamples); + comedi_buf_write_samples(s, urb->transfer_buffer, nsamples); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } + + /* if command is still running, resubmit urb for BULK transfer */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + 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 resubm failed: %d", ret); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxfast_ai_interrupt(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct usbduxfast_private *devpriv = dev->private; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ai_cmd_running) + return; + + switch (urb->status) { + case 0: + usbduxfast_ai_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, + "non-zero urb status received in ai intr context: %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) + usbduxfast_ai_stop(dev, 0); + + comedi_event(dev, s); +} + +static int usbduxfast_submit_urb(struct comedi_device *dev) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxfast_private *devpriv = dev->private; + int ret; + + usb_fill_bulk_urb(devpriv->urb, usb, usb_rcvbulkpipe(usb, BULKINEP), + devpriv->inbuf, SIZEINBUF, + usbduxfast_ai_interrupt, dev); + + ret = usb_submit_urb(devpriv->urb, GFP_ATOMIC); + if (ret) { + dev_err(dev->class_dev, "usb_submit_urb error %d\n", ret); + return ret; + } + return 0; +} + +static int usbduxfast_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int gain0 = CR_RANGE(cmd->chanlist[0]); + int i; + + if (cmd->chanlist_len > 3 && cmd->chanlist_len != 16) { + dev_err(dev->class_dev, "unsupported combination of channels\n"); + return -EINVAL; + } + + for (i = 0; i < cmd->chanlist_len; ++i) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int gain = CR_RANGE(cmd->chanlist[i]); + + if (chan != i) { + dev_err(dev->class_dev, + "channels are not consecutive\n"); + return -EINVAL; + } + if (gain != gain0 && cmd->chanlist_len > 3) { + dev_err(dev->class_dev, + "gain must be the same for all channels\n"); + return -EINVAL; + } + } + return 0; +} + +static int usbduxfast_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int err2 = 0; + unsigned int steps; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_EXT | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + 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 (!cmd->chanlist_len) + err |= -EINVAL; + + /* external start trigger is only valid for 1 or 16 channels */ + if (cmd->start_src == TRIG_EXT && + cmd->chanlist_len != 1 && cmd->chanlist_len != 16) + err |= -EINVAL; + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + /* + * Validate the conversion timing: + * for 1 channel the timing in 30MHz "steps" is: + * steps <= MAX_SAMPLING_PERIOD + * for all other chanlist_len it is: + * MIN_SAMPLING_PERIOD <= steps <= MAX_SAMPLING_PERIOD + */ + steps = (cmd->convert_arg * 30) / 1000; + if (cmd->chanlist_len != 1) + err2 |= comedi_check_trigger_arg_min(&steps, + MIN_SAMPLING_PERIOD); + else + err2 |= comedi_check_trigger_arg_min(&steps, 1); + err2 |= comedi_check_trigger_arg_max(&steps, MAX_SAMPLING_PERIOD); + if (err2) { + err |= err2; + arg = (steps * 1000) / 30; + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + 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 */ + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= usbduxfast_ai_check_chanlist(dev, s, cmd); + if (err) + return 5; + + return 0; +} + +static int usbduxfast_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct usbduxfast_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 = usbduxfast_submit_urb(dev); + if (ret < 0) { + dev_err(dev->class_dev, "urbSubmit: err=%d\n", ret); + devpriv->ai_cmd_running = 0; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(dev->class_dev, "ai is already running\n"); + } + mutex_unlock(&devpriv->mut); + return 1; +} + +static int usbduxfast_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxfast_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int rngmask = 0xff; + int j, ret; + long steps, steps_tmp; + + mutex_lock(&devpriv->mut); + if (devpriv->ai_cmd_running) { + ret = -EBUSY; + goto cmd_exit; + } + + /* + * ignore the first buffers from the device if there + * is an error condition + */ + devpriv->ignore = PACKETS_TO_IGNORE; + + steps = (cmd->convert_arg * 30) / 1000; + + switch (cmd->chanlist_len) { + case 1: + /* + * one channel + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* + * for external trigger: looping in this state until + * the RDY0 pin becomes zero + */ + + /* we loop here until ready has been set */ + if (cmd->start_src == TRIG_EXT) { + /* branch back to state 0 */ + /* deceision state w/o data */ + /* RDY0 = 0 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x01, rngmask, 0x00); + } else { /* we just proceed to state 1 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x00, rngmask, 0x00); + } + + if (steps < MIN_SAMPLING_PERIOD) { + /* for fast single channel aqu without mux */ + if (steps <= 1) { + /* + * we just stay here at state 1 and rexecute + * the same state this gives us 30MHz sampling + * rate + */ + + /* branch back to state 1 */ + /* deceision state with data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 1, + 0x89, 0x03, rngmask, 0xff); + } else { + /* + * we loop through two states: data and delay + * max rate is 15MHz + */ + /* data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 1, steps - 1, + 0x02, rngmask, 0x00); + + /* branch back to state 1 */ + /* deceision state w/o data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 2, + 0x09, 0x01, rngmask, 0xff); + } + } else { + /* + * we loop through 3 states: 2x delay and 1x data + * this gives a min sampling rate of 60kHz + */ + + /* we have 1 state with duration 1 */ + steps = steps - 1; + + /* do the first part of the delay */ + usbduxfast_cmd_data(dev, 1, + steps / 2, 0x00, rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 2, steps - steps / 2, + 0x00, rngmask, 0x00); + + /* get the data and branch back */ + + /* branch back to state 1 */ + /* deceision state w data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 3, + 0x09, 0x03, rngmask, 0xff); + } + break; + + case 2: + /* + * two channels + * commit data to the FIFO + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* data */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00); + + /* we have 1 state with duration 1: state 0 */ + steps_tmp = steps - 1; + + if (CR_RANGE(cmd->chanlist[1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + /* count */ + usbduxfast_cmd_data(dev, 1, steps_tmp / 2, + 0x00, 0xfe & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 2, steps_tmp - steps_tmp / 2, + 0x00, rngmask, 0x00); + + /* data */ + usbduxfast_cmd_data(dev, 3, 0x01, 0x02, rngmask, 0x00); + + /* + * we have 2 states with duration 1: step 6 and + * the IDLE state + */ + steps_tmp = steps - 2; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + /* reset */ + usbduxfast_cmd_data(dev, 4, steps_tmp / 2, + 0x00, (0xff - 0x02) & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2, + 0x00, rngmask, 0x00); + + usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00); + break; + + case 3: + /* + * three channels + */ + for (j = 0; j < 1; j++) { + int index = j * 2; + + if (CR_RANGE(cmd->chanlist[j]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + /* + * commit data to the FIFO and do the first part + * of the delay + */ + /* data */ + /* no change */ + usbduxfast_cmd_data(dev, index, steps / 2, + 0x02, rngmask, 0x00); + + if (CR_RANGE(cmd->chanlist[j + 1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + /* no data */ + /* count */ + usbduxfast_cmd_data(dev, index + 1, steps - steps / 2, + 0x00, 0xfe & rngmask, 0x00); + } + + /* 2 steps with duration 1: the idele step and step 6: */ + steps_tmp = steps - 2; + + /* commit data to the FIFO and do the first part of the delay */ + /* data */ + usbduxfast_cmd_data(dev, 4, steps_tmp / 2, + 0x02, rngmask, 0x00); + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + /* no data */ + /* reset */ + usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2, + 0x00, (0xff - 0x02) & rngmask, 0x00); + + usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00); + break; + + case 16: + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + if (cmd->start_src == TRIG_EXT) { + /* + * we loop here until ready has been set + */ + + /* branch back to state 0 */ + /* deceision state w/o data */ + /* reset */ + /* RDY0 = 0 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x01, + (0xff - 0x02) & rngmask, 0x00); + } else { + /* + * we just proceed to state 1 + */ + + /* 30us reset pulse */ + /* reset */ + usbduxfast_cmd_data(dev, 0, 0xff, 0x00, + (0xff - 0x02) & rngmask, 0x00); + } + + /* commit data to the FIFO */ + /* data */ + usbduxfast_cmd_data(dev, 1, 0x01, 0x02, rngmask, 0x00); + + /* we have 2 states with duration 1 */ + steps = steps - 2; + + /* do the first part of the delay */ + usbduxfast_cmd_data(dev, 2, steps / 2, + 0x00, 0xfe & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 3, steps - steps / 2, + 0x00, rngmask, 0x00); + + /* branch back to state 1 */ + /* deceision state w/o data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 4, 0x09, 0x01, rngmask, 0xff); + + break; + } + + /* 0 means that the AD commands are sent */ + ret = usbduxfast_send_cmd(dev, SENDADCOMMANDS); + if (ret < 0) + goto cmd_exit; + + if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) { + /* enable this acquisition operation */ + devpriv->ai_cmd_running = 1; + ret = usbduxfast_submit_urb(dev); + if (ret < 0) { + devpriv->ai_cmd_running = 0; + /* fixme: unlink here?? */ + goto cmd_exit; + } + s->async->inttrig = NULL; + } else { /* TRIG_INT */ + s->async->inttrig = usbduxfast_ai_inttrig; + } + +cmd_exit: + mutex_unlock(&devpriv->mut); + + return ret; +} + +/* + * Mode 0 is used to get a single conversion on demand. + */ +static int usbduxfast_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxfast_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + u8 rngmask = range ? (0xff - 0x04) : 0xff; + int i, j, n, actual_length; + int ret; + + mutex_lock(&devpriv->mut); + + if (devpriv->ai_cmd_running) { + dev_err(dev->class_dev, + "ai_insn_read not possible, async cmd is running\n"); + mutex_unlock(&devpriv->mut); + return -EBUSY; + } + + /* set command for the first channel */ + + /* commit data to the FIFO */ + /* data */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00); + + /* do the first part of the delay */ + usbduxfast_cmd_data(dev, 1, 0x0c, 0x00, 0xfe & rngmask, 0x00); + usbduxfast_cmd_data(dev, 2, 0x01, 0x00, 0xfe & rngmask, 0x00); + usbduxfast_cmd_data(dev, 3, 0x01, 0x00, 0xfe & rngmask, 0x00); + usbduxfast_cmd_data(dev, 4, 0x01, 0x00, 0xfe & rngmask, 0x00); + + /* second part */ + usbduxfast_cmd_data(dev, 5, 0x0c, 0x00, rngmask, 0x00); + usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00); + + ret = usbduxfast_send_cmd(dev, SENDADCOMMANDS); + if (ret < 0) { + mutex_unlock(&devpriv->mut); + return ret; + } + + for (i = 0; i < PACKETS_TO_IGNORE; i++) { + ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP), + devpriv->inbuf, SIZEINBUF, + &actual_length, 10000); + if (ret < 0) { + dev_err(dev->class_dev, "insn timeout, no data\n"); + mutex_unlock(&devpriv->mut); + return ret; + } + } + + for (i = 0; i < insn->n;) { + ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP), + devpriv->inbuf, SIZEINBUF, + &actual_length, 10000); + if (ret < 0) { + dev_err(dev->class_dev, "insn data error: %d\n", ret); + mutex_unlock(&devpriv->mut); + return ret; + } + n = actual_length / sizeof(u16); + if ((n % 16) != 0) { + dev_err(dev->class_dev, "insn data packet corrupted\n"); + mutex_unlock(&devpriv->mut); + return -EINVAL; + } + for (j = chan; (j < n) && (i < insn->n); j = j + 16) { + data[i] = ((u16 *)(devpriv->inbuf))[j]; + i++; + } + } + + mutex_unlock(&devpriv->mut); + + return insn->n; +} + +static int usbduxfast_upload_firmware(struct comedi_device *dev, + const u8 *data, size_t size, + unsigned long context) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + u8 *buf; + unsigned char *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), + USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, + USBDUXFASTSUB_CPUCS, 0x0000, + tmp, 1, + EZTIMEOUT); + 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), + USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, + 0, 0x0000, + buf, size, + EZTIMEOUT); + 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), + USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, + USBDUXFASTSUB_CPUCS, 0x0000, + tmp, 1, + EZTIMEOUT); + if (ret < 0) + dev_err(dev->class_dev, "can not start firmware\n"); + +done: + kfree(tmp); + kfree(buf); + return ret; +} + +static int usbduxfast_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 usbduxfast_private *devpriv; + struct comedi_subdevice *s; + int ret; + + if (usb->speed != USB_SPEED_HIGH) { + dev_err(dev->class_dev, + "This driver needs USB 2.0 to operate. Aborting...\n"); + return -ENODEV; + } + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + mutex_init(&devpriv->mut); + usb_set_intfdata(intf, devpriv); + + devpriv->duxbuf = kmalloc(SIZEOFDUXBUF, GFP_KERNEL); + if (!devpriv->duxbuf) + return -ENOMEM; + + ret = usb_set_interface(usb, + intf->altsetting->desc.bInterfaceNumber, 1); + if (ret < 0) { + dev_err(dev->class_dev, + "could not switch to alternate setting 1\n"); + return -ENODEV; + } + + devpriv->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!devpriv->urb) + return -ENOMEM; + + devpriv->inbuf = kmalloc(SIZEINBUF, GFP_KERNEL); + if (!devpriv->inbuf) + return -ENOMEM; + + ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE, + usbduxfast_upload_firmware, 0); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + 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; + s->n_chan = 16; + s->maxdata = 0x1000; /* 12-bit + 1 overflow bit */ + s->range_table = &range_usbduxfast_ai_range; + s->insn_read = usbduxfast_ai_insn_read; + s->len_chanlist = s->n_chan; + s->do_cmdtest = usbduxfast_ai_cmdtest; + s->do_cmd = usbduxfast_ai_cmd; + s->cancel = usbduxfast_ai_cancel; + + return 0; +} + +static void usbduxfast_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct usbduxfast_private *devpriv = dev->private; + + if (!devpriv) + return; + + mutex_lock(&devpriv->mut); + + usb_set_intfdata(intf, NULL); + + if (devpriv->urb) { + /* waits until a running transfer is over */ + usb_kill_urb(devpriv->urb); + + kfree(devpriv->inbuf); + usb_free_urb(devpriv->urb); + } + + kfree(devpriv->duxbuf); + + mutex_unlock(&devpriv->mut); + + mutex_destroy(&devpriv->mut); +} + +static struct comedi_driver usbduxfast_driver = { + .driver_name = "usbduxfast", + .module = THIS_MODULE, + .auto_attach = usbduxfast_auto_attach, + .detach = usbduxfast_detach, +}; + +static int usbduxfast_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return comedi_usb_auto_config(intf, &usbduxfast_driver, 0); +} + +static const struct usb_device_id usbduxfast_usb_table[] = { + /* { USB_DEVICE(0x4b4, 0x8613) }, testing */ + { USB_DEVICE(0x13d8, 0x0010) }, /* real ID */ + { USB_DEVICE(0x13d8, 0x0011) }, /* real ID */ + { } +}; +MODULE_DEVICE_TABLE(usb, usbduxfast_usb_table); + +static struct usb_driver usbduxfast_usb_driver = { + .name = "usbduxfast", + .probe = usbduxfast_usb_probe, + .disconnect = comedi_usb_auto_unconfig, + .id_table = usbduxfast_usb_table, +}; +module_comedi_usb_driver(usbduxfast_driver, usbduxfast_usb_driver); + +MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com"); +MODULE_DESCRIPTION("USB-DUXfast, BerndPorr@f2s.com"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FIRMWARE); |