/* 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 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 #include #include #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;_iad_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("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("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("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("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("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("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("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 { opt_iobase = 0, opt_irq, opt_dma1, opt_dma2, /* i/o base, irq, dma channels */ 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("comedi%d: dt282x: 0x%04lx", dev->minor, iobase); if (!request_region(iobase, DT2821_SIZE, "dt282x")) { printk(" 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(" 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(" 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(" error probing irq (bad)"); } } #endif if (irq > 0) { printk(" ( irq = %d )", irq); ret = request_irq(irq, dt282x_interrupt, 0, "dt282x", dev); if (ret < 0) { printk(" failed to get irq\n"); return -EIO; } dev->irq = irq; } else if (irq == 0) { printk(" (no irq)"); } else { #if 0 printk(" (probe returned multiple irqs--bad)"); #else printk(" (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("\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("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(" (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(" can't get DMA memory"); return -ENOMEM; } printk(" (dma=%d,%d)", dma1, dma2); devpriv->usedma = 1; return 0; }