/*
Driver: das16.o
Description: DAS16 compatible boards
-Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess
+Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze
Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g),
DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202),
DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601),
CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16),
CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12),
CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330)
-Status: works in das16 mode, das-1600 enhanced mode features untested.
-Updated: 2001-8-27
+Status: works
+Updated: 2002-04-17
A rewrite of the das16 and das1600 drivers.
Options:
- [0] - base io address
- [1] - irq (optional)
- [2] - dma (optional)
- [3] - master clock speed in MHz (optional, 1 or 10, ignored if
- board can probe clock, defaults to 1)
- [4] - analog input range lowest voltage in microvolts (optional,
- only useful if your board does not have software
+ [0] - base io address
+ [1] - irq (optional)
+ [2] - dma (optional)
+ [3] - master clock speed in MHz (optional, 1 or 10, ignored if
+ board can probe clock, defaults to 1)
+ [4] - analog input range lowest voltage in microvolts (optional,
+ only useful if your board does not have software
programmable gain)
- [5] - analog input range highest voltage in microvolts (optional,
- only useful if board does not have software programmable
+ [5] - analog input range highest voltage in microvolts (optional,
+ only useful if board does not have software programmable
gain)
- [6] - analog output range lowest voltage in microvolts (optional)
- [7] - analog output range highest voltage in microvolts (optional)
+ [6] - analog output range lowest voltage in microvolts (optional)
+ [7] - analog output range highest voltage in microvolts (optional)
+ [8] - use timer mode for DMA, needed e.g. for buggy DMA controller
+ in NS CS5530A (Geode Companion). If set, also allows
+ comedi_command() to be run without an irq.
Passing a zero for an option is the same as leaving it unspecified.
-Both an irq line and dma channel are required for timed or externally
-triggered conversions.
+Both a dma channel and an irq (or use of 'timer mode', option 8) are required
+for timed or externally triggered conversions.
*/
/*
Keithley Manuals:
2309.PDF (das16)
+ 4919.PDF (das1400, 1600)
+ 4922.PDF (das-1400)
4923.PDF (das1200, 1400, 1600)
Computer boards manuals also available from their website www.measurementcomputing.com
#include "8255.h"
#undef DEBUG
+//#define DEBUG
+
+#ifdef DEBUG
+#define DEBUG_PRINT(format, args...) rt_printk("das16: " format, ## args)
+#else
+#define DEBUG_PRINT(format, args...)
+#endif
#define DAS16_SIZE 20 // number of ioports
#define DAS16_DMA_SIZE 0xff00 // size in bytes of allocated dma buffer
static int das16_cancel(comedi_device *dev, comedi_subdevice *s);
static void das16_reset(comedi_device *dev);
-static void das16_interrupt(int irq, void *d, struct pt_regs *regs);
+static void das16_dma_interrupt(int irq, void *d, struct pt_regs *regs);
+static void das16_timer_interrupt(unsigned long arg);
+static void das16_interrupt(comedi_device *dev);
static unsigned int das16_set_pacer(comedi_device *dev, unsigned int ns, int flags);
static int das1600_mode_detect(comedi_device *dev);
-static unsigned int das16_suggest_transfer_size(comedi_cmd cmd);
+static unsigned int das16_suggest_transfer_size(comedi_device *dev, comedi_cmd cmd);
-#ifdef DEBUG
static void reg_dump(comedi_device *dev);
-#endif
typedef struct das16_board_struct{
char *name;
offset: sizeof(das16_boards[0]),
};
-
#define DAS16_TIMEOUT 1000
+static const int timer_period = HZ / 10 + 1; // period for timer interrupt in jiffies (about 1/10 of a second)
struct das16_private_struct {
unsigned int ai_unipolar; // unipolar flag
unsigned int clockbase; // master clock speed in ns
volatile unsigned int control_state; // dma, interrupt and trigger control bits
volatile unsigned int adc_count; // number of samples remaining
+ volatile unsigned int remains; // remaining bytes after last irq
unsigned int divisor1; // divisor dividing master clock to get conversion frequency
unsigned int divisor2; // divisor dividing master clock to get conversion frequency
unsigned int dma_chan; // dma channel
// user-defined analog input and output ranges defined from config options
comedi_lrange *user_ai_range_table;
comedi_lrange *user_ao_range_table;
+
+ struct timer_list timer; // for timed interrupt
+ volatile unsigned int timer_running : 1;
+ volatile unsigned int timer_mode : 1; // true if using timer mode
};
#define devpriv ((struct das16_private_struct *)(dev->private))
#define thisboard ((struct das16_board_struct *)(dev->board_ptr))
unsigned long flags;
int range;
- if(dev->irq == 0 || devpriv->dma_chan == 0)
+ if(devpriv->dma_chan == 0 || (dev->irq == 0 && devpriv->timer_mode == 0))
{
- comedi_error(dev, "irq and dma required to execute comedi_cmd");
+ comedi_error(dev, "irq (or use of 'timer mode') dma required to execute comedi_cmd");
return -1;
}
if(cmd->flags & TRIG_RT)
}
devpriv->adc_count = cmd->stop_arg * cmd->chanlist_len;
+ devpriv->remains = 0;
// disable conversions for das1600 mode
if(thisboard->size > 0x400)
clear_dma_ff(devpriv->dma_chan);
set_dma_addr(devpriv->dma_chan, virt_to_bus(devpriv->dma_buffer));
// set appropriate size of transfer
- devpriv->dma_transfer_size = das16_suggest_transfer_size(*cmd);
- if(cmd->stop_src == TRIG_COUNT &&
- devpriv->adc_count * sample_size < devpriv->dma_transfer_size)
- {
- devpriv->dma_transfer_size = devpriv->adc_count * sample_size;
- }
+ devpriv->dma_transfer_size = das16_suggest_transfer_size(dev, *cmd);
set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size);
enable_dma(devpriv->dma_chan);
release_dma_lock(flags);
- /* clear interrupt bit */
- outb(0x00, dev->iobase + DAS16_STATUS);
- /* enable interrupts, dma and pacer clocked conversions */
- devpriv->control_state |= DAS16_INTE | DMA_ENABLE;
+ // set up interrupt
+ if (devpriv->timer_mode)
+ {
+ devpriv->timer_running = 1;
+ devpriv->timer.expires = jiffies + timer_period;
+ add_timer(&devpriv->timer);
+ devpriv->control_state &= ~DAS16_INTE;
+ }else
+ {
+ /* clear interrupt bit */
+ outb(0x00, dev->iobase + DAS16_STATUS);
+ /* enable interrupts, dma and pacer clocked conversions */
+ devpriv->control_state |= DAS16_INTE;
+ }
+ devpriv->control_state |= DMA_ENABLE;
if(cmd->convert_src == TRIG_EXT)
devpriv->control_state |= EXT_PACER;
else
if(devpriv->dma_chan)
disable_dma(devpriv->dma_chan);
+ // disable SW timer
+ if( devpriv->timer_mode && devpriv->timer_running )
+ {
+ devpriv->timer_running = 0;
+ del_timer(&devpriv->timer);
+ }
+
/* disable burst mode */
if(thisboard->size > 0x400)
{
static void das16_reset(comedi_device *dev)
{
- outb(0,dev->iobase+DAS16_STATUS);
- outb(0,dev->iobase+DAS16_CONTROL);
- outb(0,dev->iobase+DAS16_PACER);
- outb(0,dev->iobase+DAS16_CNTR_CONTROL);
+ outb(0, dev->iobase + DAS16_STATUS);
+ outb(0, dev->iobase + DAS16_CONTROL);
+ outb(0, dev->iobase + DAS16_PACER);
+ outb(0, dev->iobase + DAS16_CNTR_CONTROL);
}
static int das16_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
}
-static void das16_interrupt(int irq, void *d, struct pt_regs *regs)
+static void das16_dma_interrupt(int irq, void *d, struct pt_regs *regs)
{
- int i;
int status;
- unsigned long flags;
comedi_device *dev = d;
+
+ status = inb(dev->iobase + DAS16_STATUS);
+
+ if((status & DAS16_INT ) == 0)
+ {
+ comedi_error(dev, "spurious interrupt");
+ return;
+ }
+
+ /* clear interrupt */
+ outb(0x00, dev->iobase + DAS16_STATUS);
+
+ das16_interrupt(dev);
+}
+
+static void das16_timer_interrupt(unsigned long arg)
+{
+ comedi_device *dev = (comedi_device*) arg;
+
+ das16_interrupt(dev);
+
+ if(devpriv->timer_running)
+ mod_timer(&devpriv->timer, jiffies + timer_period);
+}
+
+static void das16_interrupt( comedi_device *dev )
+{
+ int i;
+ unsigned long flags;
comedi_subdevice *s = dev->read_subdev;
comedi_async *async;
- unsigned int max_points, num_points, residue, leftover;
+ comedi_cmd *cmd;
+ int num_points, num_bytes, residue, next_num_bytes;
sampl_t dpnt;
if(dev->attached == 0)
}
// initialize async here to make sure it is not NULL
async = s->async;
+ cmd = &async->cmd;
async->events = 0;
- status = inb(dev->iobase + DAS16_STATUS);
-
- if((status & DAS16_INT ) == 0)
- {
- comedi_error(dev, "spurious interrupt");
- return;
- }
-
- /* clear interrupt */
- outb(0x00, dev->iobase + DAS16_STATUS);
-
flags = claim_dma_lock();
disable_dma(devpriv->dma_chan);
/* clear flip-flop to make sure 2-byte registers for
clear_dma_ff(devpriv->dma_chan);
// figure out how many points to read
- max_points = devpriv->dma_transfer_size / sample_size;
+
/* residue is the number of points left to be done on the dma
* transfer. It should always be zero at this point unless
* the stop_src is set to external triggering.
*/
- residue = get_dma_residue(devpriv->dma_chan) / sample_size;
- if(residue > max_points)
+ residue = get_dma_residue(devpriv->dma_chan);
+ if(residue > devpriv->dma_transfer_size)
{
- comedi_error(dev, "residue > max_points!\n");
- async->events |= COMEDI_CB_ERROR;
- residue = max_points;
- }
- num_points = max_points - residue;
- if(devpriv->adc_count < num_points &&
- async->cmd.stop_src == TRIG_COUNT)
- num_points = devpriv->adc_count;
+ comedi_error(dev, "residue > transfer size!\n");
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ num_bytes = 0;
+ }else
+ num_bytes = devpriv->dma_transfer_size + devpriv->remains - residue;
+ num_points = num_bytes / sample_size;
- // figure out how many points will be stored next time
- leftover = 0;
- if(async->cmd.stop_src == TRIG_NONE)
- {
- leftover = devpriv->dma_transfer_size / sample_size;
- }else if(devpriv->adc_count > num_points)
+ if(cmd->stop_src == TRIG_COUNT &&
+ num_points > devpriv->adc_count)
{
- leftover = devpriv->adc_count - num_points;
- if(leftover > max_points)
- leftover = max_points;
+ num_points = devpriv->adc_count;
+ async->events |= COMEDI_CB_EOA;
}
for(i = 0; i < num_points; i++)
if(thisboard->ai_nbits == 12)
dpnt = (dpnt >> 4) & 0xfff;
comedi_buf_put(async, dpnt);
- if(devpriv->adc_count > 0) devpriv->adc_count--;
+ devpriv->adc_count--;
}
+ // copy possible leftover byte to beginning of buffer to be read next time
+ devpriv->dma_buffer[0] = devpriv->dma_buffer[i];
+
+ devpriv->remains = num_bytes % sample_size;
+
+ // figure out how many bytes for next transfer
+ next_num_bytes = devpriv->dma_transfer_size - devpriv->remains;
+ if(cmd->stop_src == TRIG_COUNT &&
+ next_num_bytes + devpriv->remains > devpriv->adc_count * sample_size)
+ next_num_bytes = devpriv->adc_count * sample_size - devpriv->remains;
+
// re-enable dma
- if(leftover)
+ if(( async->events & COMEDI_CB_EOA ) == 0)
{
- set_dma_addr(devpriv->dma_chan, virt_to_bus(devpriv->dma_buffer));
- set_dma_count(devpriv->dma_chan, leftover * sample_size);
+ set_dma_addr(devpriv->dma_chan, virt_to_bus(devpriv->dma_buffer) + devpriv->remains);
+ set_dma_count(devpriv->dma_chan, next_num_bytes);
enable_dma(devpriv->dma_chan);
}
release_dma_lock(flags);
async->events |= COMEDI_CB_BLOCK;
- if(async->cmd.stop_src == TRIG_COUNT &&
- devpriv->adc_count == 0)
- { /* end of acquisition */
+ if(async->events & COMEDI_CB_EOA)
das16_cancel(dev, s);
- async->events |= COMEDI_CB_EOA;
- }
comedi_event(dev, s, async->events);
}
return ns;
}
-#ifdef DEBUG
static void reg_dump(comedi_device *dev)
{
- rt_printk("********DAS1600 REGISTER DUMP********\n");
- rt_printk("DAS16_MUX: %x\n", inb(dev->iobase+DAS16_MUX) );
- rt_printk("DAS16_DIO: %x\n", inb(dev->iobase+DAS16_DIO) );
- rt_printk("DAS16_STATUS: %x\n", inb(dev->iobase+DAS16_STATUS) );
- rt_printk("DAS16_CONTROL: %x\n", inb(dev->iobase+DAS16_CONTROL) );
- rt_printk("DAS16_PACER: %x\n", inb(dev->iobase+DAS16_PACER) );
- rt_printk("DAS16_GAIN: %x\n", inb(dev->iobase+DAS16_GAIN) );
- rt_printk("DAS16_CNTR_CONTROL: %x\n", inb(dev->iobase+DAS16_CNTR_CONTROL) );
- rt_printk("DAS1600_CONV: %x\n", inb(dev->iobase+DAS1600_CONV) );
- rt_printk("DAS1600_BURST: %x\n", inb(dev->iobase+DAS1600_BURST) );
- rt_printk("DAS1600_ENABLE: %x\n", inb(dev->iobase+DAS1600_ENABLE) );
- rt_printk("DAS1600_STATUS_B: %x\n", inb(dev->iobase+DAS1600_STATUS_B) );
+ DEBUG_PRINT("********DAS1600 REGISTER DUMP********\n");
+ DEBUG_PRINT("DAS16_MUX: %x\n", inb(dev->iobase+DAS16_MUX) );
+ DEBUG_PRINT("DAS16_DIO: %x\n", inb(dev->iobase+DAS16_DIO) );
+ DEBUG_PRINT("DAS16_STATUS: %x\n", inb(dev->iobase+DAS16_STATUS) );
+ DEBUG_PRINT("DAS16_CONTROL: %x\n", inb(dev->iobase+DAS16_CONTROL) );
+ DEBUG_PRINT("DAS16_PACER: %x\n", inb(dev->iobase+DAS16_PACER) );
+ DEBUG_PRINT("DAS16_GAIN: %x\n", inb(dev->iobase+DAS16_GAIN) );
+ DEBUG_PRINT("DAS16_CNTR_CONTROL: %x\n", inb(dev->iobase+DAS16_CNTR_CONTROL) );
+ DEBUG_PRINT("DAS1600_CONV: %x\n", inb(dev->iobase+DAS1600_CONV) );
+ DEBUG_PRINT("DAS1600_BURST: %x\n", inb(dev->iobase+DAS1600_BURST) );
+ DEBUG_PRINT("DAS1600_ENABLE: %x\n", inb(dev->iobase+DAS1600_ENABLE) );
+ DEBUG_PRINT("DAS1600_STATUS_B: %x\n", inb(dev->iobase+DAS1600_STATUS_B) );
}
-#endif
static int das16_probe(comedi_device *dev, comedi_devconfig *it)
{
printk(" 1MHz pacer clock\n");
}
+ reg_dump(dev);
+
return 0;
}
int ret, irq;
int iobase;
int dma_chan;
+ int timer_mode;
unsigned long flags;
comedi_krange *user_ai_range, *user_ao_range;
irq = it->options[1];
if(irq > 1 && irq < 8)
{
- if((ret=comedi_request_irq(irq, das16_interrupt, 0, "das16",dev)) < 0)
+ if((ret=comedi_request_irq(irq, das16_dma_interrupt, 0, "das16",dev)) < 0)
return ret;
dev->irq = irq;
printk(" ( irq = %d )",irq);
user_ao_range->flags = UNIT_volt;
}
+ timer_mode = it->options[8];
+ if(timer_mode)
+ {
+ init_timer(&(devpriv->timer));
+ devpriv->timer.function = das16_timer_interrupt;
+ devpriv->timer.data = (unsigned long) dev;
+ devpriv->timer_mode = timer_mode ? 1 : 0;
+ }
+
dev->n_subdevices = 5;
if((ret=alloc_subdevices(dev))<0)
return ret;
COMEDI_INITCLEANUP(driver_das16);
// utility function that suggests a dma transfer size in bytes
-static unsigned int das16_suggest_transfer_size(comedi_cmd cmd)
+static unsigned int das16_suggest_transfer_size(comedi_device *dev, comedi_cmd cmd)
{
unsigned int size;
unsigned int freq;
+ /* if we are using timer interrupt, we don't care how long it
+ * will take to complete transfer since it will be interrupted
+ * by timer interrupt */
+ if(devpriv->timer_mode) return DAS16_DMA_SIZE;
+
+ /* otherwise, we are relying on dma terminal count interrupt,
+ * so pick a reasonable size */
if(cmd.convert_src == TRIG_TIMER)
freq = 1000000000 / cmd.convert_arg;
else if(cmd.scan_begin_src == TRIG_TIMER)
else if(size < sample_size)
size = sample_size;
+ if( cmd.stop_src == TRIG_COUNT && size > devpriv->adc_count * sample_size )
+ size = devpriv->adc_count * sample_size;
+
return size;
}