1040 строки
24 KiB
C
1040 строки
24 KiB
C
// 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);
|