/* * 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
*/
/* 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
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 */ 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 */
__be32 *in_buf; /* input buffer for single insn */
u8 *insn_buf;
/* time between samples in units of the timer */ unsignedint ai_timer; unsignedint ao_timer; /* counter between acquisitions */ unsignedint ai_counter; unsignedint ao_counter; /* interval in frames/uframes */ unsignedint ai_interval; /* commands */
u8 *dux_commands; struct mutex mut;
};
staticvoid usbduxsigma_unlink_urbs(struct urb **urbs, int num_urbs)
{ int i;
for (i = 0; i < num_urbs; i++)
usb_kill_urb(urbs[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;
}
/* 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);
/* 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:
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);
}
staticint 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;
}
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);
}
staticint 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;
}
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
*/ unsignedint interval = usbduxsigma_chans_to_interval(len);
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);
/* * 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;
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;
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 */
}
}
/* 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);
/* * 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);
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;
}
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 */
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");
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;
}
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;
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.