Added pcl812 driver from Michal Dobes <majkl@tesnet.cz>
authorDavid Schleef <ds@schleef.org>
Thu, 2 Mar 2000 08:11:29 +0000 (08:11 +0000)
committerDavid Schleef <ds@schleef.org>
Thu, 2 Mar 2000 08:11:29 +0000 (08:11 +0000)
Documentation/comedi/drivers.txt
comedi/Config.in
comedi/comedi_module.h
comedi/drivers.c
comedi/drivers/Makefile
comedi/drivers/pcl812.c [new file with mode: 0644]

index 3cd2fac50c4b4002f4e8a7b7d14e4ec8eb05acb3..8e84a5d7d02d3cf6dd8888e6748ab47bd738138c 100644 (file)
@@ -401,5 +401,39 @@ Author: Anders Blomdell <anders.blomdell@control.lth.se>
 Status: works
 
 
+pcl812.o: Advantech PCL-812PG and Advantech PCL-813B
+
+Author: Michal Dobes <majkl@tesnet.cz>
+Status: works (I hope. My board fire up under my hands 
+               and I haven't time to finish tests.)
+
+ Card: Advantech PCL-812PG
+  This card have integrated 16SE A/D 30kHz, 2 D/A, 16DI, 16DO.
+  Driver support mode0, 1 and 3 for A/D and mode0 for others.
+  If you disable IRQ then only mode0 for A/D is allowed.
+  
+ Driver name: pcl812pg
+ Options:
+    [0] - IO Base
+    [1] - IRQ  (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+    [2] - 0=trigger source is internal 8253 with 2MHz clock
+          1=trigger source is external 
+    [3] - 0=A/D have max +/-5V input
+          1=A/D have max +/-10V input
+    [4] - 0=D/A outputs 0-5V  (internal reference -5V)
+          1=D/A outputs 0-10V (internal reference -10V)
+         2=D/A outputs unknow (external reference)
+
+ Card: Advantech PCL-813B
+  This card have integrated 32SE A/D 25kHz.
+  Driver support only mode0 A/D becouse construction of this card 
+  don't allow anything else.
+  
+ Driver name: pcl813b
+ Options:
+    [0] - IO Base
+    [1] - 0= bipolar inputs
+          1= unipolar inputs
+    [2] - max number of samples in ai_mode0 (defaul=1scan)
 
 
index 9bde2ee8d7bc6cb2911504b88bd96b118261d3f4..7a35ca85a3b55582b8d3876606c9b03a18062329 100644 (file)
@@ -66,6 +66,7 @@ tristate 'Generic parallel port support' CONFIG_COMEDI_PARPORT
 tristate 'PCL-711, PCL-711b, ACL-8112, and compatibles' CONFIG_COMEDI_PCL711
 tristate 'PCL-725' CONFIG_COMEDI_PCL725
 tristate 'PCL-726' CONFIG_COMEDI_PCL726
+tristate 'Advantech PCL-812PG, PCL-813B' CONFIG_COMEDI_PCL812
 tristate 'Analog Devices RTI-800/815' CONFIG_COMEDI_RTI800
 tristate 'Analog Devices RTI-802' CONFIG_COMEDI_RTI802
 tristate 'Intelligent Instrumentation PCI-20001C' CONFIG_COMEDI_II_PCI20KC
index e99ea8bf59cb5fe59e76153e5784990c199d67b4..4df8b65e982370d60eb09f885ab078e917b2f8f7 100644 (file)
@@ -273,6 +273,7 @@ struct comedi_lrange_struct{
 #define TIMER_atmio                    3
 #define TIMER_acl8112                  4
 #define TIMER_nanosec                  5
+#define TIMER_pcl812                   6       
 
 
 /* some silly little inline functions */
index d8b5aa7d85c006deeae5dde842171165592cc893..d3b9e2c1d46ad98d0c94f0f663792bd07489d45e 100644 (file)
@@ -401,6 +401,9 @@ void init_drivers(void)
 #ifdef CONFIG_COMEDI_DAS16
        REG(driver_das16);
 #endif
+#ifdef CONFIG_COMEDI_PCL812
+       REG(driver_pcl812);
+#endif
 #endif
 }
 
index a40f9950a8a27f8c5d2dd0bbfde5b8d1218106bc..6ac327fd333505866a61d979dab47c6eb5dbe500 100644 (file)
@@ -41,6 +41,7 @@ obj-$(CONFIG_COMEDI_MITE)             += mite.o
 obj-$(CONFIG_COMEDI_PCL711)            += pcl711.o
 obj-$(CONFIG_COMEDI_PCL725)            += pcl725.o
 obj-$(CONFIG_COMEDI_PCL726)            += pcl726.o
+obj-$(CONFIG_COMEDI_PCL812)            += pcl812.o
 
 obj-$(CONFIG_COMEDI_PARPORT)           += comedi_parport.o
 
diff --git a/comedi/drivers/pcl812.c b/comedi/drivers/pcl812.c
new file mode 100644 (file)
index 0000000..b797504
--- /dev/null
@@ -0,0 +1,785 @@
+    /*
+   module/pcl812.c
+   hardware driver for Advantech cards
+    card:   PCL-812PG, PCL-813B
+    driver: pcl812pg,  pcl813b
+    
+   Michal Dobes <majkl@tesnet.cz>  
+   Based on 711.c 
+   
+   Options for PCL-812PG:
+    [0] - IO Base
+    [1] - IRQ  (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+    [2] - 0=trigger source is internal 8253 with 2MHz clock
+          1=trigger source is external 
+    [3] - 0=A/D have max +/-5V input
+          1=A/D have max +/-10V input
+    [4] - 0=D/A outputs 0-5V  (internal reference -5V)
+          1=D/A outputs 0-10V (internal reference -10V)
+         2=D/A outputs unknow (external reference)
+
+  Options for PCL-813B:
+    [0] - IO Base
+    [1] - 0= bipolar inputs
+          1= unipolar inputs
+    [2] - max number of samples in ai_mode0 (defaul=1scan)
+
+       
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <comedi_module.h>
+
+/* #define MD_DEBUG */
+
+#define boardPCL812PG 0
+#define boardPCL813B 1
+
+#define PCLx1x_RANGE 16
+
+#define PCL812_CLRINT 8
+#define PCL812_GAIN 9
+#define PCL812_MUX 10
+#define PCL812_MODE 11
+#define PCL812_CNTENABLE 10
+#define PCL812_SOFTTRIG 12
+#define PCL812_CTR0 0
+#define PCL812_CTR1 1
+#define PCL812_CTR2 2
+#define PCL812_CTRCTL 3
+
+#define PCL812_AD_LO 4
+#define PCL812_AD_HI 5
+#define PCL812_DA1_LO 4
+#define PCL812_DA1_HI 5
+#define PCL812_DA2_LO 6
+#define PCL812_DA2_HI 7
+#define PCL812_DI_LO 6
+#define PCL812_DI_HI 7
+#define PCL812_DO_LO 13
+#define PCL812_DO_HI 14
+
+#define PCL812_DRDY 0x10
+
+/*
+  For PCL-813B: 
+  I don't know if timeouts which are specified at a documentation 
+  are miliseconds or microseconds. If your card don't work properly then
+  undef next #define.
+*/
+#define PCL813_MICROSECS
+
+#define INT_TYPE_AI1_INT 1
+/* #define INT_TYPE_AI1_DMA 2 */
+#define INT_TYPE_AI3_INT 3
+/* #define INT_TYPE_AI3_DMA 4 */
+
+/*
+--BEGIN-RANGE-DEFS--
+RANGE_pcl812pg_ai
+        -5      5
+        -2.5    2.5
+        -1.25   1.25
+        -0.625  0.625
+        -0.3125 0.3125
+RANGE_pcl812pg2_ai
+       -10     10
+        -5      5
+        -2.5    2.5
+        -1.25   1.25
+        -0.625  0.625
+RANGE_pcl813b_ai
+        -5      5
+        -2.5    2.5
+        -1.25   1.25
+        -0.625  0.625
+RANGE_pcl813b2_ai
+       0       10
+        0      5
+        0      2.5
+        0      1.25
+---END-RANGE-DEFS---
+*/
+
+static int pcl812_attach(comedi_device *dev,comedi_devconfig *it);
+static int pcl812_detach(comedi_device *dev);
+static int pcl812_recognize(char *name);
+
+comedi_driver driver_pcl812={
+       driver_name:    "pcl812",
+       module:         &__this_module,
+       attach:         pcl812_attach,
+       detach:         pcl812_detach,
+       recognize:      pcl812_recognize,
+};
+
+typedef struct {
+       char *name;
+       int n_ranges;
+       int n_aichan;
+       int ai_maxsample;
+       int n_aochan;
+       int n_dichan;
+       int n_dochan;
+       int ai_range_type;
+       int ai_timer_type;
+       int ao_range_type;
+       int io_range;
+       unsigned int IRQbits;
+/*     unsigned int DMAbits; */
+/*     void *ai_mode[4];*/
+       
+} boardtype;
+
+static boardtype boardtypes[] =
+{
+       {"pcl812pg", 5, 16,  30, 2, 16, 16, RANGE_pcl812pg_ai, TIMER_pcl812, RANGE_unipolar5, PCLx1x_RANGE, 0xdcfc/*, 0x00 */},
+       {"pcl813b",  4, 32,  25, 0,  0,  0, RANGE_pcl813b_ai,             0,               0, PCLx1x_RANGE, 0x0000/*, 0x00 */}
+};
+
+#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
+
+typedef struct {
+/*     int dma;
+       unsigned long dmabuf[2];
+       unsigned int dmapages[2];
+       unsigned int hwdmaptr[2];
+       unsigned int hwdmasize[2];
+       int next_dma_buf;
+       unsigned long dma_runs_to_end; */
+       int irq_free;
+       int irq_blocked;
+       int irq_was_now_closed;
+       int max_812_ai_mode0_samples;
+       int max_812_ai_mode0_rangewait;
+       int max_812_ai_mode0_chanset;
+       int max_812_ai_mode0_convstart;
+       int int812_mode; /*1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */
+       //int int13_act_ptr;
+       int int13_act_scan;
+       int int13_act_chan;
+} pcl812_private;
+
+#define devpriv ((pcl812_private *)dev->private)
+#define this_board (boardtypes+dev->board)
+
+/* 
+==============================================================================
+   ANALOG INPUT MODE0, 812pg and 813b card
+*/
+static int pcl812_ai_mode0(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+ int nmax;
+ int i, n, p;
+ int timeout, hi; 
+  
+  nmax=devpriv->max_812_ai_mode0_samples; /* block for max cca 1ms  (812) */
+  
+  if ((it->n*it->n_chan)<=nmax) nmax=it->n*it->n_chan;
+
+  nmax=nmax/it->n_chan;
+  if (!nmax) nmax++;
+  
+  outb(1, dev->iobase+PCL812_MODE); /* select software trigger */
+  
+  p=0; /* ptr to buff */
+  
+  for (n=0; n<nmax; n++) {
+    for (i=0; i<it->n_chan; i++) {      
+      outb(CR_RANGE(it->chanlist[i]), dev->iobase+PCL812_GAIN); /* select gain */
+      udelay(devpriv->max_812_ai_mode0_rangewait); 
+      outb(CR_CHAN(it->chanlist[i]), dev->iobase+PCL812_MUX); /* select channel */
+      udelay(devpriv->max_812_ai_mode0_chanset); 
+      outb(255, dev->iobase+PCL812_SOFTTRIG); /* start conversion */
+      udelay(devpriv->max_812_ai_mode0_convstart); 
+      timeout=20; /* wait max 100us, it must finish under 33us */
+      while (timeout--) {
+       hi=inb(dev->iobase + PCL812_AD_HI);
+       if (!(hi & PCL812_DRDY)) goto conv_finish;
+       udelay(5);
+       }
+      rt_printk("comedi%d: pcl812: (%s at 0x%x) A/D mode0 timeout\n", dev->minor, dev->board_name, dev->iobase);
+      it->data[p++]=0;
+      outb(0, dev->iobase+PCL812_MODE);
+      return -ETIME;
+
+conv_finish:
+      it->data[p++] = ((hi & 0xf) << 8) | inb(dev->iobase + PCL812_AD_LO);;
+     }
+   }
+
+  outb(0, dev->iobase+PCL812_MODE);
+  return p;
+}
+
+/* 
+==============================================================================
+   ANALOG OUTPUT MODE0, 812pg card
+   only one sample per call is supported
+*/
+static int pcl812_ao_mode0(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+  int chan = CR_CHAN(it->chanlist[0]);
+  sampl_t data = it->data[0];
+
+  outb((data & 0xff), dev->iobase + (chan ? PCL812_DA2_LO : PCL812_DA1_LO));
+  outb(((data& 0xf) >> 8), dev->iobase + (chan ? PCL812_DA2_HI : PCL812_DA1_HI));
+
+  return 0;
+}
+
+/* 
+==============================================================================
+   DIGITAL INPUT MODE0, 812pg card
+   
+   only one sample per call is supported
+*/
+static int pcl812_di_mode0(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) { 
+  int data;
+  int chan;
+  int i;
+
+  data = inb(dev->iobase + PCL812_DI_LO) |
+         (inb(dev->iobase + PCL812_DI_HI) << 8);
+
+  for(i=0;i<it->n_chan;i++) {
+    chan=CR_CHAN(it->chanlist[i]);
+    it->data[i]=(data>>chan)&1;
+   }
+
+  return it->n_chan;
+}
+
+/* 
+==============================================================================
+   DIGITAL OUTPUT MODE0, 812pg card
+   
+   only one sample per call is supported
+*/
+static int pcl812_do_mode0(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+  int mask, data;
+  int chan;
+  int i;
+
+  data=s->state;
+  for(i=0;i<it->n_chan;i++) {
+    chan=CR_CHAN(it->chanlist[i]);
+    mask=(1<<chan);
+    data &= ~mask;
+    if(it->data[i])
+      data |= mask;
+   }
+  outb(data & 0xff, dev->iobase + PCL812_DO_LO);
+  outb((data >> 8), dev->iobase + PCL812_DO_HI);
+  s->state = data;
+
+  return it->n_chan;
+}
+
+/* 
+==============================================================================
+   analog input interrupt mode 1 & 3, 812pg card
+   one sample per interrupt version   
+*/
+static void interrupt_pcl812_ai_mode13_int(int irq, void *d, struct pt_regs *regs) {
+
+ int hi;
+ comedi_device *dev = d;
+ comedi_subdevice *s = dev->subdevices + 0;
+  
+  int timeout=20; /* wait max 100us, it must finish under 33us */
+  while (timeout--) {
+  hi=inb(dev->iobase + PCL812_AD_HI);
+  if (!(hi & PCL812_DRDY)) goto conv_finish;
+    udelay(5);
+   }
+  hi=inb(dev->iobase+PCL812_AD_LO);
+  outb(0,dev->iobase+PCL812_CLRINT); /* clear INT request */
+  rt_printk("comedi%d: pcl812: (%s at 0x%x) A/D mode1/3 IRQ without DRDY!\n", dev->minor, dev->board_name, dev->iobase);
+  return;
+
+conv_finish:
+  
+  s->cur_trig.data[s->buf_int_ptr++]=((hi<<8)|inb(dev->iobase+PCL812_AD_LO))&0xfff;
+
+  outb(0,dev->iobase+PCL812_CLRINT); /* clear INT request */
+
+  s->buf_int_count+=sizeof(sampl_t);
+  
+  if ((++devpriv->int13_act_chan)>=s->cur_trig.n_chan) { /* one scan done */
+    devpriv->int13_act_chan=0;
+    outb(CR_RANGE(s->cur_trig.chanlist[devpriv->int13_act_chan]), dev->iobase+PCL812_GAIN); /* select next gain */
+    outb(CR_CHAN(s->cur_trig.chanlist[devpriv->int13_act_chan]), dev->iobase+PCL812_MUX); /* select next channel */
+    if (s->cur_trig.flags & TRIG_WAKE_EOS) comedi_eos(dev,s);  
+    devpriv->int13_act_scan++;
+   } else {
+    outb(CR_RANGE(s->cur_trig.chanlist[devpriv->int13_act_chan]), dev->iobase+PCL812_GAIN); /* select next gain */
+    outb(CR_CHAN(s->cur_trig.chanlist[devpriv->int13_act_chan]), dev->iobase+PCL812_MUX); /* select next channel */
+   }
+
+  if (s->buf_int_ptr>=s->cur_trig.data_len) { /* buffer rollover */
+    s->buf_int_ptr=0;
+    //devpriv->int13_act_ptr=0;
+    comedi_eobuf(dev,s);    
+   }
+
+  if ( devpriv->int13_act_scan>=s->cur_trig.n ) { /* all data sampled */
+    outb(0, dev->iobase+PCL812_MODE); /* Stop A/D */
+    outb(0,dev->iobase+PCL812_CLRINT); /* clear INT request */
+    if (devpriv->int812_mode==1) {
+      /* Stop pacer */
+      outb(0xb4, dev->iobase + PCL812_CTRCTL);
+      outb(0x74, dev->iobase + PCL812_CTRCTL);
+     }
+    s->busy = 0;
+    devpriv->irq_blocked=0;
+    devpriv->int812_mode=0;
+    devpriv->irq_was_now_closed=1;
+     /* printk("comedi_done\n"); */
+    comedi_done(dev,s); 
+   }
+}
+
+
+/* 
+==============================================================================
+    INT procedure
+*/
+static void interrupt_pcl812(int irq, void *d, struct pt_regs *regs) {
+
+ comedi_device *dev = d;
+
+  if ((!dev->irq)|(!devpriv->irq_free)|(!devpriv->irq_blocked)|(!devpriv->int812_mode)) {
+    if (devpriv->irq_was_now_closed) {
+      devpriv->irq_was_now_closed=0;
+      outb(0,dev->iobase+PCL812_CLRINT); /* clear INT request */
+      rt_printk("comedi%d: pcl812: (%s at 0x%x) too much IRQs!\n", dev->minor, dev->board_name, dev->iobase);
+      return;
+     } 
+    rt_printk("comedi%d: pcl812: (%s at 0x%x) bad IRQ!\n", dev->minor, dev->board_name, dev->iobase);
+    return;
+   }
+
+  switch (devpriv->int812_mode) {
+    case INT_TYPE_AI1_INT:
+      interrupt_pcl812_ai_mode13_int(irq, d, regs);
+      return;
+    case INT_TYPE_AI3_INT:
+      interrupt_pcl812_ai_mode13_int(irq, d, regs);
+      return;
+/*    case INT_TYPE_AI1_DMA:
+      interrupt_pcl812_ai_mode13_dma(irq, d, regs);
+      return;
+    case INT_TYPE_AI3_DMA:
+      interrupt_pcl812_ai_mode13_dma(irq, d, regs);
+      return; */
+   }
+}
+
+/* 
+==============================================================================
+   ANALOG INPUT MODE 1, 812pg card
+   interrupt pacer pooling
+*/
+static int pcl812_ai_mode1_int(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+  /*
+   *  Set timers
+   *   timer chip is an 8253, with timers 1 and 2
+   *   cascaded
+   *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
+   *        Mode 2 = Rate generator
+   *
+   *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
+   */
+
+  outb(0x74, dev->iobase + PCL812_CTRCTL);
+  outb((it->trigvar >> 16) & 0xff, dev->iobase + PCL812_CTR1);
+  outb((it->trigvar >> 24) & 0xff, dev->iobase + PCL812_CTR1);
+  outb(0xb4, dev->iobase + PCL812_CTRCTL);
+  outb(it->trigvar & 0xff, dev->iobase + PCL812_CTR2);
+  outb((it->trigvar >> 8) & 0xff, dev->iobase + PCL812_CTR2);
+
+  /* clear pending interrupts (just in case) */
+  outb(0, dev->iobase + PCL812_CLRINT);
+
+  //devpriv->int13_act_ptr=0;
+  devpriv->int13_act_scan=0;
+  devpriv->int13_act_chan=0;
+  devpriv->int812_mode=INT_TYPE_AI1_INT; /* analog in, mode 0, int driven */
+  devpriv->irq_blocked=1;
+  devpriv->irq_was_now_closed=0;
+
+  outb(6, dev->iobase + PCL812_MODE); /* Pacer+IRQ */
+
+  return 0;
+}
+
+/* 
+==============================================================================
+   ANALOG INPUT MODE 1, 812pg card
+*/
+static int pcl812_ai_mode1(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+
+  if (!dev->irq)
+    return -EINVAL;
+  if (devpriv->irq_blocked)
+    return -EBUSY;
+  if (it->n_chan<0) return -1;
+
+//  if (devpriv->dma) { /* check if we can use DMA? */
+//    if (it->n_chan==1) {
+//      return pcl812_ai_mode1_dma(dev, s, it); /* we scanning only one chan, we can */
+//     } else  {
+//      fst=it->chanlist[0];
+//      for (i=1; i<it->n_chan; i++)
+//        if (fst!=it->chanlist[0]) { i=-1; break; }
+//      if (i==-1) { return pcl812_ai_mode1_int(dev, s, it); }
+//            else { return pcl812_ai_mode1_dma(dev, s, it); }
+//     }
+// }
+  return pcl812_ai_mode1_int(dev, s, it); /* no, we can only int driven */
+}
+
+/* 
+==============================================================================
+   ANALOG INPUT MODE 3, 812pg card
+*/
+static int pcl812_ai_mode3_int(comedi_device * dev, comedi_subdevice * s, comedi_trig * it) {
+
+  if (!dev->irq)
+    return -EINVAL;
+  if (devpriv->irq_blocked)
+    return -EBUSY;
+
+  /* clear pending interrupts (just in case) */
+  outb(0, dev->iobase + PCL812_CLRINT);
+
+  //devpriv->int13_act_ptr=0;
+  devpriv->int13_act_scan=0;
+  devpriv->int13_act_chan=0;
+  devpriv->int812_mode=3; /* analog in, mode 3, int driven */
+  devpriv->irq_blocked=1;
+
+  outb(6, dev->iobase + PCL812_MODE); /* external trigger+IRQ */
+
+  return 0;
+}
+
+/* 
+==============================================================================
+  Free any resources that we have claimed  
+*/
+static void free_resources(comedi_device * dev) {
+
+  if (dev->irq) free_irq(dev->irq, dev);
+  if (dev->iobase) release_region(dev->iobase, dev->iosize);
+/*  if(dev->private) {
+    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->dma) free_dma(devpriv->dma);
+   } */
+}
+
+/* 
+==============================================================================
+ reset whole PCL-812 or PCL-813 
+*/
+static void pcl812_reset(comedi_device * dev) {
+  if (dev->board==boardPCL812PG) {
+    outb(0, dev->iobase + PCL812_DA1_LO);
+    outb(0, dev->iobase + PCL812_DA1_HI);
+    outb(0, dev->iobase + PCL812_DA2_LO);
+    outb(0, dev->iobase + PCL812_DA2_HI);
+    outb(0, dev->iobase + PCL812_DO_HI);
+    outb(0, dev->iobase + PCL812_DO_LO);
+    outb(0, dev->iobase + PCL812_MODE);
+    outb(0, dev->iobase + PCL812_CLRINT);
+   }    
+  outb(0, dev->iobase + PCL812_GAIN);
+  outb(0, dev->iobase + PCL812_MUX);
+  udelay(5);
+  if (dev->board==boardPCL813B) {
+#ifdef PCL813_MICROSECS
+    udelay(5);
+#else
+    udelay(5000);
+#endif
+   }
+}
+
+
+/* 
+==============================================================================
+
+   Initialization 
+
+*/
+static int pcl812_attach(comedi_device * dev, comedi_devconfig * it) {
+
+ int ret;
+ int iobase;
+ int irq/*,dma*/;
+/* unsigned long pages;*/
+ int i;
+ int board;
+ comedi_subdevice *s;
+ int num_of_subdevs, subdevs[5];
+       
+  board = dev->board; /* inicialized from pcl812_recognize()? */
+
+  /* claim our I/O space */
+  iobase = it->options[0];
+  printk("comedi%d: pcl812:  board=%s, ioport=0x%03x", dev->minor, boardtypes[board].name, iobase);
+  if (check_region(iobase, boardtypes[board].io_range) < 0) {
+    printk("I/O port conflict\n");
+    return -EIO;
+   }
+  request_region(dev->iobase, boardtypes[board].io_range, "pcl812");
+  dev->iobase=iobase;
+  dev->iosize=boardtypes[board].io_range;
+
+  /* there should be a sanity check here */
+
+  if((ret=alloc_private(dev,sizeof(pcl812_private)))<0)
+    return ret; /* Can't alloc mem */
+
+  /* set up some name stuff */
+  dev->board_name = boardtypes[board].name;
+
+  /* grab our IRQ */
+  irq=0;
+  if (boardtypes[board].IRQbits!=0) { /* board support IRQ */
+    irq=it->options[1];
+    if (irq)  {/* we want to use IRQ */
+      if (((1<<irq)&boardtypes[board].IRQbits)==0) {
+        printk(", IRQ %d is out of allowed range, DISABLING IT",irq);
+        irq=0; /* Bad IRQ */
+       } else { 
+        if (request_irq(irq, interrupt_pcl812, SA_INTERRUPT, "pcl812", dev)) {
+          printk(", unable to allocate IRQ %d, DISABLING IT", irq);
+          irq=0; /* Can't use IRQ */
+         } else {
+          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->int812_mode=0; /* mode of irq */
+       
+  /* grab our DMA */
+//  dma=0;
+//  devpriv->dma=dma;
+//  if (!devpriv->irq_free) goto no_dma; /* if we haven't IRQ, we can't use DMA */
+//  if (boardtypes[board].DMAbits!=0) { /* board support DMA */
+//    dma=it->options[2];
+//    if (((1<<dma)&boardtypes[board].DMAbits)==0) {
+//      printk(", DMA is out of allowed range, FAIL!\n");
+//      return -EINVAL; /* Bad DMA */
+//     } 
+//    ret=request_dma(dma, "pcl812");
+//    if (ret) {
+//      printk(", unable to allocate DMA %d, FAIL!\n",dma);
+//      return -EBUSY; /* DMA isn't free */
+//     } 
+//    devpriv->dma=dma;
+//    printk(", dma=%d", dma);
+//    pages=1; /* we need 8KB */
+//    devpriv->dmabuf[0]= __get_dma_pages(GFP_KERNEL, pages);
+//    if (!devpriv->dmabuf[0]) {
+//      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]=PAGE_SIZE*2;
+//    devpriv->dmabuf[1]= __get_dma_pages(GFP_KERNEL, pages);
+//    if (!devpriv->dmabuf[1]) {
+//      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]=PAGE_SIZE*2;
+//   }
+
+//no_dma:
+   
+  num_of_subdevs=0;
+
+  /*if (!((board==boardPCL812PG)&&(it->options[3]==1))) {*/
+  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:
+        s->subdev_flags = SDF_READABLE;
+        s->n_chan = this_board->n_aichan;
+        s->maxdata = 0xfff;
+        s->len_chanlist = 1024;
+        s->timer_type = this_board->ai_timer_type;
+        s->range_type = this_board->ai_range_type;
+        switch (board) {
+          case boardPCL812PG:
+           s->subdev_flags|=SDF_GROUND;
+            s->trig[0] = pcl812_ai_mode0;
+           if (it->options[3]==1) s->range_type=RANGE_pcl812pg2_ai;
+           if (dev->irq) {
+             if (it->options[2]!=1) { s->trig[1] = pcl812_ai_mode1; }
+                               else { s->trig[3] = pcl812_ai_mode3_int; }
+             }
+            break;
+         case boardPCL813B:
+           s->subdev_flags|=SDF_GROUND;
+           if (it->options[1]==1) s->range_type=RANGE_pcl813b2_ai; 
+           s->trig[0] = pcl812_ai_mode0;
+           break;
+        }
+         break;
+      case COMEDI_SUBD_AO:
+        s->subdev_flags = SDF_WRITEABLE;
+        s->n_chan = this_board->n_aochan;
+        s->maxdata = 0xfff;
+        s->len_chanlist = 1;
+        //s->timer_type = this_board->ai_timer_type;
+        s->range_type = this_board->ao_range_type;
+        switch (board) {
+          case boardPCL812PG:
+           s->subdev_flags|=SDF_GROUND;
+           s->trig[0] = pcl812_ao_mode0;
+            //s->trig[1] = pcl812_ao_mode1;
+           if (it->options[4]==1) s->range_type=RANGE_unipolar5; 
+           if (it->options[4]==2) s->range_type=RANGE_unknown; 
+           break;
+         }     
+       break;
+      case COMEDI_SUBD_DI:
+        s->subdev_flags = SDF_READABLE;
+        s->n_chan = this_board->n_dichan;
+        s->maxdata = 1;
+        s->len_chanlist = this_board->n_dichan;
+        s->range_type = RANGE_digital;
+        switch (board) {
+          case boardPCL812PG:
+           s->trig[0] = pcl812_di_mode0;
+           break;
+         }     
+        break;
+      case COMEDI_SUBD_DO:
+        s->subdev_flags = SDF_WRITEABLE;
+        s->n_chan = this_board->n_dochan;
+        s->maxdata = 1;
+        s->len_chanlist = this_board->n_dochan;
+        s->range_type = RANGE_digital;
+        switch (board) {
+          case boardPCL812PG:
+            s->trig[0] = pcl812_do_mode0;
+           break;
+         }     
+       break;
+      }
+     s++;
+   }
+         
+  /*dev->rem = pcl812_rem;*/
+       
+  switch (dev->board) {
+    case boardPCL812PG: 
+     pcl812_reset(dev); 
+     devpriv->max_812_ai_mode0_samples=32;
+     devpriv->max_812_ai_mode0_rangewait=1;
+     devpriv->max_812_ai_mode0_chanset=1;
+     devpriv->max_812_ai_mode0_convstart=5;
+     break;
+    case boardPCL813B: 
+     pcl812_reset(dev); 
+     if (it->options[2]<2) { devpriv->max_812_ai_mode0_samples=1; }
+                      else { devpriv->max_812_ai_mode0_samples=it->options[2]; }
+#ifdef PCL813_MICROSECS
+     devpriv->max_812_ai_mode0_rangewait=1; /* maybe there must by greatest timeout */
+     devpriv->max_812_ai_mode0_chanset=5;
+     devpriv->max_812_ai_mode0_convstart=20;
+#else
+     devpriv->max_812_ai_mode0_rangewaint=1;
+     devpriv->max_812_ai_mode0_chanset=5000;
+     devpriv->max_812_ai_mode0_convstart=20000;
+#endif
+     break;
+   }
+  printk("\n");
+  return 0;
+}
+
+
+/*
+==============================================================================
+  Removes device
+ */
+static int pcl812_detach(comedi_device * dev) {
+
+#ifdef MD_DEBUG
+  printk("comedi%d: pcl812: remove\n", dev->minor);
+#endif
+  free_resources(dev);
+  return 0;
+}
+
+static int pcl812_recognize(char *name) {
+
+ int i;
+
+#ifdef MD_DEBUG
+  printk("comedi: pcl812: recognize code '%s'\n",name);
+#endif
+  for (i = 0; i < n_boardtypes; i++) {
+    if (!strcmp(boardtypes[i].name, name)) {
+#ifdef MD_DEBUG
+      printk("comedi: pcl812: recognize found '%s'\n", boardtypes[i].name);
+#endif
+      return i;
+     }
+   }
+  return -1;
+}
+
+/*  
+==============================================================================
+*/
+#ifdef MODULE
+int init_module(void)
+{
+       comedi_driver_register(&driver_pcl812);
+       
+       return 0;
+}
+
+void cleanup_module(void)
+{
+       comedi_driver_unregister(&driver_pcl812);
+}
+#endif