From Juan Grigera <juan@grigera.com.ar>
authorDavid Schleef <ds@schleef.org>
Wed, 3 Apr 2002 07:12:25 +0000 (07:12 +0000)
committerDavid Schleef <ds@schleef.org>
Wed, 3 Apr 2002 07:12:25 +0000 (07:12 +0000)
comedi/drivers/pcl816.c [new file with mode: 0644]

diff --git a/comedi/drivers/pcl816.c b/comedi/drivers/pcl816.c
new file mode 100644 (file)
index 0000000..1fec344
--- /dev/null
@@ -0,0 +1,1263 @@
+/*
+   module/pcl816.c
+
+   Author:  Juan Grigera <juan@grigera.com.ar>
+            based on pcl818 by Michal Dobes <majkl@tesnet.cz> and bits of pcl812
+
+   hardware driver for Advantech cards:
+    card:   PCL-816, PCL814B
+    driver: pcl816
+*/
+/*
+Driver: pcl816.o
+Description: Advantech PCL-816 cards, PCL-814
+Author: Juan Grigera <juan@grigera.com.ar>
+Devices: [Advantech] PCL-816, PCL-814B
+Status: works
+
+PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO.
+Differences are at resolution (16 vs 12 bits).
+
+The driver support AI command mode, other subdevices not written.
+See the head of the source file pcl816.c for configuration options.
+*/
+/*
+   Options for PCL-816:
+    [0] - IO Base
+    [1] - IRQ  (0=disable, 2, 3, 4, 5, 6, 7)
+    [2] - DMA  (0=disable, 1, 3)
+    [3] - 0, 10=10MHz clock for 8254
+              1= 1MHz clock for 8254
+
+       Notes:
+               WARNING: AO, DI, and DO are not implemented!!!!
+
+*/
+#define DEBUG(x) x
+
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mc146818rtc.h>
+#include <linux/delay.h>
+#include <asm/dma.h>
+#include <linux/comedidev.h>
+#include "8253.h"
+
+
+// boards constants
+// IO space len
+#define PCLx1x_RANGE 16
+
+//#define outb(x,y)  printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y)
+
+// INTEL 8254 counters
+#define PCL816_CTR0 4
+#define PCL816_CTR1 5
+#define PCL816_CTR2 6
+// R: counter read-back register W: counter control
+#define PCL816_CTRCTL 7
+
+// R: A/D high byte W: A/D range control
+#define PCL816_RANGE 9
+// W: clear INT request
+#define PCL816_CLRINT 10
+// R: next mux scan channel W: mux scan channel & range control pointer
+#define PCL816_MUX 11
+// R/W: operation control register
+#define PCL816_CONTROL 12
+
+// R: return status byte  W: set DMA/IRQ
+#define PCL816_STATUS 13
+#define PCL816_STATUS_DRDY_MASK 0x80
+
+// R: low byte of A/D W: soft A/D trigger
+#define PCL816_AD_LO 8
+// R: high byte of A/D W: A/D range control
+#define PCL816_AD_HI 9
+
+
+// type of interrupt handler
+#define INT_TYPE_AI1_INT 1
+#define INT_TYPE_AI1_DMA 2
+#define INT_TYPE_AI3_INT 4
+#define INT_TYPE_AI3_DMA 5
+#define INT_TYPE_AI1_DMA_RTC 9
+#define INT_TYPE_AI3_DMA_RTC 10
+
+
+// RTC stuff...
+#define RTC_IRQ        8
+#define RTC_IO_EXTENT  0x10
+
+#define MAGIC_DMA_WORD 0x5a5a
+
+static comedi_lrange range_pcl816 = { 8, {
+                                             BIP_RANGE (10),
+                                             BIP_RANGE (5),
+                                             BIP_RANGE (2.5),
+                                             BIP_RANGE (1.25),
+                                             UNI_RANGE (10),
+                                             UNI_RANGE (5),
+                                             UNI_RANGE (2.5),
+                                             UNI_RANGE (1.25),
+                             }
+};
+typedef struct {
+  char *name;                  // driver name
+  int n_ranges;                        // len of range list
+  int n_aichan;                        // num of A/D chans in diferencial mode
+  unsigned int ai_ns_min;              // minimal alllowed delay between samples (in ns)
+  int n_aochan;                        // num of D/A chans
+  int n_dichan;                        // num of DI chans
+  int n_dochan;                        // num of DO chans
+  comedi_lrange *ai_range_type;        // default A/D rangelist
+  comedi_lrange *ao_range_type;        // dafault D/A rangelist
+  int io_range;                        // len of IO space
+  unsigned int IRQbits;                // allowed interrupts
+  unsigned int DMAbits;                // allowed DMA chans
+  int ai_maxdata;              // maxdata for A/D
+  int ao_maxdata;              // maxdata for D/A           
+  int ai_chanlist;             // allowed len of channel list A/D
+  int ao_chanlist;             // allowed len of channel list D/A
+  int i8254_osc_base;          // 1/frequency of on board oscilator in ns
+}
+boardtype;
+
+static boardtype boardtypes[] = {
+    {"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816,
+               &range_pcl816, PCLx1x_RANGE, 
+               0x00fc,                 // IRQ mask 
+               0x0a,                   // DMA mask
+               0xffff,                 // 16-bit card 
+               0xffff,                 // D/A maxdata
+               1024, 
+               1,                              // ao chan list 
+               100 },
+    {"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816,
+               &range_pcl816, PCLx1x_RANGE, 
+               0x00fc,
+               0x0a, 
+               0x3fff,                 /* 14 bit card */
+               0x3fff, 
+               1024,
+               1,
+               100},
+};
+
+#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
+#define devpriv ((pcl816_private *)dev->private)
+#define this_board ((boardtype *)dev->board_ptr)
+
+static int pcl816_attach (comedi_device * dev, comedi_devconfig * it);
+static int pcl816_detach (comedi_device * dev);
+
+static int RTC_lock = 0;       /* RTC lock */
+static int RTC_timer_lock = 0; /* RTC int lock */
+
+comedi_driver driver_pcl816 = {
+  driver_name: "pcl816",
+  module:       THIS_MODULE,
+  attach:       pcl816_attach,
+  detach:       pcl816_detach,
+  board_name:  boardtypes,
+  num_names:   n_boardtypes,
+  offset:              sizeof(boardtype),
+};
+COMEDI_INITCLEANUP(driver_pcl816);
+
+
+typedef struct
+{
+  int dma;                     // used DMA, 0=don't use DMA
+  int dma_rtc;                 // 1=RTC used with DMA, 0=no RTC alloc
+  unsigned int rtc_iobase;     // RTC port region
+  unsigned int rtc_iosize;
+  unsigned int rtc_irq;
+  unsigned long dmabuf[2];     // pointers to begin of DMA buffers
+  unsigned int dmapages[2];    // len of DMA buffers in PAGE_SIZEs
+  unsigned int hwdmaptr[2];    // hardware address of DMA buffers
+  unsigned int hwdmasize[2];   // len of DMA buffers in Bytes
+  unsigned int dmasamplsize;   // size in samples hwdmasize[0]/2
+  unsigned int last_top_dma;   // DMA pointer in last RTC int
+  int next_dma_buf;            // which DMA buffer will be used next round
+  long dma_runs_to_end;                // how many we must permorm DMA transfer to end of record
+  unsigned long last_dma_run;  // how many bytes we must transfer on last DMA page
+
+  unsigned int ai_scans;               // len of scanlist
+  unsigned char ai_neverending;        // if=1, then we do neverending record (you must use cancel())
+  int irq_free;                        // 1=have allocated IRQ
+  int irq_blocked;             // 1=IRQ now uses any subdev
+  int rtc_irq_blocked;         // 1=we now do AI with DMA&RTC
+  int irq_was_now_closed;      // when IRQ finish, there's stored int816_mode for last interrupt
+  int int816_mode;             // who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma 
+  comedi_subdevice *last_int_sub;      // ptr to subdevice which now finish
+  int ai_act_scan;             // how many scans we finished
+  unsigned int ai_act_chanlist[16];    // MUX setting for actual AI operations
+  unsigned int ai_act_chanlist_len;    // how long is actual MUX list
+  unsigned int ai_act_chanlist_pos;    // actual position in MUX list
+  unsigned int ai_poll_ptr;            // how many sampes transfer poll
+  unsigned int buf_ptr;                // data buffer ptr in samples
+  comedi_subdevice *sub_ai;    // ptr to AI subdevice
+  struct timer_list rtc_irq_timer;     // timer for RTC sanity check
+  unsigned long rtc_freq;      // RTC int freq
+} pcl816_private;
+
+
+
+
+/* 
+==============================================================================
+*/
+static int check_and_setup_channel_list (comedi_device * dev, comedi_subdevice * s, unsigned int *chanlist, int chanlen);
+static int pcl816_ai_cancel (comedi_device * dev, comedi_subdevice * s);
+static void start_pacer (comedi_device * dev, int mode, unsigned int divisor1,
+                 unsigned int divisor2);
+static int set_rtc_irq_bit (unsigned char bit);
+
+static int pcl816_ai_cmdtest(comedi_device *dev, comedi_subdevice *s, comedi_cmd *cmd);
+static int pcl816_ai_cmd(comedi_device *dev, comedi_subdevice *s);
+
+
+/* 
+==============================================================================
+   ANALOG INPUT MODE0, 816 cards, slow version
+*/
+static int pcl816_ai_insn_read(comedi_device *dev, comedi_subdevice *s,
+       comedi_insn *insn, lsampl_t *data)
+{
+       int n;
+       int timeout;
+
+       DPRINTK("mode 0 analog input\n");
+
+       for(n=0;n<insn->n;n++){
+
+               // software trigger, DMA and INT off 
+               outb (0, dev->iobase + PCL816_CONTROL); 
+               // clear INT (conversion end) flag 
+               outb (0, dev->iobase + PCL816_CLRINT);  
+               
+               // Set the input channel
+               outb (CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX); 
+           outb (CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE);        /* select gain */
+
+               udelay (5);
+               outb (0, dev->iobase + PCL816_AD_LO);   /* start conversion */
+
+               timeout=100;
+               while (timeout--) {
+               if (!(inb (dev->iobase + PCL816_STATUS) & PCL816_STATUS_DRDY_MASK)) {
+                       // return read value            
+                               data[n] =
+                               ((inb (dev->iobase + PCL816_AD_HI) << 8 ) |
+                                       (inb (dev->iobase + PCL816_AD_LO)));
+
+                               outb (0, dev->iobase + PCL816_CLRINT);  /* clear INT (conversion end) flag */
+                               break;
+                       }
+                       udelay (1);
+               }
+               // Return timeout error
+               if(!timeout) {
+                       comedi_error (dev, "A/D insn timeout\n");
+                       data[0] = 0;
+                       outb (0, dev->iobase + PCL816_CLRINT);  /* clear INT (conversion end) flag */
+                       return -EIO;
+               }
+       
+       }
+       return n;
+}
+
+/*
+==============================================================================
+   analog input interrupt mode 1 & 3, 818 cards
+   one sample per interrupt version   
+*/
+static void
+interrupt_pcl816_ai_mode13_int (int irq, void *d, struct pt_regs *regs)
+{
+  comedi_device *dev = d;
+  comedi_subdevice *s = dev->subdevices + 0;
+  int low, hi;
+  int timeout = 50;            /* wait max 50us */
+
+  while (timeout--) {
+         if (!(inb (dev->iobase + PCL816_STATUS) & PCL816_STATUS_DRDY_MASK)) 
+               break;
+      udelay (1);
+  }
+  if(!timeout) {                       // timeout, bail error
+       outb (0, dev->iobase + PCL816_CLRINT);  /* clear INT request */
+       comedi_error (dev, "A/D mode1/3 IRQ without DRDY!");
+       pcl816_ai_cancel (dev, s);
+       s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+       comedi_event(dev, s, s->async->events);
+       return;
+
+  }
+
+
+  // get the sample
+  low = inb (dev->iobase + PCL816_AD_LO);
+  hi = inb (dev->iobase + PCL816_AD_HI);
+  ((sampl_t *)s->async->data)[devpriv->buf_ptr++] = (hi << 8)|low;
+
+  outb (0, dev->iobase + PCL816_CLRINT);       /* clear INT request */
+
+  s->async->buf_int_ptr+=sizeof(sampl_t);
+  s->async->buf_int_count+=sizeof(sampl_t);
+
+  if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len)
+    devpriv->ai_act_chanlist_pos = 0;
+
+   s->async->buf_int_ptr+=sizeof(sampl_t);
+   s->async->buf_int_count+=sizeof(sampl_t);
+   s->async->cur_chan++;
+
+   if (s->async->cur_chan>=s->async->cmd.chanlist_len){
+               s->async->cur_chan=0;
+               s->async->events |= COMEDI_CB_BLOCK;
+               devpriv->ai_act_scan++;
+   }
+
+   if (s->async->buf_int_ptr >= s->async->data_len) {  /* buffer rollover */
+               s->async->buf_int_ptr=0;
+               devpriv->buf_ptr=0;
+               //printk("B ");
+               s->async->events |= COMEDI_CB_EOBUF;
+    }
+
+  if (!devpriv->ai_neverending)
+       if (devpriv->ai_act_scan >= devpriv->ai_scans) {        /* all data sampled */
+               /* all data sampled */
+               pcl816_ai_cancel (dev, s);
+               s->async->events |= COMEDI_CB_EOA;
+               return;
+       }
+       comedi_event(dev, s, s->async->events);
+}
+
+/*
+==============================================================================
+   analog input dma mode 1 & 3, 816 cards
+*/
+static void transfer_from_dma_buf(comedi_device *dev,comedi_subdevice *s,
+       sampl_t *ptr, unsigned int bufptr, unsigned int len)
+{
+       int i;
+       
+       s->async->events = 0;
+
+       for (i = 0; i < len; i++)  {
+
+           ((sampl_t*)s->async->data)[devpriv->buf_ptr++] = ptr[bufptr++];
+               s->async->buf_int_ptr+=sizeof(sampl_t);
+               s->async->buf_int_count+=sizeof(sampl_t);
+
+
+               if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) {
+               devpriv->ai_act_chanlist_pos = 0;
+                   devpriv->ai_act_scan++;
+               }
+
+               if (s->async->buf_int_ptr >= s->async->data_len) {              // buffer rollover 
+                       devpriv->buf_ptr=0;
+               s->async->buf_int_ptr=0;
+                       s->async->events |= COMEDI_CB_EOBUF;
+                       comedi_event(dev,s,s->async->events);
+                       s->async->events = 0;
+               }
+
+               if (!devpriv->ai_neverending)
+                       if (devpriv->ai_act_scan >= devpriv->ai_scans) {        // all data sampled 
+                               pcl816_ai_cancel(dev,s);
+                               s->async->events |= COMEDI_CB_EOA;
+                               s->async->events |= COMEDI_CB_BLOCK;
+                               comedi_event(dev, s, s->async->events);
+                               return;
+                       }
+       }
+
+       if (len > 0) {
+               comedi_event(dev, s, s->async->events);
+       }
+}
+
+
+static void interrupt_pcl816_ai_mode13_dma(int irq, void *d, struct pt_regs *regs)
+{
+  comedi_device *dev = d;
+  comedi_subdevice *s = dev->subdevices + 0;
+  int  len, bufptr, this_dma_buf;
+  unsigned long dma_flags;
+  sampl_t *ptr;
+
+  disable_dma (devpriv->dma);
+  this_dma_buf = devpriv->next_dma_buf;
+
+  if((devpriv->dma_runs_to_end > -1) ||
+         devpriv->ai_neverending)   {                          // switch dma bufs
+
+         devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
+      set_dma_mode (devpriv->dma, DMA_MODE_READ);
+      dma_flags = claim_dma_lock ();
+//  clear_dma_ff (devpriv->dma);
+      set_dma_addr (devpriv->dma, devpriv->hwdmaptr[devpriv->next_dma_buf]);
+      if (devpriv->dma_runs_to_end)    {
+                 set_dma_count (devpriv->dma, devpriv->hwdmasize[devpriv->next_dma_buf]);
+         } else {
+                 set_dma_count (devpriv->dma, devpriv->last_dma_run);
+         }
+      release_dma_lock (dma_flags);
+      enable_dma (devpriv->dma);
+  }
+
+  devpriv->dma_runs_to_end--;
+  outb (0, dev->iobase + PCL816_CLRINT);       /* clear INT request */
+
+  ptr = (sampl_t *) devpriv->dmabuf[this_dma_buf];
+
+  len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr;
+  bufptr = devpriv->ai_poll_ptr;
+  devpriv->ai_poll_ptr = 0;
+
+  transfer_from_dma_buf(dev, s, ptr, bufptr, len);
+}
+
+
+/*
+==============================================================================
+    INT procedure
+*/
+static void
+interrupt_pcl816 (int irq, void *d, struct pt_regs *regs)
+{
+       comedi_device *dev = d;
+       DPRINTK("<I>");
+
+       if(!dev->attached) {
+               comedi_error(dev, "premature interrupt");
+               return;
+       }
+
+
+       switch (devpriv->int816_mode)  {
+    case INT_TYPE_AI1_DMA:
+    case INT_TYPE_AI3_DMA:
+               interrupt_pcl816_ai_mode13_dma (irq, d, regs);
+               return;
+    case INT_TYPE_AI1_INT:
+    case INT_TYPE_AI3_INT:
+               interrupt_pcl816_ai_mode13_int (irq, d, regs);
+               return;
+    }
+
+
+       outb (0, dev->iobase + PCL816_CLRINT);  /* clear INT request */
+       if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) |
+      (!devpriv->int816_mode))
+    {
+               if (devpriv->irq_was_now_closed)        {
+                       devpriv->irq_was_now_closed = 0;
+                       // comedi_error(dev,"last IRQ..");
+                       return;
+               }
+               comedi_error (dev, "bad IRQ!");
+               return;
+       }
+       comedi_error (dev, "IRQ from unknow source!");
+}
+
+
+/*
+==============================================================================
+   COMMAND MODE
+*/
+static void pcl816_cmdtest_out(int e,comedi_cmd *cmd) {
+       rt_printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n",e,cmd->start_src,cmd->scan_begin_src,cmd->convert_src);
+       rt_printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n",e,cmd->start_arg,cmd->scan_begin_arg,cmd->convert_arg);
+       rt_printk("pcl816 e=%d stopsrc=%x scanend=%x\n",e,cmd->stop_src,cmd->scan_end_src);
+       rt_printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n",e,cmd->stop_arg,cmd->scan_end_arg,cmd->chanlist_len);
+}
+
+/* 
+==============================================================================
+*/
+static int pcl816_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
+{
+       int err=0;
+       int tmp,divisor1,divisor2;
+
+DEBUG(
+       rt_printk("pcl816 pcl812_ai_cmdtest\n");
+       pcl816_cmdtest_out(-1, cmd);
+);
+
+       /* 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;
+       if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
+
+
+       if(!cmd->convert_src & (TRIG_EXT | TRIG_TIMER)) 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_NOW;
+               err++;
+       }
+
+       if(cmd->scan_begin_src!=TRIG_FOLLOW) {
+               cmd->scan_begin_src=TRIG_FOLLOW;
+               err++;
+       }
+
+       if(cmd->convert_src!=TRIG_EXT &&
+          cmd->convert_src!=TRIG_TIMER) {
+                       cmd->convert_src=TRIG_TIMER;
+                       err++;
+       }
+       
+
+       if(cmd->scan_end_src!=TRIG_COUNT) {
+               cmd->scan_end_src=TRIG_COUNT;
+               err++;
+       }
+
+       if(cmd->stop_src!=TRIG_NONE &&
+          cmd->stop_src!=TRIG_COUNT) 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!=0){
+               cmd->scan_begin_arg=0;
+               err++;
+       }
+       if(cmd->convert_src==TRIG_TIMER){
+               if(cmd->convert_arg<this_board->ai_ns_min){
+                       cmd->convert_arg=this_board->ai_ns_min;
+                       err++;
+               }
+       } else { /* TRIG_EXT */
+               if(cmd->convert_arg!=0){
+                       cmd->convert_arg=0;
+                       err++;
+               }
+       }
+
+       if(!cmd->chanlist_len){
+               cmd->chanlist_len=1;
+               err++;
+       }
+       if(cmd->chanlist_len > this_board->n_aichan){
+               cmd->chanlist_len=this_board->n_aichan;
+               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;
+               i8253_cascade_ns_to_timer (this_board->i8254_osc_base,
+                       &divisor1, &divisor2, &cmd->convert_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               if(cmd->convert_arg<this_board->ai_ns_min)
+                       cmd->convert_arg=this_board->ai_ns_min;
+               if(tmp!=cmd->convert_arg)err++;
+       }
+
+       if(err) {
+               return 4;
+       }
+
+       return 0;
+}
+
+static int pcl816_ai_cmd(comedi_device *dev,comedi_subdevice *s)
+{
+       unsigned int    divisor1, divisor2, dma_flags, bytes, dmairq;
+       comedi_cmd      *cmd=&s->async->cmd;
+       
+
+       if(cmd->start_src!=TRIG_NOW) return -EINVAL;
+       if(cmd->scan_begin_src!=TRIG_FOLLOW) return -EINVAL;
+       if(cmd->scan_end_src!=TRIG_COUNT) return -EINVAL;
+       if(cmd->scan_end_arg!=cmd->chanlist_len) return -EINVAL;
+//     if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL;
+    if (devpriv->irq_blocked)
+       return -EBUSY;
+
+       if (cmd->convert_src==TRIG_TIMER) {
+               if(cmd->convert_arg < this_board->ai_ns_min) 
+                       cmd->convert_arg=this_board->ai_ns_min;
+
+            i8253_cascade_ns_to_timer (this_board->i8254_osc_base, &divisor1,
+                                &divisor2, &cmd->convert_arg, cmd->flags&TRIG_ROUND_MASK);
+            if (divisor1 == 1) {                       // PCL816 crash if any divisor is set to 1 
+                         divisor1 = 2;
+                         divisor2 /= 2;
+                }
+                if (divisor2 == 1) {
+                  divisor2 = 2;
+                  divisor1 /= 2;
+                }
+       }
+
+       start_pacer(dev, -1, 0, 0); // stop pacer
+
+       if (!check_and_setup_channel_list (dev, s, cmd->chanlist, cmd->chanlist_len))
+           return -EINVAL;
+       udelay (1);
+
+               
+    devpriv->ai_act_scan=0;
+    s->async->cur_chan=0;
+       devpriv->irq_blocked = 1;
+       devpriv->ai_poll_ptr=0;
+       devpriv->irq_was_now_closed = 0;
+       devpriv->buf_ptr = 0;
+       
+       if (cmd->stop_src==TRIG_COUNT) { 
+               devpriv->ai_scans = cmd->stop_arg; 
+               devpriv->ai_neverending = 0; 
+       } else { 
+               devpriv->ai_scans = 0; 
+               devpriv->ai_neverending = 1; 
+       }
+
+
+       if ((cmd->flags & TRIG_WAKE_EOS)) {     // don't we want wake up every scan?            
+               printk("pl816: You wankt WAKE_EOS but I dont want handle it");
+               //              devpriv->ai_eos=1;
+               //if (devpriv->ai_n_chan==1)
+               //      devpriv->dma=0; // DMA is useless for this situation
+       }
+
+       if (devpriv->dma) {     
+           bytes = devpriv->hwdmasize[0];
+               if (!devpriv->ai_neverending) {
+             bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof (sampl_t);       // how many 
+         devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0];     // how many DMA pages we must fill
+                 devpriv->last_dma_run = bytes % devpriv->hwdmasize[0];        //on last dma transfer must be moved
+             devpriv->dma_runs_to_end--;
+         if (devpriv->dma_runs_to_end >= 0)
+                       bytes = devpriv->hwdmasize[0];
+               } else
+                       devpriv->dma_runs_to_end = -1;
+
+               devpriv->next_dma_buf = 0;
+               set_dma_mode (devpriv->dma, DMA_MODE_READ);
+               dma_flags = claim_dma_lock ();
+               clear_dma_ff (devpriv->dma);
+               set_dma_addr (devpriv->dma, devpriv->hwdmaptr[0]);
+               set_dma_count (devpriv->dma, bytes);
+               release_dma_lock (dma_flags);
+               enable_dma (devpriv->dma);
+       }
+       
+
+       start_pacer(dev, 1, divisor1, divisor2);
+       dmairq = ((devpriv->dma & 0x3)<<4) | (dev->irq & 0x7);
+
+       switch (cmd->convert_src) {
+       case TRIG_TIMER:
+        devpriv->int816_mode = INT_TYPE_AI1_DMA;
+        outb (0x32, dev->iobase + PCL816_CONTROL);  // Pacer+IRQ+DMA 
+           outb(dmairq, dev->iobase + PCL816_STATUS);  // write irq and DMA to card
+               break;
+
+       default:
+               devpriv->int816_mode = INT_TYPE_AI3_DMA;
+               outb (0x34, dev->iobase + PCL816_CONTROL);      // Ext trig+IRQ+DMA
+               outb(dmairq, dev->iobase + PCL816_STATUS); // write irq to card
+               break;
+       }
+
+
+    DPRINTK("pcl816 END: pcl812_ai_cmd()\n");
+    return 0;
+}
+
+
+static int pcl816_ai_poll(comedi_device *dev,comedi_subdevice *s)
+{
+       unsigned long flags;
+    unsigned int top1,top2,i;
+
+       if (!devpriv->dma) return 0; // poll is valid only for DMA transfer
+       
+       comedi_spin_lock_irqsave(&dev->spinlock, flags);
+
+       for (i=0; i<20; i++) {
+               top1 = get_dma_residue(devpriv->dma); // where is now DMA
+               top2 = get_dma_residue(devpriv->dma);
+               if (top1 == top2) break;
+       }
+       if (top1 != top2) {
+               comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+               return 0;
+       }
+
+       top1 = devpriv->hwdmasize [0] - top1; // where is now DMA in buffer
+       top1 >>= 1; // sample position
+       top2 = top1 - devpriv->ai_poll_ptr;
+       if (top2 < 1) { // no new samples
+               comedi_spin_unlock_irqrestore(&dev->spinlock,flags);
+               return 0;
+       }
+
+       transfer_from_dma_buf(dev, s, (sampl_t*)devpriv->dmabuf[devpriv->next_dma_buf],
+                               devpriv->ai_poll_ptr, top2);
+       
+       devpriv->ai_poll_ptr = top1; // new buffer position
+       comedi_spin_unlock_irqrestore(&dev->spinlock,flags);
+
+       return s->async->buf_int_count - s->async->buf_user_count;
+}
+
+
+/* 
+==============================================================================
+ cancel any mode 1-4 AI
+*/
+int
+pcl816_ai_cancel (comedi_device * dev, comedi_subdevice * s)
+{
+//  DEBUG(rt_printk("pcl816_ai_cancel()\n");)
+  
+  if (devpriv->irq_blocked > 0) {
+  switch (devpriv->int816_mode)        {
+       case INT_TYPE_AI1_DMA_RTC:
+       case INT_TYPE_AI3_DMA_RTC:
+         set_rtc_irq_bit (0);  // stop RTC
+         del_timer (&devpriv->rtc_irq_timer);
+       case INT_TYPE_AI1_DMA:
+       case INT_TYPE_AI3_DMA:
+         disable_dma (devpriv->dma);
+       case INT_TYPE_AI1_INT:
+       case INT_TYPE_AI3_INT:
+         outb (inb (dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL);       /* Stop A/D */
+         udelay (1);
+         outb (0, dev->iobase + PCL816_CONTROL);       /* Stop A/D */
+         outb (0xb0, dev->iobase + PCL816_CTRCTL);     /* Stop pacer */
+         outb (0x70, dev->iobase + PCL816_CTRCTL);
+         outb (0, dev->iobase + PCL816_AD_LO);
+         inb (dev->iobase + PCL816_AD_LO);
+         inb (dev->iobase + PCL816_AD_HI);
+         outb (0, dev->iobase + PCL816_CLRINT);        /* clear INT request */
+         outb (0, dev->iobase + PCL816_CONTROL);       /* Stop A/D */
+         devpriv->irq_blocked = 0;
+         devpriv->irq_was_now_closed = devpriv->int816_mode;
+         devpriv->int816_mode = 0;
+         devpriv->last_int_sub = s;
+//       s->busy = 0;
+         break;
+       }
+       }
+    
+       DEBUG(rt_printk("comedi: pcl816_ai_cancel() successful\n");)
+       return 0;
+}
+
+/* 
+==============================================================================
+ chech for PCL816
+*/
+int pcl816_check (int iobase)
+{
+  outb (0x00, iobase + PCL816_MUX);
+  udelay (1);
+  if (inb (iobase + PCL816_MUX) != 0x00)
+    return 1;                  //there isn't card
+  outb (0x55, iobase + PCL816_MUX);
+  udelay (1);
+  if (inb (iobase + PCL816_MUX) != 0x55)
+    return 1;                  //there isn't card
+  outb (0x00, iobase + PCL816_MUX);
+  udelay (1);
+  outb (0x18, iobase + PCL816_CONTROL);
+  udelay (1);
+  if (inb (iobase + PCL816_CONTROL) != 0x18)
+    return 1;                  //there isn't card
+  return 0;                    // ok, card exist
+}
+
+/* 
+==============================================================================
+ reset whole PCL-816 cards
+*/
+void
+pcl816_reset (comedi_device * dev)
+{
+//  outb (0, dev->iobase + PCL818_DA_LO);      // DAC=0V
+//  outb (0, dev->iobase + PCL818_DA_HI);
+//  udelay (1);
+//  outb (0, dev->iobase + PCL818_DO_HI);      // DO=$0000
+//  outb (0, dev->iobase + PCL818_DO_LO);
+//  udelay (1);
+  outb (0, dev->iobase + PCL816_CONTROL);
+  outb (0, dev->iobase + PCL816_MUX);
+  outb (0, dev->iobase + PCL816_CLRINT);
+  outb (0xb0, dev->iobase + PCL816_CTRCTL);    /* Stop pacer */
+  outb (0x70, dev->iobase + PCL816_CTRCTL);
+  outb (0x30, dev->iobase + PCL816_CTRCTL);
+  outb (0, dev->iobase + PCL816_RANGE);
+}
+
+
+/*
+==============================================================================
+ Start/stop pacer onboard pacer
+*/
+static void
+start_pacer (comedi_device * dev, int mode, unsigned int divisor1,
+            unsigned int divisor2)
+{
+       outb (0x32, dev->iobase + PCL816_CTRCTL);
+       outb (0xff, dev->iobase + PCL816_CTR0);
+       outb (0x00, dev->iobase + PCL816_CTR0);
+       udelay (1);
+    outb(0xb4, dev->iobase + PCL816_CTRCTL);   // set counter 2 as mode 3
+    outb(0x74, dev->iobase + PCL816_CTRCTL);   // set counter 1 as mode 3
+    udelay(1);
+
+       if (mode == 1) {
+           DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1, divisor2);
+               outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2);
+               outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2);
+               outb(divisor1  & 0xff, dev->iobase + PCL816_CTR1);
+               outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1);
+    }
+       
+       /* clear pending interrupts (just in case) */
+//     outb(0, dev->iobase + PCL816_CLRINT);
+}
+
+/*
+==============================================================================
+ Check if channel list from user is builded correctly 
+ If it's ok, then program scan/gain logic
+*/
+static int
+check_and_setup_channel_list (comedi_device * dev, comedi_subdevice * s, unsigned int *chanlist, int chanlen)
+{
+  unsigned int chansegment[16];
+  unsigned int i, nowmustbechan, seglen, segpos;
+
+  // correct channel and range number check itself comedi/range.c 
+  if (chanlen < 1) {
+      comedi_error (dev, "range/channel list is empty!");
+      return 0;
+  }
+
+  if (chanlen > 1)
+    {
+      chansegment[0] = chanlist[0];    // first channel is everytime ok
+      for (i = 1, seglen = 1; i < chanlen; i++, seglen++)      {       
+                       // build part of chanlist
+         DEBUG(rt_printk("%d. %d %d\n",i,CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i]));)
+         if (chanlist[0] == chanlist[i])
+           break;              // we detect loop, this must by finish
+         nowmustbechan = (CR_CHAN (chansegment[i - 1]) + 1) % chanlen;
+         if (nowmustbechan != CR_CHAN (chanlist[i])) {
+               // channel list isn't continous :-(
+             rt_printk("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n",
+                dev->minor, i, CR_CHAN (chanlist[i]), nowmustbechan, CR_CHAN (chanlist[0]));
+             return 0;
+       }
+         chansegment[i] = chanlist[i]; // well, this is next correct channel in list
+       }
+
+    for (i = 0, segpos = 0; i < chanlen; i++)
+       {                       // check whole chanlist
+         DEBUG(rt_printk("%d %d=%d %d\n",CR_CHAN(chansegment[i%seglen]),CR_RANGE(chansegment[i%seglen]),CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i]));)
+         if (chanlist[i] != chansegment[i % seglen]) {
+             rt_printk("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
+                dev->minor, i, CR_CHAN (chansegment[i]),
+                CR_RANGE (chansegment[i]), CR_AREF (chansegment[i]),
+                CR_CHAN (chanlist[i % seglen]),
+                CR_RANGE(chanlist[i % seglen]),
+                CR_AREF (chansegment[i % seglen]));
+             return 0;         // chan/gain list is strange
+           }
+       }
+    }
+  else
+    {
+      seglen = 1;
+    }
+
+  devpriv->ai_act_chanlist_len = seglen;
+  devpriv->ai_act_chanlist_pos = 0;
+
+  for (i = 0; i < seglen; i++) {                               // store range list to card
+      devpriv->ai_act_chanlist[i] = CR_CHAN (chanlist[i]);
+         outb (CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX);  
+         outb (CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE);     /* select gain */
+  }
+
+  udelay (1);
+
+  outb (devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4),
+       dev->iobase + PCL816_MUX);      /* select channel interval to scan */
+
+  return 1;                    // we can serve this with MUX logic
+}
+
+/* 
+==============================================================================
+  Enable(1)/disable(0) periodic interrupts from RTC
+*/
+static int set_rtc_irq_bit(unsigned char bit)
+{
+        unsigned char val;
+        unsigned long flags;
+        if (bit==1) {
+               RTC_timer_lock++;
+               if (RTC_timer_lock>1) return 0;
+       } else {
+               RTC_timer_lock--;
+               if (RTC_timer_lock<0) RTC_timer_lock=0;
+               if (RTC_timer_lock>0) return 0;
+       }
+  
+        save_flags(flags);
+        cli();
+        val = CMOS_READ(RTC_CONTROL);
+        if (bit) { val |= RTC_PIE; }
+           else { val &=  ~RTC_PIE; }
+        CMOS_WRITE(val, RTC_CONTROL);
+        CMOS_READ(RTC_INTR_FLAGS);
+        restore_flags(flags);
+        return 0;
+}
+
+
+/* 
+==============================================================================
+  Free any resources that we have claimed  
+*/
+static void
+free_resources (comedi_device * dev)
+{
+  //rt_printk("free_resource()\n");
+  if (dev->private)
+    {
+      pcl816_ai_cancel (dev, devpriv->sub_ai);
+      pcl816_reset (dev);
+      if (devpriv->dma)
+       free_dma (devpriv->dma);
+      if (devpriv->dmabuf[0])
+       free_pages (devpriv->dmabuf[0], devpriv->dmapages[0]);
+      if (devpriv->dmabuf[1])
+       free_pages (devpriv->dmabuf[1], devpriv->dmapages[1]);
+      if (devpriv->rtc_irq)
+       free_irq (devpriv->rtc_irq, dev);
+      if ((devpriv->dma_rtc) && (RTC_lock == 1))
+       {
+         if (devpriv->rtc_iobase)
+           release_region (devpriv->rtc_iobase, devpriv->rtc_iosize);
+       }
+    }
+
+  if (dev->irq)
+    free_irq (dev->irq, dev);
+  if (dev->iobase)
+    release_region (dev->iobase, this_board->io_range);
+  //rt_printk("free_resource() end\n");
+}
+
+
+/* 
+==============================================================================
+
+   Initialization 
+
+*/
+static int
+pcl816_attach (comedi_device * dev, comedi_devconfig * it)
+{
+  int ret;
+  int iobase;
+  int irq, dma;
+  unsigned long pages;
+  int i;
+  comedi_subdevice *s;
+  int num_of_subdevs, subdevs[5];
+
+  /* claim our I/O space */
+  iobase = it->options[0];
+  printk("comedi%d: pcl816:  board=%s, ioport=0x%03x", dev->minor,
+               this_board->name, iobase);
+                
+  if (check_region (iobase, this_board->io_range) < 0) {
+      rt_printk ("I/O port conflict\n");
+      return -EIO;
+  }
+
+  request_region (iobase, this_board->io_range, "pcl816");
+  dev->iobase = iobase;
+
+  if (pcl816_check (iobase)) {
+     rt_printk (", I cann't detect board. FAIL!\n");
+     return -EIO;
+  }
+
+  if ((ret = alloc_private (dev, sizeof (pcl816_private))) < 0)
+    return ret;                        /* Can't alloc mem */
+
+  /* set up some name stuff */
+  dev->board_name = this_board->name;
+
+  /* grab our IRQ */
+  irq = 0;
+  if (this_board->IRQbits != 0) {      /* board support IRQ */
+               irq = it->options[1];
+      if (irq > 0)
+       {                       /* we want to use IRQ */
+         if (((1 << irq) & this_board->IRQbits) == 0)
+           {
+             rt_printk (", IRQ %d is out of allowed range, DISABLING IT", irq);
+             irq = 0;          /* Bad IRQ */
+           }
+         else
+           {
+             if (request_irq(irq, interrupt_pcl816, SA_INTERRUPT, "pcl816", dev)) {
+                        rt_printk (", unable to allocate IRQ %d, DISABLING IT",irq);
+                        irq = 0;       /* Can't use IRQ */
+                       }
+             else      {
+                 rt_printk (", irq=%d", irq);
+               }
+           }
+       }
+    }
+
+  dev->irq = irq;
+  if (irq)    {
+      devpriv->irq_free = 1;
+    }                          /* 1=we have allocated irq */
+  else    {
+      devpriv->irq_free = 0;
+    }
+  devpriv->irq_blocked = 0;    /* number of subdevice which use IRQ */
+  devpriv->int816_mode = 0;    /* mode of irq */
+
+  /* grab RTC for DMA operations */
+  devpriv->dma_rtc = 0;
+  if (it->options[2] > 0)
+    {                          // we want to use DMA
+      if (RTC_lock == 0)
+       {
+         if (check_region (RTC_PORT (0), RTC_IO_EXTENT) < 0)
+           goto no_rtc;
+         request_region (RTC_PORT (0), RTC_IO_EXTENT, "pcl816 (RTC)");
+       }
+      devpriv->rtc_iobase = RTC_PORT (0);
+      devpriv->rtc_iosize = RTC_IO_EXTENT;
+      RTC_lock++;
+#ifdef UNTESTED_CODE
+      if (!request_irq (RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc,
+          SA_INTERRUPT | SA_SHIRQ, "pcl816 DMA (RTC)", dev))   {
+                 devpriv->dma_rtc = 1;
+                 devpriv->rtc_irq = RTC_IRQ;
+                 rt_printk (", dma_irq=%d", devpriv->rtc_irq);
+         } else {
+                 RTC_lock--;
+                 if (RTC_lock == 0)
+               {
+                     if (devpriv->rtc_iobase)
+                       release_region (devpriv->rtc_iobase, devpriv->rtc_iosize);
+                   }
+                 devpriv->rtc_iobase = 0;
+                 devpriv->rtc_iosize = 0;
+         }
+#else
+       printk("pcl816: RTC code missing");
+#endif
+         
+    }
+
+no_rtc:
+  /* grab our DMA */
+  dma = 0;
+  devpriv->dma = dma;
+  if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0))
+    goto no_dma;               /* if we haven't IRQ, we can't use DMA */
+  
+  if (this_board->DMAbits != 0) {  /* board support DMA */
+      dma = it->options[2];
+      if (dma < 1)
+                       goto no_dma;            /* DMA disabled */
+                       
+      if (((1 << dma) & this_board->DMAbits) == 0)     {
+               rt_printk (", DMA is out of allowed range, FAIL!\n");
+               return -EINVAL; /* Bad DMA */
+         }
+      ret = request_dma (dma, "pcl816");
+      if (ret) {
+               rt_printk (", unable to allocate DMA %d, FAIL!\n", dma);
+               return -EBUSY;  /* DMA isn't free */
+         }
+      
+         devpriv->dma = dma;
+      rt_printk (", dma=%d", dma);
+      pages = 2;               /* we need 16KB */
+      devpriv->dmabuf[0] = __get_dma_pages (GFP_KERNEL, pages);
+      
+         if (!devpriv->dmabuf[0])      {
+                       rt_printk (", unable to allocate DMA buffer, FAIL!\n");
+                       /* maybe experiment with try_to_free_pages() will help .... */
+                       return -EBUSY;  /* no buffer :-( */
+         }
+      devpriv->dmapages[0] = pages;
+      devpriv->hwdmaptr[0] = virt_to_bus ((void *) devpriv->dmabuf[0]);
+      devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE;
+      //rt_printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE);
+
+      if (devpriv->dma_rtc == 0)       {                       // we must do duble buff :-(
+               devpriv->dmabuf[1] = __get_dma_pages (GFP_KERNEL, pages);
+               if (!devpriv->dmabuf[1]) {
+             rt_printk (", unable to allocate DMA buffer, FAIL!\n");
+             return -EBUSY;
+           }
+         devpriv->dmapages[1] = pages;
+         devpriv->hwdmaptr[1] = virt_to_bus ((void *) devpriv->dmabuf[1]);
+         devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE;
+       }
+    }
+
+no_dma:
+
+  num_of_subdevs = 0;
+
+  if (this_board->n_aichan > 0)
+    subdevs[num_of_subdevs++] = COMEDI_SUBD_AI;
+/*  if (this_board->n_aochan > 0)
+    subdevs[num_of_subdevs++] = COMEDI_SUBD_AO;
+  if (this_board->n_dichan > 0)
+    subdevs[num_of_subdevs++] = COMEDI_SUBD_DI;
+  if (this_board->n_dochan > 0)
+    subdevs[num_of_subdevs++] = COMEDI_SUBD_DO;
+*/
+  dev->n_subdevices = num_of_subdevs;
+  if ((ret = alloc_subdevices (dev)) < 0)
+    return ret;
+
+  s = dev->subdevices + 0;
+  for (i = 0; i < num_of_subdevs; i++) {
+      s->type = subdevs[i];
+      switch (s->type) {
+
+               case COMEDI_SUBD_AI:
+                       devpriv->sub_ai = s;
+                       dev->read_subdev = s;
+                       s->subdev_flags = SDF_READABLE | SDF_RT;
+                       s->n_chan = this_board->n_aichan;
+                       s->subdev_flags |= SDF_DIFF;
+                       rt_printk (", %dchans DIFF DAC - %d", s->n_chan, i);
+                       s->maxdata = this_board->ai_maxdata;
+                       s->len_chanlist = this_board->ai_chanlist;
+                       s->range_table = this_board->ai_range_type;
+                       s->cancel = pcl816_ai_cancel;
+                       s->do_cmdtest = pcl816_ai_cmdtest;
+                       s->do_cmd = pcl816_ai_cmd;
+                       s->poll = pcl816_ai_poll;
+                       s->insn_read = pcl816_ai_insn_read;
+                       break;
+
+       case COMEDI_SUBD_AO:
+         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_RT;
+         s->n_chan = this_board->n_aochan;
+         s->maxdata = this_board->ao_maxdata;
+         s->len_chanlist = this_board->ao_chanlist;
+         s->range_table = this_board->ao_range_type;
+         break;
+
+       case COMEDI_SUBD_DI:
+         s->subdev_flags = SDF_READABLE | SDF_RT;
+         s->n_chan = this_board->n_dichan;
+         s->maxdata = 1;
+         s->len_chanlist = this_board->n_dichan;
+         s->range_table = &range_digital;
+         break;
+
+       case COMEDI_SUBD_DO:
+         s->subdev_flags = SDF_WRITEABLE | SDF_RT;
+         s->n_chan = this_board->n_dochan;
+         s->maxdata = 1;
+         s->len_chanlist = this_board->n_dochan;
+         s->range_table = &range_digital;
+         break;
+       }
+      s++;
+    }
+
+
+
+  pcl816_reset (dev);
+
+  rt_printk ("\n");
+
+  return 0;
+}
+
+
+/*
+==============================================================================
+  Removes device
+ */
+static int
+pcl816_detach (comedi_device * dev)
+{
+  DEBUG(rt_printk("comedi%d: pcl816: remove\n", dev->minor);)
+  free_resources (dev);
+  if (devpriv->dma_rtc)
+    RTC_lock--;
+  return 0;
+}