From: H Hartley Sweeten Date: Thu, 24 May 2012 01:31:48 +0000 (-0700) Subject: staging: comedi: rtd520 complete the refactor to remove all forward declarations X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=c98d90fd1fb9a5ff612e05766198cb09a1dd63b7;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git staging: comedi: rtd520 complete the refactor to remove all forward declarations Complete the refactor of the rtd520 driver to remove all the forward declarations. Signed-off-by: H Hartley Sweeten Cc: Ian Abbott Cc: Mori Hess Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c index d3e2786f07cb..84b28b6b99d6 100644 --- a/drivers/staging/comedi/drivers/rtd520.c +++ b/drivers/staging/comedi/drivers/rtd520.c @@ -702,1616 +702,1586 @@ struct rtdPrivate { #define RtdDma1Status(dev) \ readb(devpriv->lcfg+LCFG_DMACSR1) -static int rtd_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data); -static int rtd_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data); -static int rtd_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data); -static int rtd_dio_insn_bits(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data); -static int rtd_dio_insn_config(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data); -static int rtd_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, - struct comedi_cmd *cmd); -static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); -static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s); /* - * static int rtd_ai_poll(struct comedi_device *dev, - * struct comedi_subdevice *s); - */ -static int rtd_ns_to_timer(unsigned int *ns, int roundMode); -static irqreturn_t rtd_interrupt(int irq, void *d); -static int rtd520_probe_fifo_depth(struct comedi_device *dev); + Given a desired period and the clock period (both in ns), + return the proper counter value (divider-1). + Sets the original period to be the true value. + Note: you have to check if the value is larger than the counter range! +*/ +static int rtd_ns_to_timer_base(unsigned int *nanosec, /* desired period (in ns) */ + int round_mode, int base) +{ /* clock period (in ns) */ + int divider; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + if (divider < 2) + divider = 2; /* min is divide by 2 */ + + /* Note: we don't check for max, because different timers + have different ranges */ + + *nanosec = base * divider; + return divider - 1; /* countdown is divisor+1 */ +} /* - * Attach is called by the Comedi core to configure the driver - * for a particular board. If you specified a board_name array - * in the driver structure, dev->board_ptr contains that - * address. - */ -static int rtd_attach(struct comedi_device *dev, struct comedi_devconfig *it) -{ /* board name and options flags */ - struct comedi_subdevice *s; - struct pci_dev *pcidev; - int ret; - resource_size_t physLas0; /* configuration */ - resource_size_t physLas1; /* data area */ - resource_size_t physLcfg; /* PLX9080 */ -#ifdef USE_DMA - int index; -#endif + Given a desired period (in ns), + return the proper counter value (divider-1) for the internal clock. + Sets the original period to be the true value. +*/ +static int rtd_ns_to_timer(unsigned int *ns, int round_mode) +{ + return rtd_ns_to_timer_base(ns, round_mode, RTD_CLOCK_BASE); +} - printk(KERN_INFO "comedi%d: rtd520 attaching.\n", dev->minor); +/* + Convert a single comedi channel-gain entry to a RTD520 table entry +*/ +static unsigned short rtdConvertChanGain(struct comedi_device *dev, + unsigned int comediChan, int chanIndex) +{ /* index in channel list */ + unsigned int chan, range, aref; + unsigned short r = 0; -#if defined(CONFIG_COMEDI_DEBUG) && defined(USE_DMA) - /* You can set this a load time: modprobe comedi comedi_debug=1 */ - if (0 == comedi_debug) /* force DMA debug printks */ - comedi_debug = 1; -#endif + chan = CR_CHAN(comediChan); + range = CR_RANGE(comediChan); + aref = CR_AREF(comediChan); - /* - * Allocate the private structure area. alloc_private() is a - * convenient macro defined in comedidev.h. - */ - if (alloc_private(dev, sizeof(struct rtdPrivate)) < 0) - return -ENOMEM; + r |= chan & 0xf; - /* - * Probe the device to determine what device in the series it is. - */ - for (pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, NULL); - pcidev != NULL; - pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, pcidev)) { - int i; + /* Note: we also setup the channel list bipolar flag array */ + if (range < thisboard->range10Start) { /* first batch are +-5 */ + r |= 0x000; /* +-5 range */ + r |= (range & 0x7) << 4; /* gain */ + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else if (range < thisboard->rangeUniStart) { /* second batch are +-10 */ + r |= 0x100; /* +-10 range */ + /* gain */ + r |= ((range - thisboard->range10Start) & 0x7) << 4; + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else { /* last batch is +10 */ + r |= 0x200; /* +10 range */ + /* gain */ + r |= ((range - thisboard->rangeUniStart) & 0x7) << 4; + CHAN_ARRAY_CLEAR(devpriv->chanBipolar, chanIndex); + } - if (it->options[0] || it->options[1]) { - if (pcidev->bus->number != it->options[0] - || PCI_SLOT(pcidev->devfn) != it->options[1]) { - continue; - } - } - for (i = 0; i < ARRAY_SIZE(rtd520Boards); ++i) { - if (pcidev->device == rtd520Boards[i].device_id) { - dev->board_ptr = &rtd520Boards[i]; - break; - } + switch (aref) { + case AREF_GROUND: /* on-board ground */ + break; + + case AREF_COMMON: + r |= 0x80; /* ref external analog common */ + break; + + case AREF_DIFF: + r |= 0x400; /* differential inputs */ + break; + + case AREF_OTHER: /* ??? */ + break; + } + /*printk ("chan=%d r=%d a=%d -> 0x%x\n", + chan, range, aref, r); */ + return r; +} + +/* + Setup the channel-gain table from a comedi list +*/ +static void rtd_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list) +{ + if (n_chan > 1) { /* setup channel gain table */ + int ii; + RtdClearCGT(dev); + RtdEnableCGT(dev, 1); /* enable table */ + for (ii = 0; ii < n_chan; ii++) { + RtdWriteCGTable(dev, rtdConvertChanGain(dev, list[ii], + ii)); } - if (dev->board_ptr) - break; /* found one */ + } else { /* just use the channel gain latch */ + RtdEnableCGT(dev, 0); /* disable table, enable latch */ + RtdWriteCGLatch(dev, rtdConvertChanGain(dev, list[0], 0)); } - if (!pcidev) { - if (it->options[0] && it->options[1]) { - printk(KERN_INFO "No RTD card at bus=%d slot=%d.\n", - it->options[0], it->options[1]); - } else { - printk(KERN_INFO "No RTD card found.\n"); +} + +/* determine fifo size by doing adc conversions until the fifo half +empty status flag clears */ +static int rtd520_probe_fifo_depth(struct comedi_device *dev) +{ + unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND); + unsigned i; + static const unsigned limit = 0x2000; + unsigned fifo_size = 0; + + RtdAdcClearFifo(dev); + rtd_load_channelgain_list(dev, 1, &chanspec); + RtdAdcConversionSource(dev, 0); /* software */ + /* convert samples */ + for (i = 0; i < limit; ++i) { + unsigned fifo_status; + /* trigger conversion */ + RtdAdcStart(dev); + udelay(1); + fifo_status = RtdFifoStatus(dev); + if ((fifo_status & FS_ADC_HEMPTY) == 0) { + fifo_size = 2 * i; + break; } + } + if (i == limit) { + printk(KERN_INFO "\ncomedi: %s: failed to probe fifo size.\n", + DRV_NAME); return -EIO; } - devpriv->pci_dev = pcidev; - dev->board_name = thisboard->name; - - ret = comedi_pci_enable(pcidev, DRV_NAME); - if (ret < 0) { - printk(KERN_INFO "Failed to enable PCI device and request regions.\n"); - return ret; + RtdAdcClearFifo(dev); + if (fifo_size != 0x400 && fifo_size != 0x2000) { + printk + (KERN_INFO "\ncomedi: %s: unexpected fifo size of %i, expected 1024 or 8192.\n", + DRV_NAME, fifo_size); + return -EIO; } - devpriv->got_regions = 1; - - /* - * Initialize base addresses - */ - /* Get the physical address from PCI config */ - physLas0 = pci_resource_start(devpriv->pci_dev, LAS0_PCIINDEX); - physLas1 = pci_resource_start(devpriv->pci_dev, LAS1_PCIINDEX); - physLcfg = pci_resource_start(devpriv->pci_dev, LCFG_PCIINDEX); - /* Now have the kernel map this into memory */ - /* ASSUME page aligned */ - devpriv->las0 = ioremap_nocache(physLas0, LAS0_PCISIZE); - devpriv->las1 = ioremap_nocache(physLas1, LAS1_PCISIZE); - devpriv->lcfg = ioremap_nocache(physLcfg, LCFG_PCISIZE); + return fifo_size; +} - if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg) - return -ENOMEM; +/* + "instructions" read/write data in "one-shot" or "software-triggered" + mode (simplest case). + This doesn't use interrupts. + Note, we don't do any settling delays. Use a instruction list to + select, delay, then read. + */ +static int rtd_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n, ii; + int stat; - DPRINTK("%s: LAS0=%llx, LAS1=%llx, CFG=%llx.\n", dev->board_name, - (unsigned long long)physLas0, (unsigned long long)physLas1, - (unsigned long long)physLcfg); - { /* The RTD driver does this */ - unsigned char pci_latency; - u16 revision; - /*uint32_t epld_version; */ + /* clear any old fifo data */ + RtdAdcClearFifo(dev); - pci_read_config_word(devpriv->pci_dev, PCI_REVISION_ID, - &revision); - DPRINTK("%s: PCI revision %d.\n", dev->board_name, revision); + /* write channel to multiplexer and clear channel gain table */ + rtd_load_channelgain_list(dev, 1, &insn->chanspec); - pci_read_config_byte(devpriv->pci_dev, - PCI_LATENCY_TIMER, &pci_latency); - if (pci_latency < 32) { - printk(KERN_INFO "%s: PCI latency changed from %d to %d\n", - dev->board_name, pci_latency, 32); - pci_write_config_byte(devpriv->pci_dev, - PCI_LATENCY_TIMER, 32); - } else { - DPRINTK("rtd520: PCI latency = %d\n", pci_latency); - } + /* set conversion source */ + RtdAdcConversionSource(dev, 0); /* software */ - /* - * Undocumented EPLD version (doesn't match RTD driver results) - */ - /*DPRINTK ("rtd520: Reading epld from %p\n", - devpriv->las0+0); - epld_version = readl (devpriv->las0+0); - if ((epld_version & 0xF0) >> 4 == 0x0F) { - DPRINTK("rtd520: pre-v8 EPLD. (%x)\n", epld_version); - } else { - DPRINTK("rtd520: EPLD version %x.\n", epld_version >> 4); - } */ + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + s16 d; + /* trigger conversion */ + RtdAdcStart(dev); + + for (ii = 0; ii < RTD_ADC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + if (stat & FS_ADC_NOT_EMPTY) /* 1 -> not empty */ + break; + WAIT_QUIETLY; + } + if (ii >= RTD_ADC_TIMEOUT) { + DPRINTK + ("rtd520: Error: ADC never finished! FifoStatus=0x%x\n", + stat ^ 0x6666); + return -ETIMEDOUT; + } + + /* read data */ + d = RtdAdcFifoGet(dev); /* get 2s comp value */ + /*printk ("rtd520: Got 0x%x after %d usec\n", d, ii+1); */ + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, 0)) + /* convert to comedi unsigned data */ + data[n] = d + 2048; + else + data[n] = d; } - /* Show board configuration */ - printk(KERN_INFO "%s:", dev->board_name); + /* return the number of samples read/written */ + return n; +} - /* - * Allocate the subdevice structures. alloc_subdevice() is a - * convenient macro defined in comedidev.h. - */ - if (alloc_subdevices(dev, 4) < 0) - return -ENOMEM; +/* + Get what we know is there.... Fast! + This uses 1/2 the bus cycles of read_dregs (below). + The manual claims that we can do a lword read, but it doesn't work here. +*/ +static int ai_read_n(struct comedi_device *dev, struct comedi_subdevice *s, + int count) +{ + int ii; - s = dev->subdevices + 0; - dev->read_subdev = s; - /* analog input subdevice */ - s->type = COMEDI_SUBD_AI; - s->subdev_flags = - SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF | SDF_CMD_READ; - s->n_chan = thisboard->aiChans; - s->maxdata = (1 << thisboard->aiBits) - 1; - if (thisboard->aiMaxGain <= 32) - s->range_table = &rtd_ai_7520_range; - else - s->range_table = &rtd_ai_4520_range; + for (ii = 0; ii < count; ii++) { + short sample; + s16 d; - s->len_chanlist = RTD_MAX_CHANLIST; /* devpriv->fifoLen */ - s->insn_read = rtd_ai_rinsn; - s->do_cmd = rtd_ai_cmd; - s->do_cmdtest = rtd_ai_cmdtest; - s->cancel = rtd_ai_cancel; - /* s->poll = rtd_ai_poll; *//* not ready yet */ + if (0 == devpriv->aiCount) { /* done */ + d = RtdAdcFifoGet(dev); /* Read N and discard */ + continue; + } +#if 0 + if (0 == (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY)) { /* DEBUG */ + DPRINTK("comedi: READ OOPS on %d of %d\n", ii + 1, + count); + break; + } +#endif + d = RtdAdcFifoGet(dev); /* get 2s comp value */ - s = dev->subdevices + 1; - /* analog output subdevice */ - s->type = COMEDI_SUBD_AO; - s->subdev_flags = SDF_WRITABLE; - s->n_chan = 2; - s->maxdata = (1 << thisboard->aiBits) - 1; - s->range_table = &rtd_ao_range; - s->insn_write = rtd_ao_winsn; - s->insn_read = rtd_ao_rinsn; + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + /* convert to comedi unsigned data */ + sample = d + 2048; + } else + sample = d; - s = dev->subdevices + 2; - /* digital i/o subdevice */ - s->type = COMEDI_SUBD_DIO; - s->subdev_flags = SDF_READABLE | SDF_WRITABLE; - /* we only support port 0 right now. Ignoring port 1 and user IO */ - s->n_chan = 8; - s->maxdata = 1; - s->range_table = &range_digital; - s->insn_bits = rtd_dio_insn_bits; - s->insn_config = rtd_dio_insn_config; + if (!comedi_buf_put(s->async, sample)) + return -1; - /* timer/counter subdevices (not currently supported) */ - s = dev->subdevices + 3; - s->type = COMEDI_SUBD_COUNTER; - s->subdev_flags = SDF_READABLE | SDF_WRITABLE; - s->n_chan = 3; - s->maxdata = 0xffff; + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} - /* initialize board, per RTD spec */ - /* also, initialize shadow registers */ - RtdResetBoard(dev); - udelay(100); /* needed? */ - RtdPlxInterruptWrite(dev, 0); - RtdInterruptMask(dev, 0); /* and sets shadow */ - RtdInterruptClearMask(dev, ~0); /* and sets shadow */ - RtdInterruptClear(dev); /* clears bits set by mask */ - RtdInterruptOverrunClear(dev); - RtdClearCGT(dev); - RtdAdcClearFifo(dev); - RtdDacClearFifo(dev, 0); - RtdDacClearFifo(dev, 1); - /* clear digital IO fifo */ - RtdDioStatusWrite(dev, 0); /* safe state, set shadow */ - RtdUtcCtrlPut(dev, 0, 0x30); /* safe state, set shadow */ - RtdUtcCtrlPut(dev, 1, 0x30); /* safe state, set shadow */ - RtdUtcCtrlPut(dev, 2, 0x30); /* safe state, set shadow */ - RtdUtcCtrlPut(dev, 3, 0); /* safe state, set shadow */ - /* TODO: set user out source ??? */ +/* + unknown amout of data is waiting in fifo. +*/ +static int ai_read_dregs(struct comedi_device *dev, struct comedi_subdevice *s) +{ + while (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + short sample; + s16 d = RtdAdcFifoGet(dev); /* get 2s comp value */ - /* check if our interrupt is available and get it */ - ret = request_irq(devpriv->pci_dev->irq, rtd_interrupt, - IRQF_SHARED, DRV_NAME, dev); + if (0 == devpriv->aiCount) { /* done */ + continue; /* read rest */ + } - if (ret < 0) { - printk("Could not get interrupt! (%u)\n", - devpriv->pci_dev->irq); - return ret; - } - dev->irq = devpriv->pci_dev->irq; - printk(KERN_INFO "( irq=%u )", dev->irq); + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + /* convert to comedi unsigned data */ + sample = d + 2048; + } else + sample = d; - ret = rtd520_probe_fifo_depth(dev); - if (ret < 0) - return ret; + if (!comedi_buf_put(s->async, sample)) + return -1; - devpriv->fifoLen = ret; - printk("( fifoLen=%d )", devpriv->fifoLen); + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} #ifdef USE_DMA - if (dev->irq > 0) { - printk("( DMA buff=%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; +/* + Terminate a DMA transfer and wait for everything to quiet down +*/ +void abort_dma(struct 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; */ - for (index = 0; index < DMA_CHAIN_COUNT; index++) { - devpriv->dma0Buff[index] = - pci_alloc_consistent(devpriv->pci_dev, - sizeof(u16) * - devpriv->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]); */ - } + dma_cs_addr = (unsigned long)devpriv->lcfg + + ((channel == 0) ? LCFG_DMACSR0 : LCFG_DMACSR1); - /* - * 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) * devpriv->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); */ - } + /* spinlock for plx dma control/status reg */ + /* spin_lock_irqsave( &dev->spinlock, flags ); */ - if (devpriv->dma0Chain == NULL) { - ret = -ENOMEM; - goto rtd_attach_die_error; - } + /* 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; + } - RtdDma0Mode(dev, DMA_MODE_BITS); - /* set DMA trigger source */ - RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); - } else { - printk(KERN_INFO "( no IRQ->no DMA )"); + /* 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; } -#endif /* USE_DMA */ - if (dev->irq) { /* enable plx9080 interrupts */ - RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + /* 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); } - printk("\ncomedi%d: rtd520 driver attached.\n", dev->minor); +abortDmaExit: + /* spin_unlock_irqrestore( &dev->spinlock, flags ); */ +} - return 1; +/* + Process what is in the DMA transfer buffer and pass to comedi + Note: this is not re-entrant +*/ +static int ai_process_dma(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int ii, n; + s16 *dp; -#if 0 - /* 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) * devpriv->fifoLen / 2, - devpriv->dma0Buff[index], - devpriv->dma0BuffPhysAddr[index]); - devpriv->dma0Buff[index] = NULL; + if (devpriv->aiCount == 0) /* transfer already complete */ + return 0; + + dp = devpriv->dma0Buff[devpriv->dma0Offset]; + for (ii = 0; ii < devpriv->fifoLen / 2;) { /* convert samples */ + short 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; + } } } - 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)); - free_irq(dev->irq, dev); + + /* 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; + return -1; } + comedi_buf_memcpy_to(s->async, 0, dp, n); + comedi_buf_write_free(s->async, n); - /* release all regions that were allocated */ - if (devpriv->las0) - iounmap(devpriv->las0); + /* + * 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->las1) - iounmap(devpriv->las1); + if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */ + devpriv->dma0Offset = 0; + } + return 0; +} +#endif /* USE_DMA */ - if (devpriv->lcfg) - iounmap(devpriv->lcfg); +/* + Handle all rtd520 interrupts. + Runs atomically and is never re-entered. + This is a "slow handler"; other interrupts may be active. + The data conversion may someday happen in a "bottom half". +*/ +static irqreturn_t rtd_interrupt(int irq, /* interrupt number (ignored) */ + void *d) +{ /* our data *//* cpu context (ignored) */ + struct comedi_device *dev = d; /* must be called "dev" for devpriv */ + u16 status; + u16 fifoStatus; + struct comedi_subdevice *s = dev->subdevices + 0; /* analog in subdevice */ - if (devpriv->pci_dev) - pci_dev_put(devpriv->pci_dev); + if (!dev->attached) + return IRQ_NONE; - return ret; -#endif -} + devpriv->intCount++; /* DEBUG statistics */ -static void rtd_detach(struct comedi_device *dev) -{ + fifoStatus = RtdFifoStatus(dev); + /* check for FIFO full, this automatically halts the ADC! */ + if (!(fifoStatus & FS_ADC_NOT_FULL)) { /* 0 -> full */ + DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto abortTransfer; + } #ifdef USE_DMA - int index; -#endif + if (devpriv->flags & DMA0_ACTIVE) { /* Check DMA */ + u32 istatus = RtdPlxInterruptRead(dev); - if (devpriv) { - /* Shut down any board ops by resetting it */ -#ifdef USE_DMA - if (devpriv->lcfg) { - RtdDma0Control(dev, 0); /* disable DMA */ - RtdDma1Control(dev, 0); /* disable DMA */ - RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); - } -#endif /* USE_DMA */ - if (devpriv->las0) { - 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) * - devpriv->fifoLen / 2, - devpriv->dma0Buff[index], - devpriv-> - dma0BuffPhysAddr[index]); - devpriv->dma0Buff[index] = NULL; + if (istatus & ICS_DMA0_A) { + if (ai_process_dma(dev, s) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (DMA) with %ld to go!\n", + devpriv->aiCount); + RtdDma0Control(dev, + (devpriv->dma0Control & + ~PLX_DMA_START_BIT) + | PLX_CLEAR_DMA_INTR_BIT); + goto abortTransfer; } + + /*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); + } else { + /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus); */ } - 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; - } + } + /* Fall through and check for other interrupt sources */ #endif /* USE_DMA */ - if (dev->irq) { - RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) - & ~(ICS_PLIE | ICS_DMA0_E | - ICS_DMA1_E)); - free_irq(dev->irq, dev); - } - if (devpriv->las0) - iounmap(devpriv->las0); - if (devpriv->las1) - iounmap(devpriv->las1); - if (devpriv->lcfg) - iounmap(devpriv->lcfg); - if (devpriv->pci_dev) { - if (devpriv->got_regions) - comedi_pci_disable(devpriv->pci_dev); - pci_dev_put(devpriv->pci_dev); + + status = RtdInterruptStatus(dev); + /* if interrupt was not caused by our board, or handled above */ + if (0 == status) + return IRQ_HANDLED; + + if (status & IRQM_ADC_ABOUT_CNT) { /* sample count -> read FIFO */ + /* since the priority interrupt controller may have queued a sample + counter interrupt, even though we have already finished, + 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) & 0x7777); */ + if (ai_read_n(dev, s, devpriv->fifoLen / 2) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (1/2FIFO) with %ld to go!\n", + devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto transferDone; + } + comedi_event(dev, s); + } else if (devpriv->transCount > 0) { /* read often */ + /*DPRINTK("rtd520: Sample int, reading %d fifo_status 0x%x\n", + devpriv->transCount, (fifoStatus ^ 0x6666) & 0x7777); */ + if (fifoStatus & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + if (ai_read_n(dev, s, devpriv->transCount) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (N) with %ld to go!\n", + devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK + ("rtd520: Samples Done (N). fifo_status was 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); + goto transferDone; + } + comedi_event(dev, s); + } + } else { /* wait for 1/2 FIFO (old) */ + DPRINTK + ("rtd520: Sample int. Wait for 1/2. fifo_status 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); } + } else { + DPRINTK("rtd520: unknown interrupt source!\n"); } -} -/* - Convert a single comedi channel-gain entry to a RTD520 table entry -*/ -static unsigned short rtdConvertChanGain(struct comedi_device *dev, - unsigned int comediChan, int chanIndex) -{ /* index in channel list */ - unsigned int chan, range, aref; - unsigned short r = 0; + if (0xffff & RtdInterruptOverrunStatus(dev)) { /* interrupt overrun */ + DPRINTK + ("rtd520: Interrupt overrun with %ld to go! over_status=0x%x\n", + devpriv->aiCount, 0xffff & RtdInterruptOverrunStatus(dev)); + goto abortTransfer; + } - chan = CR_CHAN(comediChan); - range = CR_RANGE(comediChan); - aref = CR_AREF(comediChan); + /* clear the interrupt */ + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); + return IRQ_HANDLED; - r |= chan & 0xf; +abortTransfer: + RtdAdcClearFifo(dev); /* clears full flag */ + s->async->events |= COMEDI_CB_ERROR; + devpriv->aiCount = 0; /* stop and don't transfer any more */ + /* fall into transferDone */ - /* Note: we also setup the channel list bipolar flag array */ - if (range < thisboard->range10Start) { /* first batch are +-5 */ - r |= 0x000; /* +-5 range */ - r |= (range & 0x7) << 4; /* gain */ - CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); - } else if (range < thisboard->rangeUniStart) { /* second batch are +-10 */ - r |= 0x100; /* +-10 range */ - /* gain */ - r |= ((range - thisboard->range10Start) & 0x7) << 4; - CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); - } else { /* last batch is +10 */ - r |= 0x200; /* +10 range */ - /* gain */ - r |= ((range - thisboard->rangeUniStart) & 0x7) << 4; - CHAN_ARRAY_CLEAR(devpriv->chanBipolar, chanIndex); +transferDone: + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* Stop PACER */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + 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 */ - switch (aref) { - case AREF_GROUND: /* on-board ground */ - break; + 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) & 0x7777); /* should read all 0s */ + ai_read_dregs(dev, s); /* read anything left in FIFO */ + } - case AREF_COMMON: - r |= 0x80; /* ref external analog common */ - break; + s->async->events |= COMEDI_CB_EOA; /* signal end to comedi */ + comedi_event(dev, s); - case AREF_DIFF: - r |= 0x400; /* differential inputs */ - break; + /* clear the interrupt */ + status = RtdInterruptStatus(dev); + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); - case AREF_OTHER: /* ??? */ - break; - } - /*printk ("chan=%d r=%d a=%d -> 0x%x\n", - chan, range, aref, r); */ - return r; + fifoStatus = RtdFifoStatus(dev); /* DEBUG */ + DPRINTK + ("rtd520: Acquisition complete. %ld ints, intStat=%x, overStat=%x\n", + devpriv->intCount, status, + 0xffff & RtdInterruptOverrunStatus(dev)); + + return IRQ_HANDLED; } +#if 0 /* - Setup the channel-gain table from a comedi list + return the number of samples available */ -static void rtd_load_channelgain_list(struct comedi_device *dev, - unsigned int n_chan, unsigned int *list) +static int rtd_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) { - if (n_chan > 1) { /* setup channel gain table */ - int ii; - RtdClearCGT(dev); - RtdEnableCGT(dev, 1); /* enable table */ - for (ii = 0; ii < n_chan; ii++) { - RtdWriteCGTable(dev, rtdConvertChanGain(dev, list[ii], - ii)); - } - } else { /* just use the channel gain latch */ - RtdEnableCGT(dev, 0); /* disable table, enable latch */ - RtdWriteCGLatch(dev, rtdConvertChanGain(dev, list[0], 0)); - } + /* 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 -/* determine fifo size by doing adc conversions until the fifo half -empty status flag clears */ -static int rtd520_probe_fifo_depth(struct comedi_device *dev) +/* + cmdtest tests a particular command to see if it is valid. + Using the cmdtest ioctl, a user can create a valid cmd + and then have it executed by the cmd ioctl (asyncronously). + + cmdtest returns 1,2,3,4 or 0, depending on which tests + the command passes. +*/ + +static int rtd_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) { - unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND); - unsigned i; - static const unsigned limit = 0x2000; - unsigned fifo_size = 0; + int err = 0; + int tmp; - RtdAdcClearFifo(dev); - rtd_load_channelgain_list(dev, 1, &chanspec); - RtdAdcConversionSource(dev, 0); /* software */ - /* convert samples */ - for (i = 0; i < limit; ++i) { - unsigned fifo_status; - /* trigger conversion */ - RtdAdcStart(dev); - udelay(1); - fifo_status = RtdFifoStatus(dev); - if ((fifo_status & FS_ADC_HEMPTY) == 0) { - fifo_size = 2 * i; - break; - } - } - if (i == limit) { - printk(KERN_INFO "\ncomedi: %s: failed to probe fifo size.\n", - DRV_NAME); - return -EIO; - } - RtdAdcClearFifo(dev); - if (fifo_size != 0x400 && fifo_size != 0x2000) { - printk - (KERN_INFO "\ncomedi: %s: unexpected fifo size of %i, expected 1024 or 8192.\n", - DRV_NAME, fifo_size); - return -EIO; - } - return fifo_size; -} + /* step 1: make sure trigger sources are trivially valid */ -/* - "instructions" read/write data in "one-shot" or "software-triggered" - mode (simplest case). - This doesn't use interrupts. + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; - Note, we don't do any settling delays. Use a instruction list to - select, delay, then read. - */ -static int rtd_ai_rinsn(struct comedi_device *dev, - struct comedi_subdevice *s, struct comedi_insn *insn, - unsigned int *data) -{ - int n, ii; - int stat; + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; - /* clear any old fifo data */ - RtdAdcClearFifo(dev); - /* write channel to multiplexer and clear channel gain table */ - rtd_load_channelgain_list(dev, 1, &insn->chanspec); + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; - /* set conversion source */ - RtdAdcConversionSource(dev, 0); /* software */ - /* convert n samples */ - for (n = 0; n < insn->n; n++) { - s16 d; - /* trigger conversion */ - RtdAdcStart(dev); + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; - for (ii = 0; ii < RTD_ADC_TIMEOUT; ++ii) { - stat = RtdFifoStatus(dev); - if (stat & FS_ADC_NOT_EMPTY) /* 1 -> not empty */ - break; - WAIT_QUIETLY; - } - if (ii >= RTD_ADC_TIMEOUT) { - DPRINTK - ("rtd520: Error: ADC never finished! FifoStatus=0x%x\n", - stat ^ 0x6666); - return -ETIMEDOUT; - } - /* read data */ - d = RtdAdcFifoGet(dev); /* get 2s comp value */ - /*printk ("rtd520: Got 0x%x after %d usec\n", d, ii+1); */ - d = d >> 3; /* low 3 bits are marker lines */ - if (CHAN_ARRAY_TEST(devpriv->chanBipolar, 0)) - /* convert to comedi unsigned data */ - data[n] = d + 2048; - else - data[n] = d; + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique + and mutually compatible */ + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) { + err++; } + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; - /* return the number of samples read/written */ - return n; -} + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; -/* - Get what we know is there.... Fast! - This uses 1/2 the bus cycles of read_dregs (below). + if (err) + return 2; - The manual claims that we can do a lword read, but it doesn't work here. -*/ -static int ai_read_n(struct comedi_device *dev, struct comedi_subdevice *s, - int count) -{ - int ii; + /* step 3: make sure arguments are trivially compatible */ - for (ii = 0; ii < count; ii++) { - short sample; - s16 d; + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } - if (0 == devpriv->aiCount) { /* done */ - d = RtdAdcFifoGet(dev); /* Read N and discard */ - continue; + if (cmd->scan_begin_src == TRIG_TIMER) { + /* 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++; + } } -#if 0 - if (0 == (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY)) { /* DEBUG */ - DPRINTK("comedi: READ OOPS on %d of %d\n", ii + 1, - count); - break; + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + 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 */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; } + } + +#if 0 + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } #endif - d = RtdAdcFifoGet(dev); /* get 2s comp value */ + if (cmd->stop_src == TRIG_COUNT) { + /* TODO check for rounding error due to counter wrap */ - d = d >> 3; /* low 3 bits are marker lines */ - if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { - /* convert to comedi unsigned data */ - sample = d + 2048; - } else - sample = d; + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } - if (!comedi_buf_put(s->async, sample)) - return -1; + if (err) + return 3; - if (devpriv->aiCount > 0) /* < 0, means read forever */ - devpriv->aiCount--; + + /* step 4: fix up any arguments */ + + if (cmd->chanlist_len > RTD_MAX_CHANLIST) { + cmd->chanlist_len = RTD_MAX_CHANLIST; + err++; } - return 0; -} + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + rtd_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; -/* - unknown amout of data is waiting in fifo. -*/ -static int ai_read_dregs(struct comedi_device *dev, struct comedi_subdevice *s) -{ - while (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ - short sample; - s16 d = RtdAdcFifoGet(dev); /* get 2s comp value */ + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + rtd_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; - if (0 == devpriv->aiCount) { /* done */ - continue; /* read rest */ + if (cmd->scan_begin_src == TRIG_TIMER + && (cmd->scan_begin_arg + < (cmd->convert_arg * cmd->scan_end_arg))) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; } + } - d = d >> 3; /* low 3 bits are marker lines */ - if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { - /* convert to comedi unsigned data */ - sample = d + 2048; - } else - sample = d; - - if (!comedi_buf_put(s->async, sample)) - return -1; + if (err) + return 4; - if (devpriv->aiCount > 0) /* < 0, means read forever */ - devpriv->aiCount--; - } return 0; } -#ifdef USE_DMA /* - Terminate a DMA transfer and wait for everything to quiet down + Execute a analog in command with many possible triggering options. + The data get stored in the async structure of the subdevice. + This is usually done by an interrupt handler. + Userland gets to the data using read calls. */ -void abort_dma(struct 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 */ - /* spin_lock_irqsave( &dev->spinlock, flags ); */ +static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int timer; - /* 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; + /* stop anything currently running */ + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* make sure PACER is stopped */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { /* cancel anything running */ + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + if (RtdPlxInterruptRead(dev) & ICS_DMA0_A) { /*clear pending int */ + RtdDma0Control(dev, PLX_CLEAR_DMA_INTR_BIT); + } } + RtdDma0Reset(dev); /* reset onboard state */ +#endif /* USE_DMA */ + RtdAdcClearFifo(dev); /* clear any old data */ + RtdInterruptOverrunClear(dev); + devpriv->intCount = 0; - /* 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; + if (!dev->irq) { /* we need interrupts for this */ + DPRINTK("rtd520: ERROR! No interrupt available!\n"); + return -ENXIO; } - /* disable channel (required) */ - writeb(0, dma_cs_addr); - udelay(1); /* needed?? */ - /* set abort bit for channel */ - writeb(PLX_DMA_ABORT_BIT, dma_cs_addr); + /* start configuration */ + /* load channel list and reset CGT */ + rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); - /* 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; + /* setup the common case and override if needed */ + if (cmd->chanlist_len > 1) { + /*DPRINTK ("rtd520: Multi channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdBurstStartSource(dev, 1); /* PACER triggers burst */ + RtdAdcConversionSource(dev, 2); /* BURST triggers ADC */ + } else { /* single channel */ + /*DPRINTK ("rtd520: single channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdAdcConversionSource(dev, 1); /* PACER triggers ADC */ } - if ((status & PLX_DMA_DONE_BIT) == 0) { - printk("rtd520: Timeout waiting for dma %i done set\n", - channel); + RtdAboutCounter(dev, devpriv->fifoLen / 2 - 1); /* 1/2 FIFO */ + + if (TRIG_TIMER == cmd->scan_begin_src) { + /* scan_begin_arg is in nanoseconds */ + /* find out how many samples to wait before transferring */ + if (cmd->flags & TRIG_WAKE_EOS) { + /* this may generate un-sustainable interrupt rates */ + /* the application is responsible for doing the right thing */ + devpriv->transCount = cmd->chanlist_len; + devpriv->flags |= SEND_EOS; + } else { + /* arrange to transfer data periodically */ + devpriv->transCount + = + (TRANS_TARGET_PERIOD * cmd->chanlist_len) / + cmd->scan_begin_arg; + if (devpriv->transCount < cmd->chanlist_len) { + /* transfer after each scan (and avoid 0) */ + devpriv->transCount = cmd->chanlist_len; + } else { /* make a multiple of scan length */ + devpriv->transCount = + (devpriv->transCount + + cmd->chanlist_len - 1) + / cmd->chanlist_len; + devpriv->transCount *= cmd->chanlist_len; + } + devpriv->flags |= SEND_EOS; + } + if (devpriv->transCount >= (devpriv->fifoLen / 2)) { + /* out of counter range, use 1/2 fifo instead */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; + } else { + /* interrupt for each transfer */ + RtdAboutCounter(dev, devpriv->transCount - 1); + } + + DPRINTK + ("rtd520: scanLen=%d transferCount=%d fifoLen=%d\n scanTime(ns)=%d flags=0x%x\n", + cmd->chanlist_len, devpriv->transCount, devpriv->fifoLen, + cmd->scan_begin_arg, devpriv->flags); + } else { /* unknown timing, just use 1/2 FIFO */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; } + RtdPacerClockSource(dev, 1); /* use INTERNAL 8Mhz clock source */ + RtdAboutStopEnable(dev, 1); /* just interrupt, dont stop */ -abortDmaExit: - /* spin_unlock_irqrestore( &dev->spinlock, flags ); */ -} + /* BUG??? these look like enumerated values, but they are bit fields */ -/* - Process what is in the DMA transfer buffer and pass to comedi - Note: this is not re-entrant -*/ -static int ai_process_dma(struct comedi_device *dev, struct comedi_subdevice *s) -{ - int ii, n; - s16 *dp; + /* First, setup when to stop */ + switch (cmd->stop_src) { + case TRIG_COUNT: /* stop after N scans */ + devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len; + if ((devpriv->transCount > 0) + && (devpriv->transCount > devpriv->aiCount)) { + devpriv->transCount = devpriv->aiCount; + } + break; - if (devpriv->aiCount == 0) /* transfer already complete */ - return 0; + case TRIG_NONE: /* stop when cancel is called */ + devpriv->aiCount = -1; /* read forever */ + break; - dp = devpriv->dma0Buff[devpriv->dma0Offset]; - for (ii = 0; ii < devpriv->fifoLen / 2;) { /* convert samples */ - short sample; + default: + DPRINTK("rtd520: Warning! ignoring stop_src mode %d\n", + cmd->stop_src); + } + + /* Scan timing */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: /* periodic scanning */ + timer = rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + /* set PACER clock */ + /*DPRINTK ("rtd520: loading %d into pacer\n", timer); */ + RtdPacerCounter(dev, timer); - 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 */ + break; - *dp++ = sample; /* put processed value back */ + case TRIG_EXT: + RtdPacerStartSource(dev, 1); /* EXTERNALy trigger pacer */ + break; - if (++s->async->cur_chan >= s->async->cmd.chanlist_len) - s->async->cur_chan = 0; + default: + DPRINTK("rtd520: Warning! ignoring scan_begin_src mode %d\n", + cmd->scan_begin_src); + } - ++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; - } + /* Sample timing within a scan */ + switch (cmd->convert_src) { + case TRIG_TIMER: /* periodic */ + if (cmd->chanlist_len > 1) { /* only needed for multi-channel */ + timer = rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_NEAREST); + /* setup BURST clock */ + /*DPRINTK ("rtd520: loading %d into burst\n", timer); */ + RtdBurstCounter(dev, timer); } - } - /* 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; - return -1; - } - comedi_buf_memcpy_to(s->async, 0, dp, n); - comedi_buf_write_free(s->async, n); + break; - /* - * always at least 1 scan -- 1/2 FIFO is larger than our max scan list - */ - s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + case TRIG_EXT: /* external */ + RtdBurstStartSource(dev, 2); /* EXTERNALy trigger burst */ + break; - if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */ - devpriv->dma0Offset = 0; + default: + DPRINTK("rtd520: Warning! ignoring convert_src mode %d\n", + cmd->convert_src); } - return 0; -} -#endif /* USE_DMA */ - -/* - Handle all rtd520 interrupts. - Runs atomically and is never re-entered. - This is a "slow handler"; other interrupts may be active. - The data conversion may someday happen in a "bottom half". -*/ -static irqreturn_t rtd_interrupt(int irq, /* interrupt number (ignored) */ - void *d) -{ /* our data *//* cpu context (ignored) */ - struct comedi_device *dev = d; /* must be called "dev" for devpriv */ - u16 status; - u16 fifoStatus; - struct comedi_subdevice *s = dev->subdevices + 0; /* analog in subdevice */ - - if (!dev->attached) - return IRQ_NONE; + /* end configuration */ - devpriv->intCount++; /* DEBUG statistics */ + /* 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); - fifoStatus = RtdFifoStatus(dev); - /* check for FIFO full, this automatically halts the ADC! */ - if (!(fifoStatus & FS_ADC_NOT_FULL)) { /* 0 -> full */ - DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ - goto abortTransfer; - } + /* TODO: allow multiple interrupt sources */ + if (devpriv->transCount > 0) { /* transfer every N samples */ + RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); + DPRINTK("rtd520: Transferring every %d\n", devpriv->transCount); + } else { /* 1/2 FIFO transfers */ #ifdef USE_DMA - if (devpriv->flags & DMA0_ACTIVE) { /* Check DMA */ - u32 istatus = RtdPlxInterruptRead(dev); + devpriv->flags |= DMA0_ACTIVE; - if (istatus & ICS_DMA0_A) { - if (ai_process_dma(dev, s) < 0) { - DPRINTK - ("rtd520: comedi read buffer overflow (DMA) with %ld to go!\n", - devpriv->aiCount); - RtdDma0Control(dev, - (devpriv->dma0Control & - ~PLX_DMA_START_BIT) - | PLX_CLEAR_DMA_INTR_BIT); - goto abortTransfer; - } + /* point to first transfer in ring */ + devpriv->dma0Offset = 0; + RtdDma0Mode(dev, DMA_MODE_BITS); + RtdDma0Next(dev, /* point to first block */ + devpriv->dma0Chain[DMA_CHAIN_COUNT - 1].next); + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */ - /*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); - } else { - /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus); */ - } - } - /* Fall through and check for other interrupt sources */ + 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_ABOUT_CNT); + DPRINTK("rtd520: Transferring every 1/2 FIFO\n"); #endif /* USE_DMA */ - - status = RtdInterruptStatus(dev); - /* if interrupt was not caused by our board, or handled above */ - if (0 == status) - return IRQ_HANDLED; - - if (status & IRQM_ADC_ABOUT_CNT) { /* sample count -> read FIFO */ - /* since the priority interrupt controller may have queued a sample - counter interrupt, even though we have already finished, - 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) & 0x7777); */ - if (ai_read_n(dev, s, devpriv->fifoLen / 2) < 0) { - DPRINTK - ("rtd520: comedi read buffer overflow (1/2FIFO) with %ld to go!\n", - devpriv->aiCount); - goto abortTransfer; - } - if (0 == devpriv->aiCount) { /* counted down */ - DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ - goto transferDone; - } - comedi_event(dev, s); - } else if (devpriv->transCount > 0) { /* read often */ - /*DPRINTK("rtd520: Sample int, reading %d fifo_status 0x%x\n", - devpriv->transCount, (fifoStatus ^ 0x6666) & 0x7777); */ - if (fifoStatus & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ - if (ai_read_n(dev, s, devpriv->transCount) < 0) { - DPRINTK - ("rtd520: comedi read buffer overflow (N) with %ld to go!\n", - devpriv->aiCount); - goto abortTransfer; - } - if (0 == devpriv->aiCount) { /* counted down */ - DPRINTK - ("rtd520: Samples Done (N). fifo_status was 0x%x\n", - (fifoStatus ^ 0x6666) & 0x7777); - goto transferDone; - } - comedi_event(dev, s); - } - } else { /* wait for 1/2 FIFO (old) */ - DPRINTK - ("rtd520: Sample int. Wait for 1/2. fifo_status 0x%x\n", - (fifoStatus ^ 0x6666) & 0x7777); - } - } else { - DPRINTK("rtd520: unknown interrupt source!\n"); - } - - if (0xffff & RtdInterruptOverrunStatus(dev)) { /* interrupt overrun */ - DPRINTK - ("rtd520: Interrupt overrun with %ld to go! over_status=0x%x\n", - devpriv->aiCount, 0xffff & RtdInterruptOverrunStatus(dev)); - goto abortTransfer; } - /* clear the interrupt */ - RtdInterruptClearMask(dev, status); - RtdInterruptClear(dev); - return IRQ_HANDLED; + /* 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; +} -abortTransfer: - RtdAdcClearFifo(dev); /* clears full flag */ - s->async->events |= COMEDI_CB_ERROR; - devpriv->aiCount = 0; /* stop and don't transfer any more */ - /* fall into transferDone */ +/* + Stop a running data acquisition. +*/ +static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + u16 status; -transferDone: RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ RtdPacerStop(dev); /* Stop PACER */ RtdAdcConversionSource(dev, 0); /* software trigger only */ - RtdInterruptMask(dev, 0); /* mask out SAMPLE */ + RtdInterruptMask(dev, 0); + devpriv->aiCount = 0; /* stop and don't transfer any more */ #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) & 0x7777); /* should read all 0s */ - ai_read_dregs(dev, s); /* read anything left in FIFO */ - } - - s->async->events |= COMEDI_CB_EOA; /* signal end to comedi */ - comedi_event(dev, s); - - /* 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)); - - return IRQ_HANDLED; -} - -#if 0 -/* - return the number of samples available -*/ -static int rtd_ai_poll(struct comedi_device *dev, struct 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; + ("rtd520: Acquisition canceled. %ld ints, intStat=%x, overStat=%x\n", + devpriv->intCount, status, + 0xffff & RtdInterruptOverrunStatus(dev)); + return 0; } -#endif /* - cmdtest tests a particular command to see if it is valid. - Using the cmdtest ioctl, a user can create a valid cmd - and then have it executed by the cmd ioctl (asyncronously). - - cmdtest returns 1,2,3,4 or 0, depending on which tests - the command passes. + Output one (or more) analog values to a single port as fast as possible. */ - -static int rtd_ai_cmdtest(struct comedi_device *dev, - struct comedi_subdevice *s, struct comedi_cmd *cmd) +static int rtd_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) { - int err = 0; - int tmp; - - /* step 1: make sure trigger sources are trivially valid */ - - tmp = cmd->start_src; - cmd->start_src &= TRIG_NOW; - if (!cmd->start_src || tmp != cmd->start_src) - err++; - - tmp = cmd->scan_begin_src; - cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; - if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) - err++; - - - tmp = cmd->convert_src; - cmd->convert_src &= TRIG_TIMER | TRIG_EXT; - if (!cmd->convert_src || tmp != cmd->convert_src) - err++; - - - tmp = cmd->scan_end_src; - cmd->scan_end_src &= TRIG_COUNT; - if (!cmd->scan_end_src || tmp != cmd->scan_end_src) - err++; - - - tmp = cmd->stop_src; - cmd->stop_src &= TRIG_COUNT | TRIG_NONE; - if (!cmd->stop_src || tmp != cmd->stop_src) - err++; + int i; + int chan = CR_CHAN(insn->chanspec); + int range = CR_RANGE(insn->chanspec); + /* Configure the output range (table index matches the range values) */ + RtdDacRange(dev, chan, range); - if (err) - return 1; + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; ++i) { + int val = data[i] << 3; + int stat = 0; /* initialize to avoid bogus warning */ + int ii; - /* step 2: make sure trigger sources are unique - and mutually compatible */ - /* note that mutual compatibility is not an issue here */ - if (cmd->scan_begin_src != TRIG_TIMER && - cmd->scan_begin_src != TRIG_EXT) { - err++; - } - if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) - err++; + /* VERIFY: comedi range and offset conversions */ - if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) - err++; + if ((range > 1) /* bipolar */ + && (data[i] < 2048)) { + /* offset and sign extend */ + val = (((int)data[i]) - 2048) << 3; + } else { /* unipolor */ + val = data[i] << 3; + } - if (err) - return 2; + DPRINTK + ("comedi: rtd520 DAC chan=%d range=%d writing %d as 0x%x\n", + chan, range, data[i], val); - /* step 3: make sure arguments are trivially compatible */ + /* a typical programming sequence */ + RtdDacFifoPut(dev, chan, val); /* put the value in */ + RtdDacUpdate(dev, chan); /* trigger the conversion */ - if (cmd->start_arg != 0) { - cmd->start_arg = 0; - err++; - } + devpriv->aoValue[chan] = data[i]; /* save for read back */ - if (cmd->scan_begin_src == TRIG_TIMER) { - /* 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 */ - /* should be level/edge, hi/lo specification here */ - /* should specify multiple external triggers */ - if (cmd->scan_begin_arg > 9) { - cmd->scan_begin_arg = 9; - err++; - } - } - if (cmd->convert_src == TRIG_TIMER) { - 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++; - } + for (ii = 0; ii < RTD_DAC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + /* 1 -> not empty */ + if (stat & ((0 == chan) ? FS_DAC1_NOT_EMPTY : + FS_DAC2_NOT_EMPTY)) + break; + WAIT_QUIETLY; } - } else { - /* external trigger */ - /* see above */ - if (cmd->convert_arg > 9) { - cmd->convert_arg = 9; - err++; + if (ii >= RTD_DAC_TIMEOUT) { + DPRINTK + ("rtd520: Error: DAC never finished! FifoStatus=0x%x\n", + stat ^ 0x6666); + return -ETIMEDOUT; } } -#if 0 - if (cmd->scan_end_arg != cmd->chanlist_len) { - cmd->scan_end_arg = cmd->chanlist_len; - err++; - } -#endif - if (cmd->stop_src == TRIG_COUNT) { - /* TODO check for rounding error due to counter wrap */ + /* return the number of samples read/written */ + return i; +} - } else { - /* TRIG_NONE */ - if (cmd->stop_arg != 0) { - cmd->stop_arg = 0; - err++; - } - } +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int rtd_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); - if (err) - return 3; + for (i = 0; i < insn->n; i++) + data[i] = devpriv->aoValue[chan]; - /* step 4: fix up any arguments */ + return i; +} - if (cmd->chanlist_len > RTD_MAX_CHANLIST) { - cmd->chanlist_len = RTD_MAX_CHANLIST; - err++; - } - if (cmd->scan_begin_src == TRIG_TIMER) { - tmp = cmd->scan_begin_arg; - rtd_ns_to_timer(&cmd->scan_begin_arg, - cmd->flags & TRIG_ROUND_MASK); - if (tmp != cmd->scan_begin_arg) - err++; +/* + Write a masked set of bits and the read back the port. + We track what the bits should be (i.e. we don't read the port first). - } - if (cmd->convert_src == TRIG_TIMER) { - tmp = cmd->convert_arg; - rtd_ns_to_timer(&cmd->convert_arg, - cmd->flags & TRIG_ROUND_MASK); - if (tmp != cmd->convert_arg) - err++; + DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write + */ +static int rtd_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; - if (cmd->scan_begin_src == TRIG_TIMER - && (cmd->scan_begin_arg - < (cmd->convert_arg * cmd->scan_end_arg))) { - cmd->scan_begin_arg = - cmd->convert_arg * cmd->scan_end_arg; - err++; - } + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + RtdDio0Write(dev, s->state); } + /* on return, data[1] contains the value of the digital + * input lines. */ + data[1] = RtdDio0Read(dev); - if (err) - return 4; + /*DPRINTK("rtd520:port_0 wrote: 0x%x read: 0x%x\n", s->state, data[1]); */ - return 0; + return 2; } /* - Execute a analog in command with many possible triggering options. - The data get stored in the async structure of the subdevice. - This is usually done by an interrupt handler. - Userland gets to the data using read calls. + Configure one bit on a IO port as Input or Output (hence the name :-). */ -static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +static int rtd_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) { - struct comedi_cmd *cmd = &s->async->cmd; - int timer; - - /* stop anything currently running */ - RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ - RtdPacerStop(dev); /* make sure PACER is stopped */ - RtdAdcConversionSource(dev, 0); /* software trigger only */ - RtdInterruptMask(dev, 0); -#ifdef USE_DMA - if (devpriv->flags & DMA0_ACTIVE) { /* cancel anything running */ - RtdPlxInterruptWrite(dev, /* disable any more interrupts */ - RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); - abort_dma(dev, 0); - devpriv->flags &= ~DMA0_ACTIVE; - if (RtdPlxInterruptRead(dev) & ICS_DMA0_A) { /*clear pending int */ - RtdDma0Control(dev, PLX_CLEAR_DMA_INTR_BIT); - } - } - RtdDma0Reset(dev); /* reset onboard state */ -#endif /* USE_DMA */ - 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 */ - rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + int chan = CR_CHAN(insn->chanspec); - /* setup the common case and override if needed */ - if (cmd->chanlist_len > 1) { - /*DPRINTK ("rtd520: Multi channel setup\n"); */ - RtdPacerStartSource(dev, 0); /* software triggers pacer */ - RtdBurstStartSource(dev, 1); /* PACER triggers burst */ - RtdAdcConversionSource(dev, 2); /* BURST triggers ADC */ - } else { /* single channel */ - /*DPRINTK ("rtd520: single channel setup\n"); */ - RtdPacerStartSource(dev, 0); /* software triggers pacer */ - RtdAdcConversionSource(dev, 1); /* PACER triggers ADC */ + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; } - RtdAboutCounter(dev, devpriv->fifoLen / 2 - 1); /* 1/2 FIFO */ - if (TRIG_TIMER == cmd->scan_begin_src) { - /* scan_begin_arg is in nanoseconds */ - /* find out how many samples to wait before transferring */ - if (cmd->flags & TRIG_WAKE_EOS) { - /* this may generate un-sustainable interrupt rates */ - /* the application is responsible for doing the right thing */ - devpriv->transCount = cmd->chanlist_len; - devpriv->flags |= SEND_EOS; - } else { - /* arrange to transfer data periodically */ - devpriv->transCount - = - (TRANS_TARGET_PERIOD * cmd->chanlist_len) / - cmd->scan_begin_arg; - if (devpriv->transCount < cmd->chanlist_len) { - /* transfer after each scan (and avoid 0) */ - devpriv->transCount = cmd->chanlist_len; - } else { /* make a multiple of scan length */ - devpriv->transCount = - (devpriv->transCount + - cmd->chanlist_len - 1) - / cmd->chanlist_len; - devpriv->transCount *= cmd->chanlist_len; - } - devpriv->flags |= SEND_EOS; - } - if (devpriv->transCount >= (devpriv->fifoLen / 2)) { - /* out of counter range, use 1/2 fifo instead */ - devpriv->transCount = 0; - devpriv->flags &= ~SEND_EOS; - } else { - /* interrupt for each transfer */ - RtdAboutCounter(dev, devpriv->transCount - 1); - } + DPRINTK("rtd520: port_0_direction=0x%x (1 means out)\n", s->io_bits); + /* TODO support digital match interrupts and strobes */ + RtdDioStatusWrite(dev, 0x01); /* make Dio0Ctrl point to direction */ + RtdDio0CtrlWrite(dev, s->io_bits); /* set direction 1 means Out */ + RtdDioStatusWrite(dev, 0); /* make Dio0Ctrl clear interrupts */ - DPRINTK - ("rtd520: scanLen=%d transferCount=%d fifoLen=%d\n scanTime(ns)=%d flags=0x%x\n", - cmd->chanlist_len, devpriv->transCount, devpriv->fifoLen, - cmd->scan_begin_arg, devpriv->flags); - } else { /* unknown timing, just use 1/2 FIFO */ - devpriv->transCount = 0; - devpriv->flags &= ~SEND_EOS; - } - RtdPacerClockSource(dev, 1); /* use INTERNAL 8Mhz clock source */ - RtdAboutStopEnable(dev, 1); /* just interrupt, dont stop */ + /* port1 can only be all input or all output */ - /* BUG??? these look like enumerated values, but they are bit fields */ + /* there are also 2 user input lines and 2 user output lines */ - /* First, setup when to stop */ - switch (cmd->stop_src) { - case TRIG_COUNT: /* stop after N scans */ - devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len; - if ((devpriv->transCount > 0) - && (devpriv->transCount > devpriv->aiCount)) { - devpriv->transCount = devpriv->aiCount; - } - break; + return 1; +} - case TRIG_NONE: /* stop when cancel is called */ - devpriv->aiCount = -1; /* read forever */ - break; +static int rtd_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ /* board name and options flags */ + struct comedi_subdevice *s; + struct pci_dev *pcidev; + int ret; + resource_size_t physLas0; /* configuration */ + resource_size_t physLas1; /* data area */ + resource_size_t physLcfg; /* PLX9080 */ +#ifdef USE_DMA + int index; +#endif - default: - DPRINTK("rtd520: Warning! ignoring stop_src mode %d\n", - cmd->stop_src); - } + printk(KERN_INFO "comedi%d: rtd520 attaching.\n", dev->minor); - /* Scan timing */ - switch (cmd->scan_begin_src) { - case TRIG_TIMER: /* periodic scanning */ - timer = rtd_ns_to_timer(&cmd->scan_begin_arg, - TRIG_ROUND_NEAREST); - /* set PACER clock */ - /*DPRINTK ("rtd520: loading %d into pacer\n", timer); */ - RtdPacerCounter(dev, timer); +#if defined(CONFIG_COMEDI_DEBUG) && defined(USE_DMA) + /* You can set this a load time: modprobe comedi comedi_debug=1 */ + if (0 == comedi_debug) /* force DMA debug printks */ + comedi_debug = 1; +#endif - break; + /* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct rtdPrivate)) < 0) + return -ENOMEM; - case TRIG_EXT: - RtdPacerStartSource(dev, 1); /* EXTERNALy trigger pacer */ - break; + /* + * Probe the device to determine what device in the series it is. + */ + for (pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, NULL); + pcidev != NULL; + pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, pcidev)) { + int i; - default: - DPRINTK("rtd520: Warning! ignoring scan_begin_src mode %d\n", - cmd->scan_begin_src); + if (it->options[0] || it->options[1]) { + if (pcidev->bus->number != it->options[0] + || PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + for (i = 0; i < ARRAY_SIZE(rtd520Boards); ++i) { + if (pcidev->device == rtd520Boards[i].device_id) { + dev->board_ptr = &rtd520Boards[i]; + break; + } + } + if (dev->board_ptr) + break; /* found one */ } - - /* Sample timing within a scan */ - switch (cmd->convert_src) { - case TRIG_TIMER: /* periodic */ - if (cmd->chanlist_len > 1) { /* only needed for multi-channel */ - timer = rtd_ns_to_timer(&cmd->convert_arg, - TRIG_ROUND_NEAREST); - /* setup BURST clock */ - /*DPRINTK ("rtd520: loading %d into burst\n", timer); */ - RtdBurstCounter(dev, timer); + if (!pcidev) { + if (it->options[0] && it->options[1]) { + printk(KERN_INFO "No RTD card at bus=%d slot=%d.\n", + it->options[0], it->options[1]); + } else { + printk(KERN_INFO "No RTD card found.\n"); } + return -EIO; + } + devpriv->pci_dev = pcidev; + dev->board_name = thisboard->name; - break; - - case TRIG_EXT: /* external */ - RtdBurstStartSource(dev, 2); /* EXTERNALy trigger burst */ - break; - - default: - DPRINTK("rtd520: Warning! ignoring convert_src mode %d\n", - cmd->convert_src); + ret = comedi_pci_enable(pcidev, DRV_NAME); + if (ret < 0) { + printk(KERN_INFO "Failed to enable PCI device and request regions.\n"); + return ret; } - /* end configuration */ + devpriv->got_regions = 1; - /* 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); + /* + * Initialize base addresses + */ + /* Get the physical address from PCI config */ + physLas0 = pci_resource_start(devpriv->pci_dev, LAS0_PCIINDEX); + physLas1 = pci_resource_start(devpriv->pci_dev, LAS1_PCIINDEX); + physLcfg = pci_resource_start(devpriv->pci_dev, LCFG_PCIINDEX); + /* Now have the kernel map this into memory */ + /* ASSUME page aligned */ + devpriv->las0 = ioremap_nocache(physLas0, LAS0_PCISIZE); + devpriv->las1 = ioremap_nocache(physLas1, LAS1_PCISIZE); + devpriv->lcfg = ioremap_nocache(physLcfg, LCFG_PCISIZE); - /* TODO: allow multiple interrupt sources */ - if (devpriv->transCount > 0) { /* transfer every N samples */ - RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); - DPRINTK("rtd520: Transferring every %d\n", devpriv->transCount); - } else { /* 1/2 FIFO transfers */ -#ifdef USE_DMA - devpriv->flags |= DMA0_ACTIVE; + if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg) + return -ENOMEM; - /* point to first transfer in ring */ - devpriv->dma0Offset = 0; - RtdDma0Mode(dev, DMA_MODE_BITS); - RtdDma0Next(dev, /* point to first block */ - devpriv->dma0Chain[DMA_CHAIN_COUNT - 1].next); - RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */ - 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_ABOUT_CNT); - DPRINTK("rtd520: Transferring every 1/2 FIFO\n"); -#endif /* USE_DMA */ - } + DPRINTK("%s: LAS0=%llx, LAS1=%llx, CFG=%llx.\n", dev->board_name, + (unsigned long long)physLas0, (unsigned long long)physLas1, + (unsigned long long)physLcfg); + { /* The RTD driver does this */ + unsigned char pci_latency; + u16 revision; + /*uint32_t epld_version; */ - /* 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; -} + pci_read_config_word(devpriv->pci_dev, PCI_REVISION_ID, + &revision); + DPRINTK("%s: PCI revision %d.\n", dev->board_name, revision); -/* - Stop a running data acquisition. -*/ -static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) -{ - u16 status; + pci_read_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 32) { + printk(KERN_INFO "%s: PCI latency changed from %d to %d\n", + dev->board_name, pci_latency, 32); + pci_write_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, 32); + } else { + DPRINTK("rtd520: PCI latency = %d\n", pci_latency); + } - RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ - RtdPacerStop(dev); /* Stop PACER */ - RtdAdcConversionSource(dev, 0); /* software trigger only */ - RtdInterruptMask(dev, 0); - devpriv->aiCount = 0; /* stop and don't transfer any more */ -#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; + /* + * Undocumented EPLD version (doesn't match RTD driver results) + */ + /*DPRINTK ("rtd520: Reading epld from %p\n", + devpriv->las0+0); + epld_version = readl (devpriv->las0+0); + if ((epld_version & 0xF0) >> 4 == 0x0F) { + DPRINTK("rtd520: pre-v8 EPLD. (%x)\n", epld_version); + } else { + DPRINTK("rtd520: EPLD version %x.\n", epld_version >> 4); + } */ } -#endif /* USE_DMA */ - status = RtdInterruptStatus(dev); - DPRINTK - ("rtd520: Acquisition canceled. %ld ints, intStat=%x, overStat=%x\n", - devpriv->intCount, status, - 0xffff & RtdInterruptOverrunStatus(dev)); - return 0; -} -/* - Given a desired period and the clock period (both in ns), - return the proper counter value (divider-1). - Sets the original period to be the true value. - Note: you have to check if the value is larger than the counter range! -*/ -static int rtd_ns_to_timer_base(unsigned int *nanosec, /* desired period (in ns) */ - int round_mode, int base) -{ /* clock period (in ns) */ - int divider; + /* Show board configuration */ + printk(KERN_INFO "%s:", dev->board_name); - switch (round_mode) { - case TRIG_ROUND_NEAREST: - default: - divider = (*nanosec + base / 2) / base; - break; - case TRIG_ROUND_DOWN: - divider = (*nanosec) / base; - break; - case TRIG_ROUND_UP: - divider = (*nanosec + base - 1) / base; - break; - } - if (divider < 2) - divider = 2; /* min is divide by 2 */ + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; - /* Note: we don't check for max, because different timers - have different ranges */ - *nanosec = base * divider; - return divider - 1; /* countdown is divisor+1 */ -} + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF | SDF_CMD_READ; + s->n_chan = thisboard->aiChans; + s->maxdata = (1 << thisboard->aiBits) - 1; + if (thisboard->aiMaxGain <= 32) + s->range_table = &rtd_ai_7520_range; + else + s->range_table = &rtd_ai_4520_range; -/* - Given a desired period (in ns), - return the proper counter value (divider-1) for the internal clock. - Sets the original period to be the true value. -*/ -static int rtd_ns_to_timer(unsigned int *ns, int round_mode) -{ - return rtd_ns_to_timer_base(ns, round_mode, RTD_CLOCK_BASE); -} + s->len_chanlist = RTD_MAX_CHANLIST; /* devpriv->fifoLen */ + s->insn_read = rtd_ai_rinsn; + s->do_cmd = rtd_ai_cmd; + s->do_cmdtest = rtd_ai_cmdtest; + s->cancel = rtd_ai_cancel; + /* s->poll = rtd_ai_poll; *//* not ready yet */ -/* - Output one (or more) analog values to a single port as fast as possible. -*/ -static int rtd_ao_winsn(struct comedi_device *dev, - struct comedi_subdevice *s, struct comedi_insn *insn, - unsigned int *data) -{ - int i; - int chan = CR_CHAN(insn->chanspec); - int range = CR_RANGE(insn->chanspec); + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->aiBits) - 1; + s->range_table = &rtd_ao_range; + s->insn_write = rtd_ao_winsn; + s->insn_read = rtd_ao_rinsn; - /* Configure the output range (table index matches the range values) */ - RtdDacRange(dev, chan, range); + s = dev->subdevices + 2; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + /* we only support port 0 right now. Ignoring port 1 and user IO */ + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = rtd_dio_insn_bits; + s->insn_config = rtd_dio_insn_config; - /* Writing a list of values to an AO channel is probably not - * very useful, but that's how the interface is defined. */ - for (i = 0; i < insn->n; ++i) { - int val = data[i] << 3; - int stat = 0; /* initialize to avoid bogus warning */ - int ii; + /* timer/counter subdevices (not currently supported) */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 0xffff; - /* VERIFY: comedi range and offset conversions */ + /* initialize board, per RTD spec */ + /* also, initialize shadow registers */ + RtdResetBoard(dev); + udelay(100); /* needed? */ + RtdPlxInterruptWrite(dev, 0); + RtdInterruptMask(dev, 0); /* and sets shadow */ + RtdInterruptClearMask(dev, ~0); /* and sets shadow */ + RtdInterruptClear(dev); /* clears bits set by mask */ + RtdInterruptOverrunClear(dev); + RtdClearCGT(dev); + RtdAdcClearFifo(dev); + RtdDacClearFifo(dev, 0); + RtdDacClearFifo(dev, 1); + /* clear digital IO fifo */ + RtdDioStatusWrite(dev, 0); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 0, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 1, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 2, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 3, 0); /* safe state, set shadow */ + /* TODO: set user out source ??? */ - if ((range > 1) /* bipolar */ - && (data[i] < 2048)) { - /* offset and sign extend */ - val = (((int)data[i]) - 2048) << 3; - } else { /* unipolor */ - val = data[i] << 3; - } + /* check if our interrupt is available and get it */ + ret = request_irq(devpriv->pci_dev->irq, rtd_interrupt, + IRQF_SHARED, DRV_NAME, dev); - DPRINTK - ("comedi: rtd520 DAC chan=%d range=%d writing %d as 0x%x\n", - chan, range, data[i], val); + if (ret < 0) { + printk("Could not get interrupt! (%u)\n", + devpriv->pci_dev->irq); + return ret; + } + dev->irq = devpriv->pci_dev->irq; + printk(KERN_INFO "( irq=%u )", dev->irq); - /* a typical programming sequence */ - RtdDacFifoPut(dev, chan, val); /* put the value in */ - RtdDacUpdate(dev, chan); /* trigger the conversion */ + ret = rtd520_probe_fifo_depth(dev); + if (ret < 0) + return ret; - devpriv->aoValue[chan] = data[i]; /* save for read back */ + devpriv->fifoLen = ret; + printk("( fifoLen=%d )", devpriv->fifoLen); + +#ifdef USE_DMA + if (dev->irq > 0) { + printk("( DMA buff=%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 (ii = 0; ii < RTD_DAC_TIMEOUT; ++ii) { - stat = RtdFifoStatus(dev); - /* 1 -> not empty */ - if (stat & ((0 == chan) ? FS_DAC1_NOT_EMPTY : - FS_DAC2_NOT_EMPTY)) - break; - WAIT_QUIETLY; + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + devpriv->dma0Buff[index] = + pci_alloc_consistent(devpriv->pci_dev, + sizeof(u16) * + devpriv->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]); */ } - if (ii >= RTD_DAC_TIMEOUT) { - DPRINTK - ("rtd520: Error: DAC never finished! FifoStatus=0x%x\n", - stat ^ 0x6666); - return -ETIMEDOUT; + + /* + * 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) * devpriv->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); */ } - } - /* return the number of samples read/written */ - return i; -} + if (devpriv->dma0Chain == NULL) { + ret = -ENOMEM; + goto rtd_attach_die_error; + } -/* AO subdevices should have a read insn as well as a write insn. - * Usually this means copying a value stored in devpriv. */ -static int rtd_ao_rinsn(struct comedi_device *dev, - struct comedi_subdevice *s, struct comedi_insn *insn, - unsigned int *data) -{ - int i; - int chan = CR_CHAN(insn->chanspec); + RtdDma0Mode(dev, DMA_MODE_BITS); + /* set DMA trigger source */ + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); + } else { + printk(KERN_INFO "( no IRQ->no DMA )"); + } +#endif /* USE_DMA */ - for (i = 0; i < insn->n; i++) - data[i] = devpriv->aoValue[chan]; + if (dev->irq) { /* enable plx9080 interrupts */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } + printk("\ncomedi%d: rtd520 driver attached.\n", dev->minor); - return i; -} + return 1; -/* - Write a masked set of bits and the read back the port. - We track what the bits should be (i.e. we don't read the port first). +#if 0 + /* 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) * devpriv->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)); + free_irq(dev->irq, dev); + } - DIO devices are slightly special. Although it is possible to - * implement the insn_read/insn_write interface, it is much more - * useful to applications if you implement the insn_bits interface. - * This allows packed reading/writing of the DIO channels. The - * comedi core can convert between insn_bits and insn_read/write - */ -static int rtd_dio_insn_bits(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data) -{ - if (insn->n != 2) - return -EINVAL; + /* release all regions that were allocated */ + if (devpriv->las0) + iounmap(devpriv->las0); - /* The insn data is a mask in data[0] and the new data - * in data[1], each channel cooresponding to a bit. */ - if (data[0]) { - s->state &= ~data[0]; - s->state |= data[0] & data[1]; + if (devpriv->las1) + iounmap(devpriv->las1); - /* Write out the new digital output lines */ - RtdDio0Write(dev, s->state); - } - /* on return, data[1] contains the value of the digital - * input lines. */ - data[1] = RtdDio0Read(dev); + if (devpriv->lcfg) + iounmap(devpriv->lcfg); - /*DPRINTK("rtd520:port_0 wrote: 0x%x read: 0x%x\n", s->state, data[1]); */ + if (devpriv->pci_dev) + pci_dev_put(devpriv->pci_dev); - return 2; + return ret; +#endif } -/* - Configure one bit on a IO port as Input or Output (hence the name :-). -*/ -static int rtd_dio_insn_config(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data) +static void rtd_detach(struct comedi_device *dev) { - int chan = CR_CHAN(insn->chanspec); +#ifdef USE_DMA + int index; +#endif - /* The input or output configuration of each digital line is - * configured by a special insn_config instruction. chanspec - * contains the channel to be changed, and data[0] contains the - * value COMEDI_INPUT or COMEDI_OUTPUT. */ - switch (data[0]) { - case INSN_CONFIG_DIO_OUTPUT: - s->io_bits |= 1 << chan; /* 1 means Out */ - break; - case INSN_CONFIG_DIO_INPUT: - s->io_bits &= ~(1 << chan); - break; - case INSN_CONFIG_DIO_QUERY: - data[1] = - (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; - return insn->n; - break; - default: - return -EINVAL; + if (devpriv) { + /* Shut down any board ops by resetting it */ +#ifdef USE_DMA + if (devpriv->lcfg) { + RtdDma0Control(dev, 0); /* disable DMA */ + RtdDma1Control(dev, 0); /* disable DMA */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } +#endif /* USE_DMA */ + if (devpriv->las0) { + 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) * + devpriv->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 */ + if (dev->irq) { + RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) + & ~(ICS_PLIE | ICS_DMA0_E | + ICS_DMA1_E)); + free_irq(dev->irq, dev); + } + if (devpriv->las0) + iounmap(devpriv->las0); + if (devpriv->las1) + iounmap(devpriv->las1); + if (devpriv->lcfg) + iounmap(devpriv->lcfg); + if (devpriv->pci_dev) { + if (devpriv->got_regions) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } } - - DPRINTK("rtd520: port_0_direction=0x%x (1 means out)\n", s->io_bits); - /* TODO support digital match interrupts and strobes */ - RtdDioStatusWrite(dev, 0x01); /* make Dio0Ctrl point to direction */ - RtdDio0CtrlWrite(dev, s->io_bits); /* set direction 1 means Out */ - RtdDioStatusWrite(dev, 0); /* make Dio0Ctrl clear interrupts */ - - /* port1 can only be all input or all output */ - - /* there are also 2 user input lines and 2 user output lines */ - - return 1; } static struct comedi_driver rtd520_driver = {