blob: 8cea9dca3d7e64fcf098dee5277e41be9e7b9bf8 [file] [log] [blame]
/*
comedi/drivers/dt282x.c
Hardware driver for Data Translation DT2821 series
COMEDI - Linux Control and Measurement Device Interface
Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
Driver: dt282x
Description: Data Translation DT2821 series (including DT-EZ)
Author: ds
Devices: [Data Translation] DT2821 (dt2821),
DT2821-F-16SE (dt2821-f), DT2821-F-8DI (dt2821-f),
DT2821-G-16SE (dt2821-f), DT2821-G-8DI (dt2821-g),
DT2823 (dt2823),
DT2824-PGH (dt2824-pgh), DT2824-PGL (dt2824-pgl), DT2825 (dt2825),
DT2827 (dt2827), DT2828 (dt2828), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez),
DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl)
Status: complete
Updated: Wed, 22 Aug 2001 17:11:34 -0700
Configuration options:
[0] - I/O port base address
[1] - IRQ
[2] - DMA 1
[3] - DMA 2
[4] - AI jumpered for 0=single ended, 1=differential
[5] - AI jumpered for 0=straight binary, 1=2's complement
[6] - AO 0 jumpered for 0=straight binary, 1=2's complement
[7] - AO 1 jumpered for 0=straight binary, 1=2's complement
[8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]
[9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]
[10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]
Notes:
- AO commands might be broken.
- If you try to run a command on both the AI and AO subdevices
simultaneously, bad things will happen. The driver needs to
be fixed to check for this situation and return an error.
*/
#include "../comedidev.h"
#include <linux/gfp.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <asm/dma.h>
#include "comedi_fc.h"
#define DEBUG
#define DT2821_TIMEOUT 100 /* 500 us */
#define DT2821_SIZE 0x10
/*
* Registers in the DT282x
*/
#define DT2821_ADCSR 0x00 /* A/D Control/Status */
#define DT2821_CHANCSR 0x02 /* Channel Control/Status */
#define DT2821_ADDAT 0x04 /* A/D data */
#define DT2821_DACSR 0x06 /* D/A Control/Status */
#define DT2821_DADAT 0x08 /* D/A data */
#define DT2821_DIODAT 0x0a /* digital data */
#define DT2821_SUPCSR 0x0c /* Supervisor Control/Status */
#define DT2821_TMRCTR 0x0e /* Timer/Counter */
/*
* At power up, some registers are in a well-known state. The
* masks and values are as follows:
*/
#define DT2821_ADCSR_MASK 0xfff0
#define DT2821_ADCSR_VAL 0x7c00
#define DT2821_CHANCSR_MASK 0xf0f0
#define DT2821_CHANCSR_VAL 0x70f0
#define DT2821_DACSR_MASK 0x7c93
#define DT2821_DACSR_VAL 0x7c90
#define DT2821_SUPCSR_MASK 0xf8ff
#define DT2821_SUPCSR_VAL 0x0000
#define DT2821_TMRCTR_MASK 0xff00
#define DT2821_TMRCTR_VAL 0xf000
/*
* Bit fields of each register
*/
/* ADCSR */
#define DT2821_ADERR 0x8000 /* (R) 1 for A/D error */
#define DT2821_ADCLK 0x0200 /* (R/W) A/D clock enable */
/* 0x7c00 read as 1's */
#define DT2821_MUXBUSY 0x0100 /* (R) multiplexer busy */
#define DT2821_ADDONE 0x0080 /* (R) A/D done */
#define DT2821_IADDONE 0x0040 /* (R/W) interrupt on A/D done */
/* 0x0030 gain select */
/* 0x000f channel select */
/* CHANCSR */
#define DT2821_LLE 0x8000 /* (R/W) Load List Enable */
/* 0x7000 read as 1's */
/* 0x0f00 (R) present address */
/* 0x00f0 read as 1's */
/* 0x000f (R) number of entries - 1 */
/* DACSR */
#define DT2821_DAERR 0x8000 /* (R) D/A error */
#define DT2821_YSEL 0x0200 /* (R/W) DAC 1 select */
#define DT2821_SSEL 0x0100 /* (R/W) single channel select */
#define DT2821_DACRDY 0x0080 /* (R) DAC ready */
#define DT2821_IDARDY 0x0040 /* (R/W) interrupt on DAC ready */
#define DT2821_DACLK 0x0020 /* (R/W) D/A clock enable */
#define DT2821_HBOE 0x0002 /* (R/W) DIO high byte output enable */
#define DT2821_LBOE 0x0001 /* (R/W) DIO low byte output enable */
/* SUPCSR */
#define DT2821_DMAD 0x8000 /* (R) DMA done */
#define DT2821_ERRINTEN 0x4000 /* (R/W) interrupt on error */
#define DT2821_CLRDMADNE 0x2000 /* (W) clear DMA done */
#define DT2821_DDMA 0x1000 /* (R/W) dual DMA */
#define DT2821_DS1 0x0800 /* (R/W) DMA select 1 */
#define DT2821_DS0 0x0400 /* (R/W) DMA select 0 */
#define DT2821_BUFFB 0x0200 /* (R/W) buffer B selected */
#define DT2821_SCDN 0x0100 /* (R) scan done */
#define DT2821_DACON 0x0080 /* (W) DAC single conversion */
#define DT2821_ADCINIT 0x0040 /* (W) A/D initialize */
#define DT2821_DACINIT 0x0020 /* (W) D/A initialize */
#define DT2821_PRLD 0x0010 /* (W) preload multiplexer */
#define DT2821_STRIG 0x0008 /* (W) software trigger */
#define DT2821_XTRIG 0x0004 /* (R/W) external trigger enable */
#define DT2821_XCLK 0x0002 /* (R/W) external clock enable */
#define DT2821_BDINIT 0x0001 /* (W) initialize board */
static const struct comedi_lrange range_dt282x_ai_lo_bipolar = {
4, {
RANGE(-10, 10),
RANGE(-5, 5),
RANGE(-2.5, 2.5),
RANGE(-1.25, 1.25)
}
};
static const struct comedi_lrange range_dt282x_ai_lo_unipolar = {
4, {
RANGE(0, 10),
RANGE(0, 5),
RANGE(0, 2.5),
RANGE(0, 1.25)
}
};
static const struct comedi_lrange range_dt282x_ai_5_bipolar = {
4, {
RANGE(-5, 5),
RANGE(-2.5, 2.5),
RANGE(-1.25, 1.25),
RANGE(-0.625, 0.625)
}
};
static const struct comedi_lrange range_dt282x_ai_5_unipolar = {
4, {
RANGE(0, 5),
RANGE(0, 2.5),
RANGE(0, 1.25),
RANGE(0, 0.625),
}
};
static const struct comedi_lrange range_dt282x_ai_hi_bipolar = {
4, {
RANGE(-10, 10),
RANGE(-1, 1),
RANGE(-0.1, 0.1),
RANGE(-0.02, 0.02)
}
};
static const struct comedi_lrange range_dt282x_ai_hi_unipolar = {
4, {
RANGE(0, 10),
RANGE(0, 1),
RANGE(0, 0.1),
RANGE(0, 0.02)
}
};
struct dt282x_board {
const char *name;
int adbits;
int adchan_se;
int adchan_di;
int ai_speed;
int ispgl;
int dachan;
int dabits;
};
static const struct dt282x_board boardtypes[] = {
{.name = "dt2821",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 20000,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2821-f",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 6500,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2821-g",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 4000,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2823",
.adbits = 16,
.adchan_se = 0,
.adchan_di = 4,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 2,
.dabits = 16,
},
{.name = "dt2824-pgh",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 20000,
.ispgl = 0,
.dachan = 0,
.dabits = 0,
},
{.name = "dt2824-pgl",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 20000,
.ispgl = 1,
.dachan = 0,
.dabits = 0,
},
{.name = "dt2825",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 20000,
.ispgl = 1,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2827",
.adbits = 16,
.adchan_se = 0,
.adchan_di = 4,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2828",
.adbits = 12,
.adchan_se = 4,
.adchan_di = 0,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt2829",
.adbits = 16,
.adchan_se = 8,
.adchan_di = 0,
.ai_speed = 33250,
.ispgl = 0,
.dachan = 2,
.dabits = 16,
},
{.name = "dt21-ez",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 2,
.dabits = 12,
},
{.name = "dt23-ez",
.adbits = 16,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 0,
.dabits = 0,
},
{.name = "dt24-ez",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 10000,
.ispgl = 0,
.dachan = 0,
.dabits = 0,
},
{.name = "dt24-ez-pgl",
.adbits = 12,
.adchan_se = 16,
.adchan_di = 8,
.ai_speed = 10000,
.ispgl = 1,
.dachan = 0,
.dabits = 0,
},
};
#define n_boardtypes (sizeof(boardtypes)/sizeof(struct dt282x_board))
#define this_board ((const struct dt282x_board *)dev->board_ptr)
struct dt282x_private {
int ad_2scomp; /* we have 2's comp jumper set */
int da0_2scomp; /* same, for DAC0 */
int da1_2scomp; /* same, for DAC1 */
const struct comedi_lrange *darangelist[2];
short ao[2];
volatile int dacsr; /* software copies of registers */
volatile int adcsr;
volatile int supcsr;
volatile int ntrig;
volatile int nread;
struct {
int chan;
short *buf; /* DMA buffer */
volatile int size; /* size of current transfer */
} dma[2];
int dma_maxsize; /* max size of DMA transfer (in bytes) */
int usedma; /* driver uses DMA */
volatile int current_dma_index;
int dma_dir;
};
#define devpriv ((struct dt282x_private *)dev->private)
#define boardtype (*(const struct dt282x_board *)dev->board_ptr)
/*
* Some useless abstractions
*/
#define chan_to_DAC(a) ((a)&1)
#define update_dacsr(a) outw(devpriv->dacsr|(a), dev->iobase+DT2821_DACSR)
#define update_adcsr(a) outw(devpriv->adcsr|(a), dev->iobase+DT2821_ADCSR)
#define mux_busy() (inw(dev->iobase+DT2821_ADCSR)&DT2821_MUXBUSY)
#define ad_done() (inw(dev->iobase+DT2821_ADCSR)&DT2821_ADDONE)
#define update_supcsr(a) outw(devpriv->supcsr|(a), dev->iobase+DT2821_SUPCSR)
/*
* danger! macro abuse... a is the expression to wait on, and b is
* the statement(s) to execute if it doesn't happen.
*/
#define wait_for(a, b) \
do { \
int _i; \
for (_i = 0; _i < DT2821_TIMEOUT; _i++) { \
if (a) { \
_i = 0; \
break; \
} \
udelay(5); \
} \
if (_i) \
b \
} while (0)
static int dt282x_attach(struct comedi_device *dev,
struct comedi_devconfig *it);
static int dt282x_detach(struct comedi_device *dev);
static struct comedi_driver driver_dt282x = {
.driver_name = "dt282x",
.module = THIS_MODULE,
.attach = dt282x_attach,
.detach = dt282x_detach,
.board_name = &boardtypes[0].name,
.num_names = n_boardtypes,
.offset = sizeof(struct dt282x_board),
};
static int __init driver_dt282x_init_module(void)
{
return comedi_driver_register(&driver_dt282x);
}
static void __exit driver_dt282x_cleanup_module(void)
{
comedi_driver_unregister(&driver_dt282x);
}
module_init(driver_dt282x_init_module);
module_exit(driver_dt282x_cleanup_module);
static void free_resources(struct comedi_device *dev);
static int prep_ai_dma(struct comedi_device *dev, int chan, int size);
static int prep_ao_dma(struct comedi_device *dev, int chan, int size);
static int dt282x_ai_cancel(struct comedi_device *dev,
struct comedi_subdevice *s);
static int dt282x_ao_cancel(struct comedi_device *dev,
struct comedi_subdevice *s);
static int dt282x_ns_to_timer(int *nanosec, int round_mode);
static void dt282x_disable_dma(struct comedi_device *dev);
static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2);
static void dt282x_munge(struct comedi_device *dev, short *buf,
unsigned int nbytes)
{
unsigned int i;
unsigned short mask = (1 << boardtype.adbits) - 1;
unsigned short sign = 1 << (boardtype.adbits - 1);
int n;
if (devpriv->ad_2scomp)
sign = 1 << (boardtype.adbits - 1);
else
sign = 0;
if (nbytes % 2)
comedi_error(dev, "bug! odd number of bytes from dma xfer");
n = nbytes / 2;
for (i = 0; i < n; i++)
buf[i] = (buf[i] & mask) ^ sign;
}
static void dt282x_ao_dma_interrupt(struct comedi_device *dev)
{
void *ptr;
int size;
int i;
struct comedi_subdevice *s = dev->subdevices + 1;
update_supcsr(DT2821_CLRDMADNE);
if (!s->async->prealloc_buf) {
printk(KERN_ERR "async->data disappeared. dang!\n");
return;
}
i = devpriv->current_dma_index;
ptr = devpriv->dma[i].buf;
disable_dma(devpriv->dma[i].chan);
devpriv->current_dma_index = 1 - i;
size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize);
if (size == 0) {
printk(KERN_ERR "dt282x: AO underrun\n");
dt282x_ao_cancel(dev, s);
s->async->events |= COMEDI_CB_OVERFLOW;
return;
}
prep_ao_dma(dev, i, size);
return;
}
static void dt282x_ai_dma_interrupt(struct comedi_device *dev)
{
void *ptr;
int size;
int i;
int ret;
struct comedi_subdevice *s = dev->subdevices;
update_supcsr(DT2821_CLRDMADNE);
if (!s->async->prealloc_buf) {
printk(KERN_ERR "async->data disappeared. dang!\n");
return;
}
i = devpriv->current_dma_index;
ptr = devpriv->dma[i].buf;
size = devpriv->dma[i].size;
disable_dma(devpriv->dma[i].chan);
devpriv->current_dma_index = 1 - i;
dt282x_munge(dev, ptr, size);
ret = cfc_write_array_to_buffer(s, ptr, size);
if (ret != size) {
dt282x_ai_cancel(dev, s);
return;
}
devpriv->nread -= size / 2;
if (devpriv->nread < 0) {
printk(KERN_INFO "dt282x: off by one\n");
devpriv->nread = 0;
}
if (!devpriv->nread) {
dt282x_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_EOA;
return;
}
#if 0
/* clear the dual dma flag, making this the last dma segment */
/* XXX probably wrong */
if (!devpriv->ntrig) {
devpriv->supcsr &= ~(DT2821_DDMA);
update_supcsr(0);
}
#endif
/* restart the channel */
prep_ai_dma(dev, i, 0);
}
static int prep_ai_dma(struct comedi_device *dev, int dma_index, int n)
{
int dma_chan;
unsigned long dma_ptr;
unsigned long flags;
if (!devpriv->ntrig)
return 0;
if (n == 0)
n = devpriv->dma_maxsize;
if (n > devpriv->ntrig * 2)
n = devpriv->ntrig * 2;
devpriv->ntrig -= n / 2;
devpriv->dma[dma_index].size = n;
dma_chan = devpriv->dma[dma_index].chan;
dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
set_dma_mode(dma_chan, DMA_MODE_READ);
flags = claim_dma_lock();
clear_dma_ff(dma_chan);
set_dma_addr(dma_chan, dma_ptr);
set_dma_count(dma_chan, n);
release_dma_lock(flags);
enable_dma(dma_chan);
return n;
}
static int prep_ao_dma(struct comedi_device *dev, int dma_index, int n)
{
int dma_chan;
unsigned long dma_ptr;
unsigned long flags;
devpriv->dma[dma_index].size = n;
dma_chan = devpriv->dma[dma_index].chan;
dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
set_dma_mode(dma_chan, DMA_MODE_WRITE);
flags = claim_dma_lock();
clear_dma_ff(dma_chan);
set_dma_addr(dma_chan, dma_ptr);
set_dma_count(dma_chan, n);
release_dma_lock(flags);
enable_dma(dma_chan);
return n;
}
static irqreturn_t dt282x_interrupt(int irq, void *d)
{
struct comedi_device *dev = d;
struct comedi_subdevice *s;
struct comedi_subdevice *s_ao;
unsigned int supcsr, adcsr, dacsr;
int handled = 0;
if (!dev->attached) {
comedi_error(dev, "spurious interrupt");
return IRQ_HANDLED;
}
s = dev->subdevices + 0;
s_ao = dev->subdevices + 1;
adcsr = inw(dev->iobase + DT2821_ADCSR);
dacsr = inw(dev->iobase + DT2821_DACSR);
supcsr = inw(dev->iobase + DT2821_SUPCSR);
if (supcsr & DT2821_DMAD) {
if (devpriv->dma_dir == DMA_MODE_READ)
dt282x_ai_dma_interrupt(dev);
else
dt282x_ao_dma_interrupt(dev);
handled = 1;
}
if (adcsr & DT2821_ADERR) {
if (devpriv->nread != 0) {
comedi_error(dev, "A/D error");
dt282x_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_ERROR;
}
handled = 1;
}
if (dacsr & DT2821_DAERR) {
#if 0
static int warn = 5;
if (--warn <= 0) {
disable_irq(dev->irq);
printk(KERN_INFO "disabling irq\n");
}
#endif
comedi_error(dev, "D/A error");
dt282x_ao_cancel(dev, s_ao);
s->async->events |= COMEDI_CB_ERROR;
handled = 1;
}
#if 0
if (adcsr & DT2821_ADDONE) {
int ret;
short data;
data = (short)inw(dev->iobase + DT2821_ADDAT);
data &= (1 << boardtype.adbits) - 1;
if (devpriv->ad_2scomp)
data ^= 1 << (boardtype.adbits - 1);
ret = comedi_buf_put(s->async, data);
if (ret == 0)
s->async->events |= COMEDI_CB_OVERFLOW;
devpriv->nread--;
if (!devpriv->nread) {
s->async->events |= COMEDI_CB_EOA;
} else {
if (supcsr & DT2821_SCDN)
update_supcsr(DT2821_STRIG);
}
handled = 1;
}
#endif
comedi_event(dev, s);
/* printk("adcsr=0x%02x dacsr-0x%02x supcsr=0x%02x\n",
adcsr, dacsr, supcsr); */
return IRQ_RETVAL(handled);
}
static void dt282x_load_changain(struct comedi_device *dev, int n,
unsigned int *chanlist)
{
unsigned int i;
unsigned int chan, range;
outw(DT2821_LLE | (n - 1), dev->iobase + DT2821_CHANCSR);
for (i = 0; i < n; i++) {
chan = CR_CHAN(chanlist[i]);
range = CR_RANGE(chanlist[i]);
update_adcsr((range << 4) | (chan));
}
outw(n - 1, dev->iobase + DT2821_CHANCSR);
}
/*
* Performs a single A/D conversion.
* - Put channel/gain into channel-gain list
* - preload multiplexer
* - trigger conversion and wait for it to finish
*/
static int dt282x_ai_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
int i;
/* XXX should we really be enabling the ad clock here? */
devpriv->adcsr = DT2821_ADCLK;
update_adcsr(0);
dt282x_load_changain(dev, 1, &insn->chanspec);
update_supcsr(DT2821_PRLD);
wait_for(!mux_busy(), comedi_error(dev, "timeout\n"); return -ETIME;);
for (i = 0; i < insn->n; i++) {
update_supcsr(DT2821_STRIG);
wait_for(ad_done(), comedi_error(dev, "timeout\n");
return -ETIME;);
data[i] =
inw(dev->iobase +
DT2821_ADDAT) & ((1 << boardtype.adbits) - 1);
if (devpriv->ad_2scomp)
data[i] ^= (1 << (boardtype.adbits - 1));
}
return i;
}
static int dt282x_ai_cmdtest(struct comedi_device *dev,
struct comedi_subdevice *s, struct comedi_cmd *cmd)
{
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_FOLLOW | TRIG_EXT;
if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
err++;
tmp = cmd->convert_src;
cmd->convert_src &= TRIG_TIMER;
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++;
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_FOLLOW &&
cmd->scan_begin_src != TRIG_EXT)
err++;
if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
err++;
if (err)
return 2;
/* step 3: make sure arguments are trivially compatible */
if (cmd->start_arg != 0) {
cmd->start_arg = 0;
err++;
}
if (cmd->scan_begin_src == TRIG_FOLLOW) {
/* internal trigger */
if (cmd->scan_begin_arg != 0) {
cmd->scan_begin_arg = 0;
err++;
}
} else {
/* external trigger */
/* should be level/edge, hi/lo specification here */
if (cmd->scan_begin_arg != 0) {
cmd->scan_begin_arg = 0;
err++;
}
}
if (cmd->convert_arg < 4000) {
/* XXX board dependent */
cmd->convert_arg = 4000;
err++;
}
#define SLOWEST_TIMER (250*(1<<15)*255)
if (cmd->convert_arg > SLOWEST_TIMER) {
cmd->convert_arg = SLOWEST_TIMER;
err++;
}
if (cmd->convert_arg < this_board->ai_speed) {
cmd->convert_arg = this_board->ai_speed;
err++;
}
if (cmd->scan_end_arg != cmd->chanlist_len) {
cmd->scan_end_arg = cmd->chanlist_len;
err++;
}
if (cmd->stop_src == TRIG_COUNT) {
/* any count is allowed */
} else {
/* TRIG_NONE */
if (cmd->stop_arg != 0) {
cmd->stop_arg = 0;
err++;
}
}
if (err)
return 3;
/* step 4: fix up any arguments */
tmp = cmd->convert_arg;
dt282x_ns_to_timer(&cmd->convert_arg, cmd->flags & TRIG_ROUND_MASK);
if (tmp != cmd->convert_arg)
err++;
if (err)
return 4;
return 0;
}
static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{
struct comedi_cmd *cmd = &s->async->cmd;
int timer;
if (devpriv->usedma == 0) {
comedi_error(dev,
"driver requires 2 dma channels"
" to execute command");
return -EIO;
}
dt282x_disable_dma(dev);
if (cmd->convert_arg < this_board->ai_speed)
cmd->convert_arg = this_board->ai_speed;
timer = dt282x_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_NEAREST);
outw(timer, dev->iobase + DT2821_TMRCTR);
if (cmd->scan_begin_src == TRIG_FOLLOW) {
/* internal trigger */
devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0;
} else {
/* external trigger */
devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0 | DT2821_DS1;
}
update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT);
devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg;
devpriv->nread = devpriv->ntrig;
devpriv->dma_dir = DMA_MODE_READ;
devpriv->current_dma_index = 0;
prep_ai_dma(dev, 0, 0);
if (devpriv->ntrig) {
prep_ai_dma(dev, 1, 0);
devpriv->supcsr |= DT2821_DDMA;
update_supcsr(0);
}
devpriv->adcsr = 0;
dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist);
devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE;
update_adcsr(0);
update_supcsr(DT2821_PRLD);
wait_for(!mux_busy(), comedi_error(dev, "timeout\n"); return -ETIME;);
if (cmd->scan_begin_src == TRIG_FOLLOW) {
update_supcsr(DT2821_STRIG);
} else {
devpriv->supcsr |= DT2821_XTRIG;
update_supcsr(0);
}
return 0;
}
static void dt282x_disable_dma(struct comedi_device *dev)
{
if (devpriv->usedma) {
disable_dma(devpriv->dma[0].chan);
disable_dma(devpriv->dma[1].chan);
}
}
static int dt282x_ai_cancel(struct comedi_device *dev,
struct comedi_subdevice *s)
{
dt282x_disable_dma(dev);
devpriv->adcsr = 0;
update_adcsr(0);
devpriv->supcsr = 0;
update_supcsr(DT2821_ADCINIT);
return 0;
}
static int dt282x_ns_to_timer(int *nanosec, int round_mode)
{
int prescale, base, divider;
for (prescale = 0; prescale < 16; prescale++) {
if (prescale == 1)
continue;
base = 250 * (1 << prescale);
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 < 256) {
*nanosec = divider * base;
return (prescale << 8) | (255 - divider);
}
}
base = 250 * (1 << 15);
divider = 255;
*nanosec = divider * base;
return (15 << 8) | (255 - divider);
}
/*
* Analog output routine. Selects single channel conversion,
* selects correct channel, converts from 2's compliment to
* offset binary if necessary, loads the data into the DAC
* data register, and performs the conversion.
*/
static int dt282x_ao_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
data[0] = devpriv->ao[CR_CHAN(insn->chanspec)];
return 1;
}
static int dt282x_ao_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
short d;
unsigned int chan;
chan = CR_CHAN(insn->chanspec);
d = data[0];
d &= (1 << boardtype.dabits) - 1;
devpriv->ao[chan] = d;
devpriv->dacsr |= DT2821_SSEL;
if (chan) {
/* select channel */
devpriv->dacsr |= DT2821_YSEL;
if (devpriv->da0_2scomp)
d ^= (1 << (boardtype.dabits - 1));
} else {
devpriv->dacsr &= ~DT2821_YSEL;
if (devpriv->da1_2scomp)
d ^= (1 << (boardtype.dabits - 1));
}
update_dacsr(0);
outw(d, dev->iobase + DT2821_DADAT);
update_supcsr(DT2821_DACON);
return 1;
}
static int dt282x_ao_cmdtest(struct comedi_device *dev,
struct comedi_subdevice *s, struct comedi_cmd *cmd)
{
int err = 0;
int tmp;
/* step 1: make sure trigger sources are trivially valid */
tmp = cmd->start_src;
cmd->start_src &= TRIG_INT;
if (!cmd->start_src || tmp != cmd->start_src)
err++;
tmp = cmd->scan_begin_src;
cmd->scan_begin_src &= TRIG_TIMER;
if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
err++;
tmp = cmd->convert_src;
cmd->convert_src &= TRIG_NOW;
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_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->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
err++;
if (err)
return 2;
/* step 3: make sure arguments are trivially compatible */
if (cmd->start_arg != 0) {
cmd->start_arg = 0;
err++;
}
if (cmd->scan_begin_arg < 5000 /* XXX unknown */) {
cmd->scan_begin_arg = 5000;
err++;
}
if (cmd->convert_arg != 0) {
cmd->convert_arg = 0;
err++;
}
if (cmd->scan_end_arg > 2) {
/* XXX chanlist stuff? */
cmd->scan_end_arg = 2;
err++;
}
if (cmd->stop_src == TRIG_COUNT) {
/* any count is allowed */
} else {
/* TRIG_NONE */
if (cmd->stop_arg != 0) {
cmd->stop_arg = 0;
err++;
}
}
if (err)
return 3;
/* step 4: fix up any arguments */
tmp = cmd->scan_begin_arg;
dt282x_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
if (tmp != cmd->scan_begin_arg)
err++;
if (err)
return 4;
return 0;
}
static int dt282x_ao_inttrig(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int x)
{
int size;
if (x != 0)
return -EINVAL;
size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf,
devpriv->dma_maxsize);
if (size == 0) {
printk(KERN_ERR "dt282x: AO underrun\n");
return -EPIPE;
}
prep_ao_dma(dev, 0, size);
size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf,
devpriv->dma_maxsize);
if (size == 0) {
printk(KERN_ERR "dt282x: AO underrun\n");
return -EPIPE;
}
prep_ao_dma(dev, 1, size);
update_supcsr(DT2821_STRIG);
s->async->inttrig = NULL;
return 1;
}
static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{
int timer;
struct comedi_cmd *cmd = &s->async->cmd;
if (devpriv->usedma == 0) {
comedi_error(dev,
"driver requires 2 dma channels"
" to execute command");
return -EIO;
}
dt282x_disable_dma(dev);
devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS1 | DT2821_DDMA;
update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT);
devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len;
devpriv->nread = devpriv->ntrig;
devpriv->dma_dir = DMA_MODE_WRITE;
devpriv->current_dma_index = 0;
timer = dt282x_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
outw(timer, dev->iobase + DT2821_TMRCTR);
devpriv->dacsr = DT2821_SSEL | DT2821_DACLK | DT2821_IDARDY;
update_dacsr(0);
s->async->inttrig = dt282x_ao_inttrig;
return 0;
}
static int dt282x_ao_cancel(struct comedi_device *dev,
struct comedi_subdevice *s)
{
dt282x_disable_dma(dev);
devpriv->dacsr = 0;
update_dacsr(0);
devpriv->supcsr = 0;
update_supcsr(DT2821_DACINIT);
return 0;
}
static int dt282x_dio_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
if (data[0]) {
s->state &= ~data[0];
s->state |= (data[0] & data[1]);
outw(s->state, dev->iobase + DT2821_DIODAT);
}
data[1] = inw(dev->iobase + DT2821_DIODAT);
return 2;
}
static int dt282x_dio_insn_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
int mask;
mask = (CR_CHAN(insn->chanspec) < 8) ? 0x00ff : 0xff00;
if (data[0])
s->io_bits |= mask;
else
s->io_bits &= ~mask;
if (s->io_bits & 0x00ff)
devpriv->dacsr |= DT2821_LBOE;
else
devpriv->dacsr &= ~DT2821_LBOE;
if (s->io_bits & 0xff00)
devpriv->dacsr |= DT2821_HBOE;
else
devpriv->dacsr &= ~DT2821_HBOE;
outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
return 1;
}
static const struct comedi_lrange *const ai_range_table[] = {
&range_dt282x_ai_lo_bipolar,
&range_dt282x_ai_lo_unipolar,
&range_dt282x_ai_5_bipolar,
&range_dt282x_ai_5_unipolar
};
static const struct comedi_lrange *const ai_range_pgl_table[] = {
&range_dt282x_ai_hi_bipolar,
&range_dt282x_ai_hi_unipolar
};
static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x)
{
if (ispgl) {
if (x < 0 || x >= 2)
x = 0;
return ai_range_pgl_table[x];
} else {
if (x < 0 || x >= 4)
x = 0;
return ai_range_table[x];
}
}
static const struct comedi_lrange *const ao_range_table[] = {
&range_bipolar10,
&range_unipolar10,
&range_bipolar5,
&range_unipolar5,
&range_bipolar2_5
};
static const struct comedi_lrange *opt_ao_range_lkup(int x)
{
if (x < 0 || x >= 5)
x = 0;
return ao_range_table[x];
}
enum { /* i/o base, irq, dma channels */
opt_iobase = 0, opt_irq, opt_dma1, opt_dma2,
opt_diff, /* differential */
opt_ai_twos, opt_ao0_twos, opt_ao1_twos, /* twos comp */
opt_ai_range, opt_ao0_range, opt_ao1_range, /* range */
};
/*
options:
0 i/o base
1 irq
2 dma1
3 dma2
4 0=single ended, 1=differential
5 ai 0=straight binary, 1=2's comp
6 ao0 0=straight binary, 1=2's comp
7 ao1 0=straight binary, 1=2's comp
8 ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V
9 ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
10 ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
*/
static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
int i, irq;
int ret;
struct comedi_subdevice *s;
unsigned long iobase;
dev->board_name = this_board->name;
iobase = it->options[opt_iobase];
if (!iobase)
iobase = 0x240;
printk(KERN_INFO "comedi%d: dt282x: 0x%04lx", dev->minor, iobase);
if (!request_region(iobase, DT2821_SIZE, "dt282x")) {
printk(KERN_INFO " I/O port conflict\n");
return -EBUSY;
}
dev->iobase = iobase;
outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR);
i = inw(dev->iobase + DT2821_ADCSR);
#ifdef DEBUG
printk(KERN_DEBUG " fingerprint=%x,%x,%x,%x,%x",
inw(dev->iobase + DT2821_ADCSR),
inw(dev->iobase + DT2821_CHANCSR),
inw(dev->iobase + DT2821_DACSR),
inw(dev->iobase + DT2821_SUPCSR),
inw(dev->iobase + DT2821_TMRCTR));
#endif
if (((inw(dev->iobase + DT2821_ADCSR) & DT2821_ADCSR_MASK)
!= DT2821_ADCSR_VAL) ||
((inw(dev->iobase + DT2821_CHANCSR) & DT2821_CHANCSR_MASK)
!= DT2821_CHANCSR_VAL) ||
((inw(dev->iobase + DT2821_DACSR) & DT2821_DACSR_MASK)
!= DT2821_DACSR_VAL) ||
((inw(dev->iobase + DT2821_SUPCSR) & DT2821_SUPCSR_MASK)
!= DT2821_SUPCSR_VAL) ||
((inw(dev->iobase + DT2821_TMRCTR) & DT2821_TMRCTR_MASK)
!= DT2821_TMRCTR_VAL)) {
printk(KERN_ERR " board not found");
return -EIO;
}
/* should do board test */
irq = it->options[opt_irq];
#if 0
if (irq < 0) {
unsigned long flags;
int irqs;
save_flags(flags);
sti();
irqs = probe_irq_on();
/* trigger interrupt */
udelay(100);
irq = probe_irq_off(irqs);
restore_flags(flags);
if (0 /* error */)
printk(KERN_ERR " error probing irq (bad)");
}
#endif
if (irq > 0) {
printk(KERN_INFO " ( irq = %d )", irq);
ret = request_irq(irq, dt282x_interrupt, 0, "dt282x", dev);
if (ret < 0) {
printk(KERN_ERR " failed to get irq\n");
return -EIO;
}
dev->irq = irq;
} else if (irq == 0) {
printk(KERN_INFO " (no irq)");
} else {
#if 0
printk(KERN_INFO " (probe returned multiple irqs--bad)");
#else
printk(KERN_INFO " (irq probe not implemented)");
#endif
}
ret = alloc_private(dev, sizeof(struct dt282x_private));
if (ret < 0)
return ret;
ret = dt282x_grab_dma(dev, it->options[opt_dma1],
it->options[opt_dma2]);
if (ret < 0)
return ret;
ret = alloc_subdevices(dev, 3);
if (ret < 0)
return ret;
s = dev->subdevices + 0;
dev->read_subdev = s;
/* ai subdevice */
s->type = COMEDI_SUBD_AI;
s->subdev_flags = SDF_READABLE | SDF_CMD_READ |
((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON);
s->n_chan =
(it->options[opt_diff]) ? boardtype.adchan_di : boardtype.adchan_se;
s->insn_read = dt282x_ai_insn_read;
s->do_cmdtest = dt282x_ai_cmdtest;
s->do_cmd = dt282x_ai_cmd;
s->cancel = dt282x_ai_cancel;
s->maxdata = (1 << boardtype.adbits) - 1;
s->len_chanlist = 16;
s->range_table =
opt_ai_range_lkup(boardtype.ispgl, it->options[opt_ai_range]);
devpriv->ad_2scomp = it->options[opt_ai_twos];
s++;
s->n_chan = boardtype.dachan;
if (s->n_chan) {
/* ao subsystem */
s->type = COMEDI_SUBD_AO;
dev->write_subdev = s;
s->subdev_flags = SDF_WRITABLE | SDF_CMD_WRITE;
s->insn_read = dt282x_ao_insn_read;
s->insn_write = dt282x_ao_insn_write;
s->do_cmdtest = dt282x_ao_cmdtest;
s->do_cmd = dt282x_ao_cmd;
s->cancel = dt282x_ao_cancel;
s->maxdata = (1 << boardtype.dabits) - 1;
s->len_chanlist = 2;
s->range_table_list = devpriv->darangelist;
devpriv->darangelist[0] =
opt_ao_range_lkup(it->options[opt_ao0_range]);
devpriv->darangelist[1] =
opt_ao_range_lkup(it->options[opt_ao1_range]);
devpriv->da0_2scomp = it->options[opt_ao0_twos];
devpriv->da1_2scomp = it->options[opt_ao1_twos];
} else {
s->type = COMEDI_SUBD_UNUSED;
}
s++;
/* dio subsystem */
s->type = COMEDI_SUBD_DIO;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan = 16;
s->insn_bits = dt282x_dio_insn_bits;
s->insn_config = dt282x_dio_insn_config;
s->maxdata = 1;
s->range_table = &range_digital;
printk(KERN_INFO "\n");
return 0;
}
static void free_resources(struct comedi_device *dev)
{
if (dev->irq)
free_irq(dev->irq, dev);
if (dev->iobase)
release_region(dev->iobase, DT2821_SIZE);
if (dev->private) {
if (devpriv->dma[0].chan)
free_dma(devpriv->dma[0].chan);
if (devpriv->dma[1].chan)
free_dma(devpriv->dma[1].chan);
if (devpriv->dma[0].buf)
free_page((unsigned long)devpriv->dma[0].buf);
if (devpriv->dma[1].buf)
free_page((unsigned long)devpriv->dma[1].buf);
}
}
static int dt282x_detach(struct comedi_device *dev)
{
printk(KERN_INFO "comedi%d: dt282x: remove\n", dev->minor);
free_resources(dev);
return 0;
}
static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2)
{
int ret;
devpriv->usedma = 0;
if (!dma1 && !dma2) {
printk(KERN_ERR " (no dma)");
return 0;
}
if (dma1 == dma2 || dma1 < 5 || dma2 < 5 || dma1 > 7 || dma2 > 7)
return -EINVAL;
if (dma2 < dma1) {
int i;
i = dma1;
dma1 = dma2;
dma2 = i;
}
ret = request_dma(dma1, "dt282x A");
if (ret)
return -EBUSY;
devpriv->dma[0].chan = dma1;
ret = request_dma(dma2, "dt282x B");
if (ret)
return -EBUSY;
devpriv->dma[1].chan = dma2;
devpriv->dma_maxsize = PAGE_SIZE;
devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
if (!devpriv->dma[0].buf || !devpriv->dma[1].buf) {
printk(KERN_ERR " can't get DMA memory");
return -ENOMEM;
}
printk(KERN_INFO " (dma=%d,%d)", dma1, dma2);
devpriv->usedma = 1;
return 0;
}
MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");