/* * Driver: usbdux * Description: University of Stirling USB DAQ & INCITE Technology Limited * Devices: [ITL] USB-DUX (usbdux) * Author: Bernd Porr <mail@berndporr.me.uk> * Updated: 10 Oct 2014 * Status: Stable * * Connection scheme for the counter at the digital port: * 0=/CLK0, 1=UP/DOWN0, 2=RESET0, 4=/CLK1, 5=UP/DOWN1, 6=RESET1. * The sampling rate of the counter is approximately 500Hz. * * Note that under USB2.0 the length of the channel list determines * the max sampling rate. If you sample only one channel you get 8kHz * sampling rate. If you sample two channels you get 4kHz and so on.
*/
/* * 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: * 0.94: D/A output should work now with any channel list combinations * 0.95: .owner commented out for kernel vers below 2.4.19 * sanity checks in ai/ao_cmd * 0.96: trying to get it working with 2.6, moved all memory alloc to comedi's * attach final USB IDs * moved memory allocation completely to the corresponding comedi * functions firmware upload is by fxload and no longer by comedi (due to * enumeration) * 0.97: USB IDs received, adjusted table * 0.98: SMP, locking, memory alloc: moved all usb memory alloc * to the usb subsystem and moved all comedi related memory * alloc to comedi. * | kernel | registration | usbdux-usb | usbdux-comedi | comedi | * 0.99: USB 2.0: changed protocol to isochronous transfer * IRQ transfer is too buggy and too risky in 2.0 * for the high speed ISO transfer is now a working version * available * 0.99b: Increased the iso transfer buffer for high sp.to 10 buffers. Some VIA * chipsets miss out IRQs. Deeper buffering is needed. * 1.00: full USB 2.0 support for the A/D converter. Now: max 8kHz sampling * rate. * Firmware vers 1.00 is needed for this. * Two 16 bit up/down/reset counter with a sampling rate of 1kHz * And loads of cleaning up, in particular streamlining the * bulk transfers. * 1.1: moved EP4 transfers to EP1 to make space for a PWM output on EP4 * 1.2: added PWM support via EP4 * 2.0: PWM seems to be stable and is not interfering with the other functions * 2.1: changed PWM API * 2.2: added firmware kernel request to fix an udev problem * 2.3: corrected a bug in bulk timeouts which were far too short * 2.4: fixed a bug which causes the driver to hang when it ran out of data. * Thanks to Jan-Matthias Braun and Ian to spot the bug and fix it. *
*/
/* timeout for the USB-transfer in ms */ #define BULK_TIMEOUT 1000
/* 300Hz max frequ under PWM */ #define MIN_PWM_PERIOD ((long)(1E9 / 300))
/* Default PWM frequency */ #define PWM_DEFAULT_PERIOD ((long)(1E9 / 100))
/* Size of one A/D value */ #define SIZEADIN ((sizeof(u16)))
/* * Size of the input-buffer IN BYTES * Always multiple of 8 for 8 microframes which is needed in the highspeed mode
*/ #define SIZEINBUF (8 * SIZEADIN)
/* 16 bytes. */ #define SIZEINSNBUF 16
/* size of one value for the D/A converter: channel and value */ #define SIZEDAOUT ((sizeof(u8) + sizeof(u16)))
/* * 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
struct usbdux_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 */ unsignedint 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 */
__le16 *in_buf; /* input buffer for single insn */
__le16 *insn_buf;
/* time between samples in units of the timer */ unsignedint ai_timer; unsignedint ao_timer; /* counter between aquisitions */ unsignedint ai_counter; unsignedint ao_counter; /* interval in frames/uframes */ unsignedint ai_interval; /* commands */
u8 *dux_commands; struct mutex mut;
};
staticvoid usbdux_unlink_urbs(struct urb **urbs, int num_urbs)
{ int i;
for (i = 0; i < num_urbs; i++)
usb_kill_urb(urbs[i]);
}
/* prevent other CPUs from submitting new commands just now */
mutex_lock(&devpriv->mut); /* unlink only if the urb really has been submitted */
usbdux_ai_stop(dev, devpriv->ai_cmd_running);
mutex_unlock(&devpriv->mut);
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 */ for (i = 0; i < cmd->chanlist_len; i++) { unsignedint range = CR_RANGE(cmd->chanlist[i]);
u16 val = le16_to_cpu(devpriv->in_buf[i]);
/* bipolar data is two's-complement */ if (comedi_range_is_bipolar(s, range))
val = comedi_offset_munge(s, val);
/* transfer data */ if (!comedi_buf_write_samples(s, &val, 1)) return;
}
/* 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 in int-context! err=%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;
}
}
}
/* 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);
usbduxsub_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");
usbduxsub_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)
usbdux_ai_stop(dev, 0);
/* prevent other CPUs from submitting a command just now */
mutex_lock(&devpriv->mut); /* unlink only if it is really running */
usbdux_ao_stop(dev, devpriv->ao_cmd_running);
mutex_unlock(&devpriv->mut);
/* transmit data to the USB bus */
datap = urb->transfer_buffer;
*datap++ = cmd->chanlist_len; for (i = 0; i < cmd->chanlist_len; i++) { unsignedint chan = CR_CHAN(cmd->chanlist[i]); unsignedshort val;
/* exit if not running a command, do not resubmit urb */ if (!devpriv->ao_cmd_running) return;
switch (urb->status) { case 0:
usbduxsub_ao_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 ao 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)
usbdux_ao_stop(dev, 0);
comedi_event(dev, s);
}
staticint usbdux_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 usbdux_private *devpriv = dev->private; struct urb *urb; int ret; int i;
/* Submit all URBs and start the transfer on the bus */ for (i = 0; i < num_urbs; i++) {
urb = urbs[i];
/* in case of a resubmission after an unlink... */ if (input_urb)
urb->interval = devpriv->ai_interval;
urb->context = dev;
urb->dev = usb;
urb->status = 0;
urb->transfer_flags = URB_ISO_ASAP;
ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret) return ret;
} return 0;
}
if (cmd->scan_begin_src == TRIG_TIMER) { /* full speed does 1kHz scans every USB frame */ unsignedint arg = 1000000; unsignedint min_arg = arg;
if (devpriv->high_speed) { /* * In high speed mode microframes are possible. * However, during one microframe we can roughly * sample one channel. Thus, the more channels * are in the channel list the more time we need.
*/ int i = 1;
/* find a power of 2 for the number of channels */ while (i < cmd->chanlist_len)
i = i * 2;
arg /= 8;
min_arg = arg * i;
}
err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
min_arg); /* calc the real sampling rate with the rounding errors */
arg = (cmd->scan_begin_arg / arg) * arg;
err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
}
/* * creates the ADC command for the MAX1271 * range is the range value from comedi
*/ static u8 create_adc_command(unsignedint chan, unsignedint range)
{
u8 p = (range <= 1);
u8 r = ((range % 2) == 0);
staticint receive_dux_commands(struct comedi_device *dev, unsignedint command)
{ struct usb_device *usb = comedi_to_usb_dev(dev); struct usbdux_private *devpriv = dev->private; int ret; int nrec; 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 (le16_to_cpu(devpriv->insn_buf[0]) == command) return ret;
} /* command not received */ return -EFAULT;
}
staticint usbdux_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{ struct usbdux_private *devpriv = dev->private; struct comedi_cmd *cmd = &s->async->cmd; int len = cmd->chanlist_len; int ret = -EBUSY; int i;
/* block other CPUs from starting an ai_cmd */
mutex_lock(&devpriv->mut);
if (devpriv->ai_cmd_running) goto ai_cmd_exit;
devpriv->dux_commands[1] = len; for (i = 0; i < len; ++i) { unsignedint chan = CR_CHAN(cmd->chanlist[i]); unsignedint range = CR_RANGE(cmd->chanlist[i]);
ret = send_dux_commands(dev, USBDUX_CMD_MULT_AI); if (ret < 0) goto ai_cmd_exit;
if (devpriv->high_speed) { /* * every channel gets a time window of 125us. Thus, if we * sample all 8 channels we need 1ms. If we sample only one * channel we need only 125us
*/
devpriv->ai_interval = 1; /* find a power of 2 for the interval */ while (devpriv->ai_interval < len)
devpriv->ai_interval *= 2;
if (cmd->start_src == TRIG_NOW) { /* enable this acquisition operation */
devpriv->ai_cmd_running = 1;
ret = usbdux_submit_urbs(dev, devpriv->ai_urbs,
devpriv->n_ai_urbs, 1); if (ret < 0) {
devpriv->ai_cmd_running = 0; /* fixme: unlink here?? */ goto ai_cmd_exit;
}
s->async->inttrig = NULL;
} else { /* TRIG_INT */ /* don't enable the acquision operation */ /* wait for an internal signal */
s->async->inttrig = usbdux_ai_inttrig;
}
ai_cmd_exit:
mutex_unlock(&devpriv->mut);
return ret;
}
/* Mode 0 is used to get a single conversion on demand */ staticint usbdux_ai_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsignedint *data)
{ struct usbdux_private *devpriv = dev->private; unsignedint chan = CR_CHAN(insn->chanspec); unsignedint range = CR_RANGE(insn->chanspec); unsignedint val; int ret = -EBUSY; int i;
mutex_lock(&devpriv->mut);
if (devpriv->ai_cmd_running) goto ai_read_exit;
/* set command for the first channel */
devpriv->dux_commands[1] = create_adc_command(chan, range);
/* adc commands */
ret = send_dux_commands(dev, USBDUX_CMD_SINGLE_AI); if (ret < 0) goto ai_read_exit;
for (i = 0; i < insn->n; i++) {
ret = receive_dux_commands(dev, USBDUX_CMD_SINGLE_AI); if (ret < 0) goto ai_read_exit;
val = le16_to_cpu(devpriv->insn_buf[1]);
/* bipolar data is two's-complement */ if (comedi_range_is_bipolar(s, range))
val = comedi_offset_munge(s, val);
if (0) { /* (devpriv->high_speed) */ /* the sampling rate is set by the coversion rate */
flags = TRIG_FOLLOW;
} else { /* start a new scan (output at once) with a timer */
flags = TRIG_TIMER;
}
err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
if (0) { /* (devpriv->high_speed) */ /* * in usb-2.0 only one conversion it transmitted * but with 8kHz/n
*/
flags = TRIG_TIMER;
} else { /* * all conversion events happen simultaneously with * a rate of 1kHz/n
*/
flags = TRIG_NOW;
}
err |= comedi_check_trigger_src(&cmd->convert_src, flags);
/* we count in steps of 1ms (125us) */ /* 125us mode not used yet */ if (0) { /* (devpriv->high_speed) */ /* 125us */ /* timing of the conversion itself: every 125 us */
devpriv->ao_timer = cmd->convert_arg / 125000;
} else { /* 1ms */ /* timing of the scan: we get all channels at once */
devpriv->ao_timer = cmd->scan_begin_arg / 1000000; if (devpriv->ao_timer < 1) {
ret = -EINVAL; goto ao_cmd_exit;
}
}
devpriv->ao_counter = devpriv->ao_timer;
if (cmd->start_src == TRIG_NOW) { /* enable this acquisition operation */
devpriv->ao_cmd_running = 1;
ret = usbdux_submit_urbs(dev, devpriv->ao_urbs,
devpriv->n_ao_urbs, 0); if (ret < 0) {
devpriv->ao_cmd_running = 0; /* fixme: unlink here?? */ goto ao_cmd_exit;
}
s->async->inttrig = NULL;
} else { /* TRIG_INT */ /* submit the urbs later */ /* wait for an internal signal */
s->async->inttrig = usbdux_ao_inttrig;
}
/* Always update the hardware. See the (*insn_config). */
devpriv->dux_commands[1] = s->io_bits;
devpriv->dux_commands[2] = s->state;
/* * This command also tells the firmware to return * the digital input lines.
*/
ret = send_dux_commands(dev, USBDUX_CMD_DIO_BITS); if (ret < 0) goto dio_exit;
ret = receive_dux_commands(dev, USBDUX_CMD_DIO_BITS); if (ret < 0) goto dio_exit;
data[1] = le16_to_cpu(devpriv->insn_buf[1]);
dio_exit:
mutex_unlock(&devpriv->mut);
return ret ? ret : insn->n;
}
staticint usbdux_counter_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsignedint *data)
{ struct usbdux_private *devpriv = dev->private; unsignedint chan = CR_CHAN(insn->chanspec); int ret = 0; int i;
mutex_lock(&devpriv->mut);
for (i = 0; i < insn->n; i++) {
ret = send_dux_commands(dev, USBDUX_CMD_TIMER_RD); if (ret < 0) goto counter_read_exit;
ret = receive_dux_commands(dev, USBDUX_CMD_TIMER_RD); if (ret < 0) goto counter_read_exit;
mutex_lock(&devpriv->mut); /* unlink only if it is really running */
usbdux_pwm_stop(dev, devpriv->pwm_cmd_running);
ret = send_dux_commands(dev, USBDUX_CMD_PWM_OFF);
mutex_unlock(&devpriv->mut);
switch (urb->status) { case 0: /* success */ break;
case -ECONNRESET: case -ENOENT: case -ESHUTDOWN: case -ECONNABORTED: /* * after an unlink command, unplug, ... etc * no unlink needed here. Already shutting down.
*/ if (devpriv->pwm_cmd_running)
usbdux_pwm_stop(dev, 0);
return;
default: /* a real error */ if (devpriv->pwm_cmd_running) {
dev_err(dev->class_dev, "Non-zero urb status received in pwm intr context: %d\n",
urb->status);
usbdux_pwm_stop(dev, 0);
} return;
}
/* are we actually running? */ if (!devpriv->pwm_cmd_running) return;
urb->transfer_buffer_length = devpriv->pwm_buf_sz;
urb->dev = comedi_to_usb_dev(dev);
urb->status = 0; if (devpriv->pwm_cmd_running) {
ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret < 0) {
dev_err(dev->class_dev, "pwm urb resubm failed in int-cont. ret=%d",
ret); if (ret == -EL2NSYNC)
dev_err(dev->class_dev, "buggy USB host controller or bug in IRQ handling!\n");
/* don't do an unlink here */
usbdux_pwm_stop(dev, 0);
}
}
}
/* 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,
usbduxsub_pwm_irq,
dev);
/* * 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.
*/
usbdux_pwm_pattern(dev, s, chan, data[0], 0);
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 usbdux_pwm_start(dev, s); case INSN_CONFIG_DISARM: return usbdux_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 usbdux_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)
*/
usbdux_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 here */ return -EINVAL;
} return -EINVAL;
}
if (size > USBDUX_FIRMWARE_MAX_LEN) {
dev_err(dev->class_dev, "usbdux firmware binary it 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),
USBDUX_FIRMWARE_CMD,
VENDOR_DIR_OUT,
USBDUX_CPU_CS, 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),
USBDUX_FIRMWARE_CMD,
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),
USBDUX_FIRMWARE_CMD,
VENDOR_DIR_OUT,
USBDUX_CPU_CS, 0x0000,
tmp, 1,
BULK_TIMEOUT); if (ret < 0)
dev_err(dev->class_dev, "can not start firmware\n");
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;
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;
ret = usbdux_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, USBDUX_FIRMWARE,
usbdux_firmware_upload, 0); if (ret < 0) return ret;
ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 5 : 4); if (ret) return ret;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.