/* das800.c driver for Keitley das800 series boards and compatibles Copyright (C) 2000 Frank Mori Hess COMEDI - Linux Control and Measurement Device Interface Copyright (C) 2000 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: das800.o Description: Keithley Metrabyte DAS800 (& compatibles) Author: Frank Mori Hess Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801), DAS-802 (das-802), [Measurement Computing] CIO-DAS800 (cio-das800), CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802), CIO-DAS802/16 (cio-das802/16) Status: works, cio-das802/16 untested - email me if you have tested it Configuration options: [0] - I/O port base address [1] - IRQ (optional, required for timed or externally triggered conversions) Notes: IRQ can be omitted, although the cmd interface will not work without it. All entries in the channel/gain list must use the same gain and be consecutive channels counting upwards in channel number (these are hardware limitations.) I've never tested the gain setting stuff since I only have a DAS-800 board with fixed gain. The cio-das802/16 does not have a fifo-empty status bit! Therefore only fifo-half-full transfers are possible with this card. */ /* cmd triggers supported: start_src: TRIG_NOW | TRIG_EXT scan_begin_src: TRIG_FOLLOW scan_end_src: TRIG_COUNT convert_src: TRIG_TIMER | TRIG_EXT stop_src: TRIG_NONE | TRIG_COUNT */ #include #include #include #include "8253.h" #include "comedi_fc.h" #define DAS800_SIZE 8 #define TIMER_BASE 1000 #define N_CHAN_AI 8 // number of analog input channels /* Registers for the das800 */ #define DAS800_LSB 0 #define FIFO_EMPTY 0x1 #define FIFO_OVF 0x2 #define DAS800_MSB 1 #define DAS800_CONTROL1 2 #define CONTROL1_INTE 0x8 #define DAS800_CONV_CONTROL 2 #define ITE 0x1 #define CASC 0x2 #define DTEN 0x4 #define IEOC 0x8 #define EACS 0x10 #define CONV_HCEN 0x80 #define DAS800_SCAN_LIMITS 2 #define DAS800_STATUS 2 #define IRQ 0x8 #define BUSY 0x80 #define DAS800_GAIN 3 #define CIO_FFOV 0x8 // fifo overflow for cio-das802/16 #define CIO_ENHF 0x90 // interrupt fifo half full for cio-das802/16 #define CONTROL1 0x80 #define CONV_CONTROL 0xa0 #define SCAN_LIMITS 0xc0 #define ID 0xe0 #define DAS800_8254 4 #define DAS800_STATUS2 7 #define STATUS2_HCEN 0x80 #define STATUS2_INTE 0X20 #define DAS800_ID 7 typedef struct das800_board_struct{ char *name; int ai_speed; comedi_lrange *ai_range; int resolution; }das800_board; //analog input ranges static comedi_lrange range_das800_ai = { 1, { RANGE( -5, 5 ), } }; static comedi_lrange range_das801_ai = { 9, { RANGE(-5, 5), RANGE(-10, 10), RANGE(0, 10), RANGE(-0.5, 0.5), RANGE(0, 1), RANGE(-0.05, 0.05), RANGE(0, 0.1), RANGE(-0.01, 0.01), RANGE(0, 0.02), } }; static comedi_lrange range_cio_das801_ai = { 9, { RANGE(-5, 5), RANGE(-10, 10), RANGE(0, 10), RANGE(-0.5, 0.5), RANGE(0, 1), RANGE(-0.05, 0.05), RANGE(0, 0.1), RANGE(-0.005, 0.005), RANGE(0, 0.01), } }; static comedi_lrange range_das802_ai = { 9, { RANGE(-5, 5), RANGE(-10, 10), RANGE(0, 10), RANGE(-2.5, 2.5), RANGE(0, 5), RANGE(-1.25, 1.25), RANGE(0, 2.5), RANGE(-0.625, 0.625), RANGE(0, 1.25), } }; static comedi_lrange range_das80216_ai = { 8, { RANGE(-10, 10), RANGE(0, 10), RANGE(-5, 5), RANGE(0, 5), RANGE(-2.5, 2.5), RANGE(0, 2.5), RANGE(-1.25, 1.25), RANGE(0, 1.25), } }; enum{das800, ciodas800, das801, ciodas801, das802, ciodas802, ciodas80216}; static das800_board das800_boards[] = { { name: "das-800", ai_speed: 25000, ai_range: &range_das800_ai, resolution: 12, }, { name: "cio-das800", ai_speed: 20000, ai_range: &range_das800_ai, resolution: 12, }, { name: "das-801", ai_speed: 25000, ai_range: &range_das801_ai, resolution: 12, }, { name: "cio-das801", ai_speed: 20000, ai_range: &range_cio_das801_ai, resolution: 12, }, { name: "das-802", ai_speed: 25000, ai_range: &range_das802_ai, resolution: 12, }, { name: "cio-das802", ai_speed: 20000, ai_range: &range_das802_ai, resolution: 12, }, { name: "cio-das802/16", ai_speed: 10000, ai_range: &range_das80216_ai, resolution: 16, }, }; /* * Useful for shorthand access to the particular board structure */ #define thisboard ((das800_board *)dev->board_ptr) typedef struct{ volatile unsigned int count; /* number of data points left to be taken */ volatile int forever; /* flag indicating whether we should take data forever */ unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */ unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */ volatile int do_bits; /* digital output bits */ }das800_private; #define devpriv ((das800_private *)dev->private) static int das800_attach(comedi_device *dev,comedi_devconfig *it); static int das800_detach(comedi_device *dev); static int das800_cancel(comedi_device *dev, comedi_subdevice *s); static comedi_driver driver_das800={ driver_name: "das800", module: THIS_MODULE, attach: das800_attach, detach: das800_detach, num_names: sizeof(das800_boards) / sizeof(das800_board), board_name: (char **)das800_boards, offset: sizeof(das800_board), }; static void das800_interrupt(int irq, void *d, struct pt_regs *regs); static void enable_das800(comedi_device *dev); static void disable_das800(comedi_device *dev); static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd); static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s); static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data); static int das800_di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data); static int das800_do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data); static int das800_probe(comedi_device *dev); static int das800_set_frequency(comedi_device *dev); /* checks and probes das-800 series board type */ static int das800_probe(comedi_device *dev) { int id_bits; unsigned long irq_flags; int board; // 'comedi spin lock irqsave' disables even rt interrupts, we use them to protect indirect addressing comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(ID, dev->iobase + DAS800_GAIN); /* select base address + 7 to be ID register */ id_bits = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */ comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); board = thisboard - das800_boards; switch(id_bits) { case 0x0: if(board == das800) { printk(" Board model: DAS-800\n"); return board; } if(board == ciodas800) { printk(" Board model: CIO-DAS800\n"); return board; } printk(" Board model (probed): DAS-800\n"); return das800; break; case 0x2: if(board == das801) { printk(" Board model: DAS-801\n"); return board; } if(board == ciodas801) { printk(" Board model: CIO-DAS801\n"); return board; } printk(" Board model (probed): DAS-801\n"); return das801; break; case 0x3: if(board == das802) { printk(" Board model: DAS-802\n"); return board; } if(board == ciodas802) { printk(" Board model: CIO-DAS802\n"); return board; } if(board == ciodas80216) { printk(" Board model: CIO-DAS802/16\n"); return board; } printk(" Board model (probed): DAS-802\n"); return das802; break; default : printk(" Board model: probe returned 0x%x (unknown)\n", id_bits); return board; break; } return -1; } /* * A convenient macro that defines init_module() and cleanup_module(), * as necessary. */ COMEDI_INITCLEANUP(driver_das800); /* interrupt service routine */ static void das800_interrupt(int irq, void *d, struct pt_regs *regs) { short i; /* loop index */ sampl_t dataPoint = 0; comedi_device *dev = d; comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */ comedi_async *async; int status; unsigned long irq_flags; static const int max_loops = 128; // half-fifo size for cio-das802/16 // flags int fifo_empty = 0; int fifo_overflow = 0; status = inb(dev->iobase + DAS800_STATUS); /* if interrupt was not generated by board or driver not attached, quit */ if(!(status & IRQ) || !(dev->attached)) { return; } /* wait until here to initialize async, since we will get null dereference * if interrupt occurs before driver is fully attached! */ async = s->async; // if hardware conversions are not enabled, then quit comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */ status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN; /* don't release spinlock yet since we want to make sure noone else disables hardware conversions */ if(status == 0) { comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); return; } /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */ for(i = 0; i < max_loops; i++) { /* read 16 bits from dev->iobase and dev->iobase + 1 */ dataPoint = inb(dev->iobase + DAS800_LSB); dataPoint += inb(dev->iobase + DAS800_MSB) << 8; if(thisboard->resolution == 12) { fifo_empty = dataPoint & FIFO_EMPTY; fifo_overflow = dataPoint & FIFO_OVF; if(fifo_overflow) break; }else { fifo_empty = 0; // cio-das802/16 has no fifo empty status bit } if(fifo_empty) { break; } /* strip off extraneous bits for 12 bit cards*/ if(thisboard->resolution == 12) dataPoint = (dataPoint >> 4) & 0xfff; /* if there are more data points to collect */ if(devpriv->count > 0 || devpriv->forever == 1) { /* write data point to buffer */ cfc_write_to_buffer( s, dataPoint); if(devpriv->count > 0) devpriv->count--; } } async->events |= COMEDI_CB_BLOCK; /* check for fifo overflow */ if(thisboard->resolution == 12) { fifo_overflow = dataPoint & FIFO_OVF; // else cio-das802/16 }else { fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV; } if(fifo_overflow) { comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); comedi_error(dev, "DAS800 FIFO overflow"); das800_cancel(dev, dev->subdevices + 0); async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; comedi_event(dev, s, async->events); async->events = 0; return; } if(devpriv->count > 0 || devpriv->forever == 1) { /* Re-enable card's interrupt. * We already have spinlock, so indirect addressing is safe */ outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* otherwise, stop taking data */ } else { disable_das800(dev); /* diable hardware triggered conversions */ async->events |= COMEDI_CB_EOA; } comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); comedi_event(dev, s, async->events); async->events = 0; return; } static int das800_attach(comedi_device *dev, comedi_devconfig *it) { comedi_subdevice *s; int iobase = it->options[0]; int irq = it->options[1]; unsigned long irq_flags; int board; printk("comedi%d: das800: io 0x%x", dev->minor, iobase); if(irq) { printk(", irq %i", irq); } printk("\n"); /* allocate and initialize dev->private */ if(alloc_private(dev, sizeof(das800_private)) < 0) return -ENOMEM; if(iobase == 0) { printk("io base address required for das800\n"); return -EINVAL; } /* check if io addresses are available */ if(check_region(iobase, DAS800_SIZE) < 0) { printk("I/O port conflict\n"); return -EIO; } request_region(iobase, DAS800_SIZE, "das800"); dev->iobase = iobase; board = das800_probe(dev); if(board < 0) { printk("unable to determine board type\n"); return -ENODEV; } dev->board_ptr = das800_boards + board; /* grab our IRQ */ if(irq == 1 || irq > 7 || irq < 0) { printk("irq out of range\n"); return -EINVAL; } if(irq) { if(comedi_request_irq( irq, das800_interrupt, 0, "das800", dev )) { printk( "unable to allocate irq %d\n", irq); return -EINVAL; } } dev->irq = irq; dev->board_name = thisboard->name; dev->n_subdevices = 3; if(alloc_subdevices(dev) < 0) return -ENOMEM; /* analog input subdevice */ s = dev->subdevices + 0; dev->read_subdev = s; s->type = COMEDI_SUBD_AI; s->subdev_flags = SDF_READABLE | SDF_GROUND; s->n_chan = 8; s->len_chanlist = 8; s->maxdata = (1 << thisboard->resolution) - 1; s->range_table = thisboard->ai_range; s->do_cmd = das800_ai_do_cmd; s->do_cmdtest = das800_ai_do_cmdtest; s->insn_read = das800_ai_rinsn; s->cancel = das800_cancel; /* di */ s = dev->subdevices + 1; s->type=COMEDI_SUBD_DI; s->subdev_flags = SDF_READABLE; s->n_chan = 3; s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = das800_di_rbits; /* do */ s = dev->subdevices + 2; s->type=COMEDI_SUBD_DO; s->subdev_flags = SDF_WRITABLE | SDF_READABLE; s->n_chan = 4; s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = das800_do_wbits; disable_das800(dev); /* initialize digital out channels */ comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); return 0; }; static int das800_detach(comedi_device *dev) { printk("comedi%d: das800: remove\n", dev->minor); /* only free stuff if it has been allocated by _attach */ if(dev->iobase) release_region(dev->iobase, DAS800_SIZE); if(dev->irq) comedi_free_irq(dev->irq, dev); return 0; }; static int das800_cancel(comedi_device *dev, comedi_subdevice *s) { devpriv->forever = 0; devpriv->count = 0; disable_das800(dev); return 0; } /* enable_das800 makes the card start taking hardware triggered conversions */ static void enable_das800(comedi_device *dev) { unsigned long irq_flags; comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); // enable fifo-half full interrupts for cio-das802/16 if(thisboard->resolution == 16) outb(CIO_ENHF, dev->iobase + DAS800_GAIN); outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */ outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */ comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); } /* disable_das800 stops hardware triggered conversions */ static void disable_das800(comedi_device *dev) { unsigned long irq_flags; comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */ comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); } static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd) { int err = 0; int tmp; int gain, startChan; int i; /* step 1: make sure trigger sources are trivially valid */ tmp = cmd->start_src; cmd->start_src &= TRIG_NOW | TRIG_EXT; if(!cmd->start_src || tmp != cmd->start_src) err++; tmp = cmd->scan_begin_src; cmd->scan_begin_src &= TRIG_FOLLOW; 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++; if(err) return 1; /* step 2: make sure trigger sources are unique and mutually compatible */ if(cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) err++; if(cmd->convert_src != TRIG_TIMER && cmd->convert_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->convert_src == TRIG_TIMER) { if(cmd->convert_arg < thisboard->ai_speed) { cmd->convert_arg = thisboard->ai_speed; err++; } } if(!cmd->chanlist_len) { cmd->chanlist_len = 1; err++; } if(cmd->scan_end_arg != cmd->chanlist_len) { cmd->scan_end_arg = cmd->chanlist_len; err++; } if(cmd->stop_src == TRIG_COUNT) { if(!cmd->stop_arg) { cmd->stop_arg = 1; err++; } } else { /* TRIG_NONE */ if(cmd->stop_arg != 0) { cmd->stop_arg = 0; err++; } } if(err)return 3; /* step 4: fix up any arguments */ if(cmd->convert_src == TRIG_TIMER) { tmp = cmd->convert_arg; /* calculate counter values that give desired timing */ i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), &(devpriv->divisor2), &(cmd->convert_arg), cmd->flags & TRIG_ROUND_MASK); if(tmp != cmd->convert_arg) err++; } if(err)return 4; // check channel/gain list against card's limitations if(cmd->chanlist) { gain = CR_RANGE(cmd->chanlist[0]); startChan = CR_CHAN(cmd->chanlist[0]); for(i = 1; i < cmd->chanlist_len; i++) { if(CR_CHAN(cmd->chanlist[i]) != (startChan + i) % N_CHAN_AI) { comedi_error(dev, "entries in chanlist must be consecutive channels, counting upwards\n"); err++; } if(CR_RANGE(cmd->chanlist[i]) != gain) { comedi_error(dev, "entries in chanlist must all have the same gain\n"); err++; } } } if(err)return 5; return 0; } static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s) { int startChan, endChan, scan, gain; int conv_bits; unsigned long irq_flags; comedi_async *async = s->async; if(!dev->irq) { comedi_error(dev, "no irq assigned for das-800, cannot do hardware conversions"); return -1; } disable_das800(dev); /* set channel scan limits */ startChan = CR_CHAN(async->cmd.chanlist[0]); endChan = (startChan + async->cmd.chanlist_len - 1) % 8; scan = (endChan << 3) | startChan; comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */ outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */ comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); /* set gain */ gain = CR_RANGE(async->cmd.chanlist[0]); if( thisboard->resolution == 12 && gain > 0) gain += 0x7; gain &= 0xf; outb(gain, dev->iobase + DAS800_GAIN); switch(async->cmd.stop_src) { case TRIG_COUNT: devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len; devpriv->forever = 0; break; case TRIG_NONE: devpriv->forever = 1; devpriv->count = 0; break; default : break; } /* enable auto channel scan, send interrupts on end of conversion * and set clock source to internal or external */ conv_bits = 0; conv_bits |= EACS | IEOC; if(async->cmd.start_src == TRIG_EXT) conv_bits |= DTEN; switch(async->cmd.convert_src) { case TRIG_TIMER: conv_bits |= CASC | ITE; /* set conversion frequency */ i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), &(devpriv->divisor2), &(async->cmd.convert_arg), async->cmd.flags & TRIG_ROUND_MASK); if(das800_set_frequency(dev) < 0) { comedi_error(dev, "Error setting up counters"); return -1; } break; case TRIG_EXT: break; default: break; } comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL); comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); async->events = 0; enable_das800(dev); return 0; } static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data) { int i, n; int chan; int range; int lsb, msb; int timeout = 1000; unsigned long irq_flags; disable_das800(dev); /* disable hardware conversions (enables software conversions) */ /* set multiplexer */ chan = CR_CHAN(insn->chanspec); comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); /* set gain / range */ range = CR_RANGE(insn->chanspec); if(thisboard->resolution == 12 && range) range += 0x7; range &= 0xf; outb(range, dev->iobase + DAS800_GAIN); udelay(5); for(n = 0; n < insn->n; n++) { /* trigger conversion */ outb_p(0, dev->iobase + DAS800_MSB); for(i = 0; i < timeout; i++) { if(!(inb(dev->iobase + DAS800_STATUS) & BUSY)) break; } if(i == timeout) { comedi_error(dev, "timeout"); return -ETIME; } lsb = inb(dev->iobase + DAS800_LSB); msb = inb(dev->iobase + DAS800_MSB); if(thisboard->resolution == 12) { data[n] = (lsb >> 4) & 0xff; data[n] |= (msb << 4); }else { data[n] = (msb << 8) | lsb; } } return n; } static int das800_di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data) { lsampl_t bits; bits = inb(dev->iobase + DAS800_STATUS) >> 4; bits &= 0x7; data[1] = bits; data[0] = 0; return 2; } static int das800_do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data) { int wbits; unsigned long irq_flags; // only set bits that have been masked data[0] &= 0xf; wbits = devpriv->do_bits >> 4; wbits &= ~data[0]; wbits |= data[0] & data[1]; devpriv->do_bits = wbits << 4; comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1); comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); data[1] = wbits; return 2; } /* loads counters with divisor1, divisor2 from private structure */ static int das800_set_frequency(comedi_device *dev) { int err = 0; if(i8254_load(dev->iobase + DAS800_8254, 1, devpriv->divisor1, 2)) err++; if(i8254_load(dev->iobase + DAS800_8254, 2, devpriv->divisor2, 2)) err++; if(err) return -1; return 0; }