Author: Dan Christian
Devices: [Real Time Devices] DM7520HR-1 (DM7520), DM7520HR-8 (DM7520-8),
PCI4520 (PCI4520), PCI4520-8 (PCI4520-8)
-Status: works. Only tested on DM7520-8.
+Status: Works. Only tested on DM7520-8. Not SMP safe.
Configuration options:
[0] - PCI bus of device (optional)
PCI chip: http://www.plxtech.com/products/toolbox/9080.htm
Notes:
- This board is (almost) completely memory mapped.
+ This board is memory mapped. There is some IO stuff, but it isn't needed.
I use a pretty loose naming style within the driver (rtd_blah).
All externally visible names should be rtd520_blah.
This board is somewhat related to the RTD PCI4400 board.
- I borrowed heavily from the ni_mio_common, ni_atmio16d, and das1800,
- since they have the best documented code.
+ I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and
+ das1800, since they have the best documented code. Driver
+ cb_pcidas64.c uses the same DMA controller.
+
+ As far as I can tell, the About interrupt doesnt work if Sample is
+ also enabled. It turns out that About really isn't needed, since
+ we always count down samples read.
+
+ There was some timer/counter code, but it didn't follow the right API.
*/
/*
driver status:
- Analog in supports instruction and command mode. I can do 200Khz
- mutli-channel sampling on a 400Mhz K6-2 with 70% idle. The DMA isn't
- used yet.
-
- Digital IO and analog out support instruction mode.
+ Analog-In supports instruction and command mode.
- There was some timer/counter code, but it didn't follow the right API.
+ With DMA, 1.15Mhz with 70% idle on a 400Mhz K6-2 (single channel).
+ Can do 200Khz multi-channel sampling with 70% idle on a 400Mhz K6-2 (no DMA).
+
+ Digital-IO and Analog-Out support instruction mode.
*/
#include <linux/init.h>
#include <asm/io.h>
+#include <asm/dma.h>
#include <linux/comedidev.h>
/*======================================================================
- Drivier specific stuff (tunable)
+ Driver specific stuff (tunable)
======================================================================*/
-/* Target period for periodic transfers */
-/* if this is too low, efficiency is poor */
+/* Enable this to test the new DMA support. You may get hard lock ups */
+#define USE_DMA
+
+/* We really only need 2 buffers. More than that means being much
+ smarter about knowing which ones are full. */
+#define DMA_CHAIN_COUNT 2 /* max DMA segments/buffers in a ring (min 2)*/
+
+/* Target period for periodic transfers. This sets the user read latency. */
+/* Note: There are certain rates where we give this up and transfer 1/2 FIFO */
+/* If this is too low, efficiency is poor */
#define TRANS_TARGET_PERIOD 10000000 /* 10 ms (in nanoseconds) */
+/* Set a practical limit on how long a list to support (affects memory use) */
/* The board support a channel list up to the FIFO length (1K or 8K) */
-/* Raising the MAX_CHANLIST uses more memory per board */
#define RTD_MAX_CHANLIST 128 /* max channel list that we allow */
/* tuning for ai/ao instruction done polling */
#define WAIT_QUIETLY /* as nothing, spin on done bit */
#define RTD_ADC_TIMEOUT 66000 /* 2 msec at 33mhz bus rate */
#define RTD_DAC_TIMEOUT 66000
+#define RTD_DMA_TIMEOUT 33000 /* 1 msec */
#else
/* by delaying, power and electrical noise are reduced somewhat */
#define WAIT_QUIETLY udelay (1)
#define RTD_ADC_TIMEOUT 2000 /* in usec */
#define RTD_DAC_TIMEOUT 2000 /* in usec */
+#define RTD_DMA_TIMEOUT 1000 /* in usec */
#endif
/*======================================================================
#define RTD_CLOCK_RATE 8000000 /* 8Mhz onboard clock */
#define RTD_CLOCK_BASE 125 /* clock period in ns */
-#define RTD_MAX_SPEED 1600 /* in nanoseconds */
-#define RTD_MIN_SPEED 1000000000 /* in nanoseconds ??? */
+/* Note: these speed are slower than the spec, but fit the counter resolution*/
+#define RTD_MAX_SPEED 1625 /* when sampling, in nanoseconds */
+/* max speed if we don't have to wait for settling */
+#define RTD_MAX_SPEED_1 875 /* if single channel, in nanoseconds */
+
+#define RTD_MIN_SPEED 2097151875 /* (24bit counter) in nanoseconds */
+/* min speed when only 1 channel (no burst counter) */
+#define RTD_MIN_SPEED_1 5000000 /* 200Hz, in nanoseconds */
+
#include "rtd520.h"
+#include "plx9080.h"
+
+/* Setup continuous ring of 1/2 FIFO transfers. See RTD manual p91 */
+#define DMA_MODE_BITS (\
+ PLX_LOCAL_BUS_16_WIDE_BITS \
+ | PLX_DMA_EN_READYIN_BIT \
+ | PLX_DMA_LOCAL_BURST_EN_BIT \
+ | PLX_EN_CHAIN_BIT \
+ | PLX_DMA_INTR_PCI_BIT \
+ | PLX_LOCAL_ADDR_CONST_BIT \
+ | PLX_DEMAND_MODE_BIT)
+
+#define DMA_TRANSFER_BITS (\
+/* descriptors in PCI memory*/ PLX_DESC_IN_PCI_BIT \
+/* interrupt at end of block */ | PLX_INTR_TERM_COUNT \
+/* from board to PCI */ | PLX_XFER_LOCAL_TO_PCI)
/*======================================================================
Comedi specific stuff
unsigned long intCount; /* interrupt count */
long aiCount; /* total transfer size (samples) */
- unsigned long aiExtraInt; /* ints but no data */
int transCount; /* # to tranfer data. 0->1/2FIFO*/
- int aboutWrap; /* number of about overflows needed */
int flags; /* flag event modes */
/* PCI device info */
u16 intClearMask; /* interrupt clear mask */
u8 utcCtrl[4]; /* crtl mode for 3 utc + read back */
u8 dioStatus; /* could be read back (dio0Ctrl) */
-
+#ifdef USE_DMA
+ /* Always DMA 1/2 FIFO. Buffer (dmaBuff?) is (at least) twice that size.
+ After transferring, interrupt processes 1/2 FIFO and passes to comedi */
+ s16 dma0Offset; /* current processing offset (0, 1/2)*/
+ uint16_t *dma0Buff[DMA_CHAIN_COUNT]; /* DMA buffers (for ADC) */
+ dma_addr_t dma0BuffPhysAddr[DMA_CHAIN_COUNT]; /* physical addresses */
+ struct plx_dma_desc *dma0Chain; /* DMA descriptor ring for dmaBuff*/
+ dma_addr_t dma0ChainPhysAddr; /* physical addresses */
+ /* shadow registers */
+ u8 dma0Control;
+ u8 dma1Control;
+#endif /* USE_DMA */
} rtdPrivate;
/* bit defines for "flags" */
#define SEND_EOS 0x01 /* send End Of Scan events */
+#define DMA0_ACTIVE 0x02 /* DMA0 is active */
+#define DMA1_ACTIVE 0x04 /* DMA1 is active */
/* Macros for accessing channel list bit array */
#define CHAN_ARRAY_TEST(array,index) \
writel (v,devpriv->las0+((n <= 0) ? LAS0_UOUT0_SELECT : LAS0_UOUT1_SELECT))
-/* PLX9080 interrupt mask and status */
-#define RtdPLXInterruptRead(dev) \
- readl (devpriv->lcfg+LCFG_ITCSR)
-#define RtdPLXInterruptWrite(dev,v) \
- writel (v, devpriv->lcfg+LCFG_ITCSR)
-
-
/* Digital IO */
#define RtdDio0Read(dev) \
(readw (devpriv->las0+LAS0_DIO0) & 0xff)
#define RtdDacClearFifo(dev,n) \
writel (0, devpriv->las0+(((n) == 0) ? LAS0_DAC1_RESET : LAS0_DAC2_RESET))
+/* Set source for DMA 0 (write only, shadow?) */
+#define RtdDma0Source(dev,n) \
+ writel ((n) & 0xf, devpriv->las0+LAS0_DMA0_SRC)
+
+/* Set source for DMA 1 (write only, shadow?) */
+#define RtdDma1Source(dev,n) \
+ writel ((n) & 0xf, devpriv->las0+LAS0_DMA1_SRC)
+
+/* Reset board state for DMA 0 */
+#define RtdDma0Reset(dev) \
+ writel (0, devpriv->las0+LAS0_DMA0_RESET)
+
+/* Reset board state for DMA 1 */
+#define RtdDma1Reset(dev) \
+ writel (0, devpriv->las0+LAS0_DMA1_SRC)
+
+
+/* PLX9080 interrupt mask and status */
+#define RtdPlxInterruptRead(dev) \
+ readl (devpriv->lcfg+LCFG_ITCSR)
+#define RtdPlxInterruptWrite(dev,v) \
+ writel (v, devpriv->lcfg+LCFG_ITCSR)
+
+/* Set mode for DMA 0 */
+#define RtdDma0Mode(dev,m) \
+ writel ((m), devpriv->lcfg+LCFG_DMAMODE0)
+
+/* Set PCI address for DMA 0 */
+#define RtdDma0PciAddr(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMAPADR0)
+
+/* Set local address for DMA 0 */
+#define RtdDma0LocalAddr(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMALADR0)
+
+/* Set byte count for DMA 0 */
+#define RtdDma0Count(dev,c) \
+ writel ((c), devpriv->lcfg+LCFG_DMASIZ0)
+
+/* Set next descriptor for DMA 0 */
+#define RtdDma0Next(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMADPR0)
+
+/* Set mode for DMA 1 */
+#define RtdDma1Mode(dev,m) \
+ writel ((m), devpriv->lcfg+LCFG_DMAMODE1)
+
+/* Set PCI address for DMA 1 */
+#define RtdDma1PciAddr(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMAADR1)
+
+/* Set local address for DMA 1 */
+#define RtdDma1LocalAddr(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMALADR1)
+
+/* Set byte count for DMA 1 */
+#define RtdDma1Count(dev,c) \
+ writel ((c), devpriv->lcfg+LCFG_DMASIZ1)
+
+/* Set next descriptor for DMA 1 */
+#define RtdDma1Next(dev,a) \
+ writel ((a), devpriv->lcfg+LCFG_DMADPR1)
+
+/* Set control for DMA 0 (write only, shadow?) */
+#define RtdDma0Control(dev,n) \
+ writeb (devpriv->dma0Control = (n), devpriv->lcfg+LCFG_DMACSR0)
+
+/* Get status for DMA 0 */
+#define RtdDma0Status(dev) \
+ readb (devpriv->lcfg+LCFG_DMACSR0)
+
+/* Set control for DMA 1 (write only, shadow?) */
+#define RtdDma1Control(dev,n) \
+ writeb (devpriv->dma1Control = (n), devpriv->lcfg+LCFG_DMACSR1)
+
+/* Get status for DMA 1 */
+#define RtdDma1Status(dev) \
+ readb (devpriv->lcfg+LCFG_DMACSR1)
+
/*
* The comedi_driver structure tells the Comedi core module
* which functions to call to configure/deconfigure (attach/detach)
unsigned long physLas0; /* configuation */
unsigned long physLas1; /* data area */
unsigned long physLcfg; /* PLX9080 */
+#ifdef USE_DMA
+ int index;
+#endif
printk ("comedi%d: rtd520 attaching.\n", dev->minor);
+#if defined (CONFIG_COMEDI_DEBUG) && defined (USE_DMA)
+ /* This is registered as a module parameter. Can be set at run time? */
+ if (0 == comedi_debug) /* enable debug printks */
+ comedi_debug = 1;
+#endif
+
/*
* Allocate the private structure area. alloc_private() is a
* convenient macro defined in comedidev.h.
devpriv->las1 = ioremap(physLas1, LAS1_PCISIZE);
devpriv->lcfg = ioremap(physLcfg, LCFG_PCISIZE);
+ /* It is critical that the FIFO length matches the board. Otherwise,
+ data transfers and DMA will either read 0s or overwrite memory! */
printk ("%s: ( fifoLength = %d )" , dev->board_name, thisboard->fifoLen);
- /*printk ("%s: LAS0=%lx, LAS1=%lx, CFG=%lx.\n", dev->board_name,
+ /*DPRINTK ("%s: LAS0=%lx, LAS1=%lx, CFG=%lx.\n", dev->board_name,
physLas0, physLas1, physLcfg);*/
/*
* n_subdevices being set correctly.
*/
dev->n_subdevices=4;
- if (alloc_subdevices(dev)<0)
+ if (alloc_subdevices(dev)<0) {
return -ENOMEM;
+ }
s=dev->subdevices+0;
dev->read_subdev=s;
dev->irq = devpriv->pci_dev->irq;
if(dev->irq>0){
if((ret=comedi_request_irq (dev->irq, rtd_interrupt,
- SA_SHIRQ, "rtd520", dev))<0)
+ SA_SHIRQ, "rtd520", dev)) < 0) {
+ printk("Could not get interrupt! (%d)\n", dev->irq);
return ret;
- printk("( irq = %d )\n", dev->irq);
+ }
+ printk("( irq = %d )", dev->irq);
} else {
printk("( NO IRQ )");
}
+
+#ifdef USE_DMA
+ printk("( DMA buffers = %d )\n", DMA_CHAIN_COUNT);
+ /* The PLX9080 has 2 DMA controllers, but there could be 4 sources:
+ ADC, digital, DAC1, and DAC2. Since only the ADC supports cmd mode
+ right now, this isn't an issue (yet) */
+ devpriv->dma0Offset = 0;
+
+ for(index = 0; index < DMA_CHAIN_COUNT; index++) {
+ devpriv->dma0Buff[index] =
+ pci_alloc_consistent(devpriv->pci_dev,
+ sizeof (u16) * thisboard->fifoLen/2,
+ &devpriv->dma0BuffPhysAddr[index]);
+ if (devpriv->dma0Buff[index] == NULL) {
+ ret = -ENOMEM;
+ goto rtd_attach_die_error;
+ }
+ DPRINTK ("buff[%d] @ %p virtual, %x PCI\n",
+ index,
+ devpriv->dma0Buff[index], devpriv->dma0BuffPhysAddr[index]);
+ }
+
+ /* setup DMA descriptor ring (use cpu_to_le32 for byte ordering?) */
+ devpriv->dma0Chain =
+ pci_alloc_consistent (devpriv->pci_dev,
+ sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT,
+ &devpriv->dma0ChainPhysAddr);
+ for (index = 0; index < DMA_CHAIN_COUNT; index++) {
+ devpriv->dma0Chain[index].pci_start_addr =
+ devpriv->dma0BuffPhysAddr[index];
+ devpriv->dma0Chain[index].local_start_addr =
+ DMALADDR_ADC;
+ devpriv->dma0Chain[index].transfer_size =
+ sizeof (u16) * thisboard->fifoLen/2;
+ devpriv->dma0Chain[index].next =
+ (devpriv->dma0ChainPhysAddr + ((index + 1) % (DMA_CHAIN_COUNT))
+ * sizeof(devpriv->dma0Chain[0]))
+ | DMA_TRANSFER_BITS;
+ DPRINTK ("ring[%d] @%lx PCI: %x, local: %x, N: 0x%x, next: %x\n",
+ index,
+ ((long)devpriv->dma0ChainPhysAddr
+ + (index * sizeof(devpriv->dma0Chain[0]))),
+ devpriv->dma0Chain[index].pci_start_addr,
+ devpriv->dma0Chain[index].local_start_addr,
+ devpriv->dma0Chain[index].transfer_size,
+ devpriv->dma0Chain[index].next);
+ }
+
+ if (devpriv->dma0Chain == NULL) {
+ ret = -ENOMEM;
+ goto rtd_attach_die_error;
+ }
+
+ RtdDma0Mode (dev, DMA_MODE_BITS);
+ RtdDma0Source (dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */
+#else /* USE_DMA */
+ printk("\n"); /* end setup line */
+#endif /* USE_DMA */
/* initialize board, per RTD spec */
/* also, initialize shadow registers */
RtdUtcCtrlPut (dev, 3, 0); /* safe state, set shadow */
/* TODO: set user out source ??? */
- if (dev->irq) { /* enable interrupt controller */
- RtdPLXInterruptWrite (dev,
- RtdPLXInterruptRead (dev) | (0x0800));
+ if (dev->irq) { /* enable plx9080 interrupts */
+ RtdPlxInterruptWrite (dev, ICS_PIE | ICS_PLIE);
}
printk("comedi%d: rtd520 driver attached.\n", dev->minor);
return 1;
+
+ /* hit an error, clean up memory and return ret */
+rtd_attach_die_error:
+#ifdef USE_DMA
+ for(index = 0; index < DMA_CHAIN_COUNT; index++) {
+ if (NULL != devpriv->dma0Buff[index]) { /* free buffer memory*/
+ pci_free_consistent (devpriv->pci_dev,
+ sizeof (u16) * thisboard->fifoLen/2,
+ devpriv->dma0Buff[index],
+ devpriv->dma0BuffPhysAddr[index]);
+ devpriv->dma0Buff[index] = NULL;
+ }
+ }
+ if (NULL != devpriv->dma0Chain) {
+ pci_free_consistent (devpriv->pci_dev,
+ sizeof(struct plx_dma_desc)
+ * DMA_CHAIN_COUNT,
+ devpriv->dma0Chain,
+ devpriv->dma0ChainPhysAddr);
+ devpriv->dma0Chain = NULL;
+ }
+#endif /* USE_DMA */
+ /* subdevices and priv are freed by the core */
+ if (dev->irq) {
+ /* disable interrupt controller */
+ RtdPlxInterruptWrite (
+ dev,
+ RtdPlxInterruptRead (dev)
+ & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E));
+ comedi_free_irq (dev->irq, dev);
+ }
+
+ /* release all regions that were allocated */
+ if (devpriv->las0) {
+ iounmap (devpriv->las0);
+ }
+ if (devpriv->las1) {
+ iounmap (devpriv->las1);
+ }
+ if (devpriv->lcfg) {
+ iounmap (devpriv->lcfg);
+ }
+ return ret;
}
/*
static int rtd_detach (
comedi_device *dev)
{
- DPRINTK("comedi%d: rtd520: removing (%ld ints, %ld extra ai)\n(int status 0x%x, overrun status 0x%x, fifo status 0x%x)...\n",
- dev->minor, devpriv->intCount, devpriv->aiExtraInt,
+#ifdef USE_DMA
+ int index;
+#endif
+
+ DPRINTK("comedi%d: rtd520: removing (%ld ints)\n(int status 0x%x, overrun status 0x%x, fifo status 0x%x)...\n",
+ dev->minor, devpriv->intCount,
0xffff & RtdInterruptStatus (dev),
0xffff & RtdInterruptOverrunStatus (dev),
(0xffff & RtdFifoStatus (dev)) ^ 0x6666);
if (devpriv) {
/* Shut down any board ops by resetting it */
+#ifdef USE_DMA
+ RtdDma0Control (dev, 0); /* disable DMA */
+ RtdDma1Control (dev, 0); /* disable DMA */
+ RtdPlxInterruptWrite (dev, ICS_PIE | ICS_PLIE);
+#endif /* USE_DMA */
RtdResetBoard (dev);
RtdInterruptMask (dev, 0);
RtdInterruptClearMask (dev,~0);
RtdInterruptClear(dev); /* clears bits set by mask */
+#ifdef USE_DMA
/* release DMA */
+ for(index = 0; index < DMA_CHAIN_COUNT; index++) {
+ if (NULL != devpriv->dma0Buff[index]) {
+ pci_free_consistent (devpriv->pci_dev,
+ sizeof (u16) * thisboard->fifoLen/2,
+ devpriv->dma0Buff[index],
+ devpriv->dma0BuffPhysAddr[index]);
+ devpriv->dma0Buff[index] = NULL;
+ }
+ }
+ if (NULL != devpriv->dma0Chain) {
+ pci_free_consistent (devpriv->pci_dev,
+ sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT,
+ devpriv->dma0Chain,
+ devpriv->dma0ChainPhysAddr);
+ devpriv->dma0Chain = NULL;
+ }
+#endif /* USE_DMA */
/* release IRQ */
if (dev->irq) {
/* disable interrupt controller */
- RtdPLXInterruptWrite (dev,
- RtdPLXInterruptRead (dev) & ~(0x0800));
+ RtdPlxInterruptWrite (
+ dev,
+ RtdPlxInterruptRead (dev)
+ & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E));
comedi_free_irq (dev->irq, dev);
}
}
}
+#ifdef USE_DMA
+/*
+ Terminate a DMA transfer and wait for everything to quiet down
+*/
+void abort_dma (
+ comedi_device *dev,
+ unsigned int channel) /* DMA channel 0, 1 */
+{
+ unsigned long dma_cs_addr; /* the control/status register */
+ uint8_t status;
+ unsigned int ii;
+ //unsigned long flags;
+
+ dma_cs_addr = (unsigned long)devpriv->lcfg
+ + ((channel == 0) ? LCFG_DMACSR0 : LCFG_DMACSR1);
+
+ // spinlock for plx dma control/status reg
+ //comedi_spin_lock_irqsave( &dev->spinlock, flags );
+
+ // abort dma transfer if necessary
+ status = readb(dma_cs_addr);
+ if((status & PLX_DMA_EN_BIT) == 0) { /* not enabled (Error?) */
+ DPRINTK ("rtd520: AbortDma on non-active channel %d (0x%x)\n",
+ channel, status);
+ goto abortDmaExit;
+ }
+
+ /* wait to make sure done bit is zero (needed?) */
+ for (ii = 0;
+ (status & PLX_DMA_DONE_BIT) && ii < RTD_DMA_TIMEOUT;
+ ii++) {
+ WAIT_QUIETLY;
+ status = readb(dma_cs_addr);
+ }
+ if (status & PLX_DMA_DONE_BIT) {
+ printk("rtd520: Timeout waiting for dma %i done clear\n", channel);
+ goto abortDmaExit;
+ }
+
+ /* disable channel (required) */
+ writeb(0, dma_cs_addr);
+ udelay(1); /* needed?? */
+ /* set abort bit for channel */
+ writeb(PLX_DMA_ABORT_BIT, dma_cs_addr);
+
+ // wait for dma done bit to be set
+ status = readb(dma_cs_addr);
+ for(ii = 0;
+ (status & PLX_DMA_DONE_BIT) == 0 && ii < RTD_DMA_TIMEOUT;
+ ii++) {
+ status = readb(dma_cs_addr);
+ WAIT_QUIETLY;
+ }
+ if ((status & PLX_DMA_DONE_BIT) == 0) {
+ printk("rtd520: Timeout waiting for dma %i done set\n", channel);
+ }
+
+abortDmaExit:
+ //comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+}
+
+/*
+ Process what is in the DMA transfer buffer and pass to comedi
+ Note: this is not re-entrant
+*/
+static void ai_process_dma (
+ comedi_device *dev,
+ comedi_subdevice *s)
+{
+ int ii, n;
+ s16 *dp;
+
+ if (devpriv->aiCount == 0) /* transfer already complete */
+ return;
+
+ dp = devpriv->dma0Buff[devpriv->dma0Offset];
+ for (ii = 0; ii < thisboard->fifoLen/2;) { /* convert samples */
+ sampl_t sample;
+
+ if (CHAN_ARRAY_TEST (devpriv->chanBipolar, s->async->cur_chan)) {
+ sample = (*dp >> 3) + 2048; /* convert to comedi unsigned data */
+ } else {
+ sample = *dp >> 3; /* low 3 bits are marker lines */
+ }
+ *dp++ = sample; /* put processed value back */
+
+ if (++s->async->cur_chan >= s->async->cmd.chanlist_len)
+ s->async->cur_chan = 0;
+
+ ++ii; /* number ready to transfer */
+ if (devpriv->aiCount > 0) { /* < 0, means read forever */
+ if (--devpriv->aiCount == 0) { /* done */
+ /*DPRINTK ("rtd520: Final %d samples\n", ii);*/
+ break;
+ }
+ }
+ }
+
+ /* now pass the whole array to the comedi buffer */
+ dp = devpriv->dma0Buff[devpriv->dma0Offset];
+ n = comedi_buf_write_alloc (s->async, ii * sizeof (s16));
+ if (n < (ii * sizeof (s16))) { /* any residual is an error */
+ DPRINTK ("rtd520:ai_process_dma buffer overflow %d samples!\n",
+ ii - (n / sizeof (s16)));
+ s->async->events |= COMEDI_CB_ERROR;
+ }
+ comedi_buf_memcpy_to (s->async, 0, dp, n);
+ comedi_buf_write_free (s->async, n);
+
+ /* always at least 1 scan -- 1/2 FIFO is larger than our max scan list */
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+
+ if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */
+ devpriv->dma0Offset = 0;
+ }
+}
+#endif /* USE_DMA */
+
/*
Handle all rtd520 interrupts.
Runs atomically and is never re-entered.
struct pt_regs *regs) /* cpu context (ignored) */
{
comedi_device *dev = d; /* must be called "dev" for devpriv */
- u16 status = RtdInterruptStatus (dev);
+ u16 status;
u16 fifoStatus;
comedi_subdevice *s = dev->subdevices + 0; /* analog in subdevice */
devpriv->intCount++; /* DEBUG statistics */
- /* if interrupt was not caused by our board */
- /* needed??? we dont claim to share interrupt lines */
- if ((0 == status)
- || !(dev->attached)) {
- return;
- }
-
fifoStatus = RtdFifoStatus (dev);
- /* check for FIFO full, this automatically halts the ADC */
+ /* check for FIFO full, this automatically halts the ADC! */
if (!(fifoStatus & FS_ADC_FULL)) { /* 0 -> full */
DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n",
fifoStatus ^ 0x6666); /* should be all 0s */
RtdPacerStop (dev); /* Stop PACER */
- RtdInterruptMask (dev, 0); /* mask out ABOUT and SAMPLE */
+#ifdef USE_DMA
+ if (devpriv->flags & DMA0_ACTIVE) {
+ abort_dma (dev, 0);
+ devpriv->flags &= ~DMA0_ACTIVE;
+ RtdPlxInterruptWrite (dev,
+ RtdPlxInterruptRead (dev) & ~ICS_DMA0_E);
+ }
+#endif /* USE_DMA */
+ RtdInterruptMask (dev, 0); /* mask out SAMPLE */
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
comedi_event (dev, s, s->async->events);
RtdAdcClearFifo (dev); /* clears full flag */
+ status = RtdInterruptStatus (dev);
RtdInterruptClearMask (dev, status);
RtdInterruptClear (dev);
return;
}
- /* ABOUT does not seem to be causing PCI interrupts */
- if (status & IRQM_ADC_ABOUT_CNT) { /* about count -> done (maybe) */
- if (0 == devpriv->aboutWrap) { /* final about interrupt */
- DPRINTK("rtd520: Done. %ld remain, fifo_status=0x%x\n",
- devpriv->aiCount,
- fifoStatus ^ 0x6666); /* should be all 0s */
- goto transferDone;
- } else if (devpriv->aiCount > 0) { /* finite acquisition */
- /* multi-count wraps */
- if (devpriv->aiCount < devpriv->aboutWrap) {
-#if 0
- DPRINTK("rtd520: About done. %ld remain of %d, fifo_status=0x%x\n",
- devpriv->aiCount, devpriv->aboutWrap,
- fifoStatus ^ 0x6666); /* should be all 0s */
-#endif
- devpriv->aboutWrap = 0;
- RtdAboutStopEnable (dev, 0); /* enable stop */
-
+#ifdef USE_DMA
+ if (devpriv->flags & DMA0_ACTIVE) { /* Check DMA */
+ u32 istatus = RtdPlxInterruptRead (dev);
+
+ if (istatus & ICS_DMA0_A) {
+ ai_process_dma (dev, s);
+ /*DPRINTK ("rtd520: DMA transfer: %ld to go, istatus %x\n",
+ devpriv->aiCount, istatus);*/
+ RtdDma0Control (dev, (devpriv->dma0Control & ~PLX_DMA_START_BIT)
+ | PLX_CLEAR_DMA_INTR_BIT);
+ if (0 == devpriv->aiCount) { /* counted down */
+ DPRINTK("rtd520: Samples Done (DMA).\n");
+ goto transferDone;
}
+ comedi_event (dev, s, s->async->events);
+ } else {
+ /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus);*/
}
}
- else if (status & IRQM_ADC_SAMPLE_CNT) { /* sample count -> read FIFO */
+ /* Fall through and check for other interrupt sources */
+#endif /* USE_DMA */
+
+ status = RtdInterruptStatus (dev);
+ /* if interrupt was not caused by our board, or handled above */
+ if (0 == status) {
+ return;
+ }
+
+ if (status & IRQM_ADC_SAMPLE_CNT) { /* sample count -> read FIFO */
/* since the priority interrupt controller may have queued a sample
counter interrupt, even though we have already finish,
we must handle the possibility that there is no data here */
if (!(fifoStatus & FS_ADC_HEMPTY)) { /* 0 -> 1/2 full */
+ /*DPRINTK("rtd520: Sample int, reading 1/2FIFO. fifo_status 0x%x\n",
+ fifoStatus ^ 0x6666);*/
ai_read_n (dev, s, thisboard->fifoLen / 2);
if (0 == devpriv->aiCount) { /* counted down */
- DPRINTK("rtd520: Samples Done 1/2. fifo_status was 0x%x\n",
+ DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n",
fifoStatus ^ 0x6666); /* should read all 0s */
goto transferDone;
}
comedi_event (dev, s, s->async->events);
} else if (devpriv->transCount > 0) { /* read often */
+ /*DPRINTK("rtd520: Sample int, reading %d fifo_status 0x%x\n",
+ devpriv->transCount, fifoStatus ^ 0x6666);*/
if (fifoStatus & FS_ADC_EMPTY) { /* 1 -> not empty */
ai_read_n (dev, s, devpriv->transCount);
if (0 == devpriv->aiCount) { /* counted down */
- DPRINTK("rtd520: Samples Done n. fifo_status was 0x%x\n",
+ DPRINTK("rtd520: Samples Done (N). fifo_status was 0x%x\n",
fifoStatus ^ 0x6666); /* should read all 0s */
goto transferDone;
}
comedi_event (dev, s, s->async->events);
}
- } /* else wait for 1/2 FIFO */
-
+ } else { /* wait for 1/2 FIFO */
+ /*DPRINTK("rtd520: Sample int. Wait for 1/2. fifo_status 0x%x\n",
+ fifoStatus ^ 0x6666);*/
+ }
}
else {
DPRINTK ("rtd520: unknown interrupt source!\n");
DPRINTK("rtd520: Interrupt overrun! over_status=0x%x\n",
0xffff & RtdInterruptOverrunStatus (dev));
RtdPacerStop (dev); /* Stop PACER */
- RtdInterruptMask (dev, 0); /* mask out ABOUT and SAMPLE */
+ RtdInterruptMask (dev, 0); /* mask out SAMPLE */
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
comedi_event (dev, s, s->async->events);
RtdInterruptOverrunClear (dev);
return;
transferDone:
- RtdInterruptMask (dev, 0); /* mask out ABOUT and SAMPLE */
RtdPacerStop (dev); /* Stop PACER */
-
- fifoStatus = RtdFifoStatus (dev);
- if (!(fifoStatus & FS_ADC_HEMPTY)) { /* 0 -> 1/2 full */
- ai_read_n (dev, s, thisboard->fifoLen / 2);
+ RtdInterruptMask (dev, 0); /* mask out SAMPLE */
+#ifdef USE_DMA
+ if (devpriv->flags & DMA0_ACTIVE) {
+ RtdPlxInterruptWrite (dev, /* disable any more interrupts */
+ RtdPlxInterruptRead (dev) & ~ICS_DMA0_E);
+ abort_dma (dev, 0);
+ devpriv->flags &= ~DMA0_ACTIVE;
+ /* if Using DMA, then we should have read everything by now */
+ if (devpriv->aiCount > 0) {
+ DPRINTK ("rtd520: Lost DMA data! %ld remain\n",
+ devpriv->aiCount);
+ }
+ }
+#endif /* USE_DMA */
+
+ if (devpriv->aiCount > 0) { /* there shouldn't be anything left */
+ fifoStatus = RtdFifoStatus (dev);
+ DPRINTK("rtd520: Finishing up. %ld remain, fifoStat=%x\n",
+ devpriv->aiCount,
+ fifoStatus ^ 0x6666); /* should read all 0s */
+ ai_read_dregs (dev, s); /* read anything left in FIFO */
}
- ai_read_dregs (dev, s); /* read anything left in FIFO */
+
s->async->events |= COMEDI_CB_EOA;/* signal end to comedi */
comedi_event (dev, s, s->async->events);
-
- fifoStatus = RtdFifoStatus (dev);
- DPRINTK("rtd520: Done. %ld ints, %ld extra ai, %ld remain\n\tintStatus=0x%x, overrun_status=0x%x, fifo_status=0x%x\n",
- devpriv->intCount, devpriv->aiExtraInt, devpriv->aiCount,
- status,
- 0xffff & RtdInterruptOverrunStatus (dev),
- fifoStatus ^ 0x6666); /* should be all 0s */
/* clear the interrupt */
+ status = RtdInterruptStatus (dev);
RtdInterruptClearMask (dev, status);
RtdInterruptClear (dev);
+
+ fifoStatus = RtdFifoStatus (dev); /* DEBUG */
+ DPRINTK("rtd520: Acquisition complete. %ld ints, intStat=%x, overStat=%x\n",
+ devpriv->intCount,
+ status,
+ 0xffff & RtdInterruptOverrunStatus (dev));
}
#if 0
static int rtd_ai_poll (comedi_device *dev,comedi_subdevice *s)
{
/* TODO: This needs to mask interrupts, read_dregs, and then re-enable */
+ /* Not sure what to do if DMA is active */
return s->async->buf_write_count - s->async->buf_read_count;
}
#endif
}
if (cmd->scan_begin_src == TRIG_TIMER){
- if (cmd->scan_begin_arg < RTD_MAX_SPEED) {
- cmd->scan_begin_arg = RTD_MAX_SPEED;
- rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_UP);
- err++;
- }
- if (cmd->scan_begin_arg > RTD_MIN_SPEED) {
- cmd->scan_begin_arg = RTD_MIN_SPEED;
- rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_DOWN);
- err++;
+ /* Note: these are time periods, not actual rates */
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (cmd->scan_begin_arg < RTD_MAX_SPEED_1) {
+ cmd->scan_begin_arg = RTD_MAX_SPEED_1;
+ rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_UP);
+ err++;
+ }
+ if (cmd->scan_begin_arg > RTD_MIN_SPEED_1) {
+ cmd->scan_begin_arg = RTD_MIN_SPEED_1;
+ rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_DOWN);
+ err++;
+ }
+ } else {
+ if (cmd->scan_begin_arg < RTD_MAX_SPEED) {
+ cmd->scan_begin_arg = RTD_MAX_SPEED;
+ rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_UP);
+ err++;
+ }
+ if (cmd->scan_begin_arg > RTD_MIN_SPEED) {
+ cmd->scan_begin_arg = RTD_MIN_SPEED;
+ rtd_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_DOWN);
+ err++;
+ }
}
} else {
/* external trigger */
}
}
if (cmd->convert_src==TRIG_TIMER) {
- if (cmd->convert_arg < RTD_MAX_SPEED) {
- cmd->convert_arg = RTD_MAX_SPEED;
- rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_UP);
- err++;
- }
- if (cmd->convert_arg > RTD_MIN_SPEED) {
- cmd->convert_arg = RTD_MIN_SPEED;
- rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_DOWN);
- err++;
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (cmd->convert_arg < RTD_MAX_SPEED_1) {
+ cmd->convert_arg = RTD_MAX_SPEED_1;
+ rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_UP);
+ err++;
+ }
+ if (cmd->convert_arg > RTD_MIN_SPEED_1) {
+ cmd->convert_arg = RTD_MIN_SPEED_1;
+ rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_DOWN);
+ err++;
+ }
+ } else {
+ if (cmd->convert_arg < RTD_MAX_SPEED) {
+ cmd->convert_arg = RTD_MAX_SPEED;
+ rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_UP);
+ err++;
+ }
+ if (cmd->convert_arg > RTD_MIN_SPEED) {
+ cmd->convert_arg = RTD_MIN_SPEED;
+ rtd_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_DOWN);
+ err++;
+ }
}
} else {
/* external trigger */
int timer;
/* stop anything currently running */
- RtdPacerStop (dev); /* Stop PACER */
+ RtdPacerStop (dev); /* make sure PACER is stopped */
RtdAdcClearFifo (dev); /* clear any old data */
RtdInterruptOverrunClear(dev);
+ devpriv->intCount = 0;
+
+ if (! dev->irq) { /* we need interrupts for this */
+ DPRINTK ("rtd520: ERROR! No interrupt available!\n");
+ return -ENXIO;
+ }
/* start configuration */
/* load channel list and reset CGT */
RtdPacerStartSource (dev, 0); /* software triggers pacer */
RtdAdcConversionSource (dev, 1); /* PACER triggers ADC */
}
+ RtdAdcSampleCounter (dev, /* 1/2 FIFO or max sample range */
+ (thisboard->fifoLen > 1024) ? 1023 : 511);
if (TRIG_TIMER == cmd->scan_begin_src) {
/* scan_begin_arg is in nanoseconds */
/* out of counter range, use 1/2 fifo instead */
devpriv->transCount = 0;
devpriv->flags &= ~SEND_EOS;
- RtdAdcSampleCounter (dev,
- (thisboard->fifoLen > 1024) ? 1023 : 511);
} else {
/* interrupt for each tranfer */
RtdAdcSampleCounter (dev, devpriv->transCount-1);
}
- DPRINTK ("rtd520: scanLen=%d tranferCount=%d fifoLen=%d\n\tscanTime(ns)=%d flags=0x%x\n",
+ DPRINTK ("rtd520: scanLen=%d tranferCount=%d fifoLen=%d\n scanTime(ns)=%d flags=0x%x\n",
cmd->chanlist_len, devpriv->transCount, thisboard->fifoLen,
cmd->scan_begin_arg, devpriv->flags);
- } else {
+ } else { /* unknown timing, just use 1/2 FIFO */
devpriv->transCount = 0;
devpriv->flags &= ~SEND_EOS;
- RtdAdcSampleCounter (dev, /* setup a periodic interrupt */
- (thisboard->fifoLen > 1024) ? 1023 : 511);
}
- RtdPacerStopSource (dev, 3); /* stop on ABOUT count down*/
- RtdAboutStopEnable (dev, 0); /* actually stop (see below) */
+ RtdPacerStopSource (dev, 0); /* stop on SOFTWARE stop */
RtdPacerClockSource (dev, 1); /* use INTERNAL 8Mhz clock source */
RtdAdcSampleCounterSource (dev, 1); /* count samples, not scans */
/* First, setup when to stop */
switch(cmd->stop_src){
case TRIG_COUNT: /* stop after N scans */
- if ((cmd->chanlist_len <= 1) /* no scanning to do */
- && (cmd->stop_arg <= 1)) {
- RtdAdcConversionSource (dev, 0); /* SOFTWARE trigger */
- } else {
- int n = cmd->stop_arg * cmd->chanlist_len;
- /* load about counter (16bit) with number of SAMPLES */
- devpriv->aiCount = n;
- if (n <= 0x10000) {
- /* Note: stop on underflow. Load with N-1 */
- DPRINTK ("rtd520: setting about counter to %d\n", n);
- devpriv->aboutWrap = 0;
- RtdAboutCounter (dev, n - 1);
- } else { /* multiple counter wraps */
- int mm, dd, ii;
-
- /*DPRINTK ("rtd520: multi-wrap count %d = %d x %d \n",
- n, cmd->chanlist_len, cmd->stop_arg);*/
- /* interrupt on ABOUT wrap, until last wrap */
- /* we can run long, aiCount handles the excess */
- dd = (n + 0xffff-1) / 0xffff;/* find divisor, round up */
- mm = n / dd;
- /* dd * mm >= n, mm < 0xffff */
-
- /* try to find good divisor */
- for (ii = 0; ((mm*dd) < n) && (ii < 6); ++ii) {
- dd++;
- mm = n / dd;
- }
-
- if ((mm*dd) < n) { /* just run long */
- ++mm;
- /* test case: try asking for 3 x 65537 samples */
- }
- /* dd * mm >= n, mm < 0xffff */
- DPRINTK ("rtd520: multi-wrap count (%d) %d = (%d) x %d\n",
- n, mm*dd, mm, dd);
- devpriv->aboutWrap = mm;
- RtdAboutCounter (dev, mm-1);
- RtdAboutStopEnable (dev, 1); /* just interrupt */
- }
+ devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len;
+ if ((devpriv->transCount > 0)
+ && (devpriv->transCount > devpriv->aiCount)) {
+ devpriv->transCount = devpriv->aiCount;
}
-
break;
case TRIG_NONE: /* stop when cancel is called */
devpriv->aiCount = -1; /* read forever */
- RtdPacerStopSource (dev, 0); /* stop on SOFTWARE stop */
- RtdAboutStopEnable (dev, 1); /* just interrupt. needed??? */
- RtdAboutCounter (dev, ~0); /* minimize interrupts. needed??? */
break;
default:
DPRINTK ("rtd520: Warning! ignoring convert_src mode %d\n",
cmd->convert_src);
}
-
-
/* end configuration */
- /* BUG: start_src is ASSUMED to be TRIG_NOW */
-
- /* interrupt setup */
- if (! dev->irq) {
- DPRINTK ("rtd520: ERROR! No interrupt available!\n");
- return -ENXIO;
- }
- /* This doesn't work. There is NO WAY to clear an interrupt
+ /* This doesn't seem to work. There is no way to clear an interrupt
that the priority controller has queued! */
RtdInterruptClearMask (dev, ~0); /* clear any existing flags */
RtdInterruptClear (dev);
/* TODO: allow multiple interrupt sources */
- if ((devpriv->transCount > 0) || (devpriv->aiCount > 512)) {
- RtdInterruptMask (dev, IRQM_ADC_ABOUT_CNT | IRQM_ADC_SAMPLE_CNT );
- } else {
- RtdInterruptMask (dev, IRQM_ADC_ABOUT_CNT);
+ if (devpriv->transCount > 0) { /* transfer every N samples */
+ RtdInterruptMask (dev, IRQM_ADC_SAMPLE_CNT);
+ DPRINTK ("rtd520: Transferring every %d\n", devpriv->transCount);
+ }
+ else { /* 1/2 FIFO transfers, use DMA */
+#ifdef USE_DMA
+ RtdDma0Control (dev, PLX_CLEAR_DMA_INTR_BIT); /* disable and clear any old ints*/
+ devpriv->flags |= DMA0_ACTIVE;
+
+ /* point to first transfer in ring */
+ devpriv->dma0Offset = 0;
+ RtdDma0Next (dev, /* point to first block */
+ devpriv->dma0Chain[DMA_CHAIN_COUNT-1].next);
+ RtdDma0Mode (dev, DMA_MODE_BITS);
+ RtdDma0Source (dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source*/
+ RtdDma0Reset (dev); /* reset onboard state */
+
+
+ RtdPlxInterruptWrite (dev, /* enable interrupt */
+ RtdPlxInterruptRead (dev) | ICS_DMA0_E);
+ /* Must be 2 steps. See PLX app note about "Starting a DMA transfer"*/
+ RtdDma0Control (dev, PLX_DMA_EN_BIT); /* enable DMA (clear INTR?) */
+ RtdDma0Control (dev, PLX_DMA_EN_BIT | PLX_DMA_START_BIT); /*start DMA*/
+ DPRINTK ("rtd520: Using DMA0 transfers. plxInt %x RtdInt %x\n",
+ RtdPlxInterruptRead (dev), devpriv->intMask);
+#else /* USE_DMA */
+ RtdInterruptMask (dev, IRQM_ADC_SAMPLE_CNT );
+ DPRINTK ("rtd520: Transferring every 1/2 FIFO\n");
+#endif /* USE_DMA */
}
+ /* BUG: start_src is ASSUMED to be TRIG_NOW */
+ /* BUG? it seems like things are running before the "start" */
RtdPacerStart (dev); /* Start PACER */
return 0;
}
/* more is probably needed here */
RtdPacerStop (dev); /* Stop PACER */
RtdAdcConversionSource (dev, 0); /* software trigger only */
+#ifdef USE_DMA
+ if (devpriv->flags & DMA0_ACTIVE) {
+ RtdPlxInterruptWrite (dev, /* disable any more interrupts */
+ RtdPlxInterruptRead (dev) & ~ICS_DMA0_E);
+ abort_dma (dev, 0);
+ }
+#endif /* USE_DMA */
return 0;
}