New driver from Everett Wang <everteq@sbcglobal.net>:
authorFrank Mori Hess <fmhess@speakeasy.net>
Thu, 21 Sep 2006 00:00:18 +0000 (00:00 +0000)
committerFrank Mori Hess <fmhess@speakeasy.net>
Thu, 21 Sep 2006 00:00:18 +0000 (00:00 +0000)
We have written a comedi driver for sensoray 526
board. It is a very small PC104 format multifunction
card. We have used this driver for a while and it is
pretty stable.

comedi/drivers/Kbuild
comedi/drivers/Makefile.am
comedi/drivers/s526.c [new file with mode: 0644]

index 3f3070669b3187f8b8be04db50b11643e7518bec..dd60c581a265f7477706a40989802b368768b64b 100644 (file)
@@ -97,6 +97,7 @@ obj-m += rti800.o
 obj-m += rti802.o
 obj-m += skel.o
 obj-m += ssv_dnp.o
+obj-m += s526.o
 obj-m += s626.o
 obj-m += comedi_test.o
 
index 0191551025eed351ec8c01a53a53c1a6d0737b4e..c33f2da9b931be2576a14aa4d572795a24838e74 100644 (file)
@@ -162,6 +162,7 @@ module_PROGRAMS = \
  rti802.ko \
  skel.ko \
  ssv_dnp.ko \
+ s526.ko \
  s626.ko \
  comedi_test.ko \
  $(pcmcia_modules) \
@@ -257,6 +258,7 @@ rti800_ko_SOURCES = rti800.c
 rti802_ko_SOURCES = rti802.c
 skel_ko_SOURCES = skel.c
 ssv_dnp_ko_SOURCES = ssv_dnp.c
+s526_ko_SOURCES = s526.c
 s626_ko_SOURCES = s626.c
 comedi_test_ko_SOURCES = comedi_test.c
 usbdux_ko_SOURCES = usbdux.c
diff --git a/comedi/drivers/s526.c b/comedi/drivers/s526.c
new file mode 100644 (file)
index 0000000..1dc15ae
--- /dev/null
@@ -0,0 +1,965 @@
+/*
+    comedi/drivers/s526.c
+    Sensoray s526 Comedi driver
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+/*
+Driver: s526.ko
+Description: Sensoray 526 driver
+Devices: Sensoray s526
+Author: Richie 
+       Everett Wang <everett.wang@everteq.com>
+Updated: Thu, 14 Sep. 2006
+Status: experimental
+
+Encoder works
+Analog input works
+Analog output works
+PWM output works
+Commands are not supported yet.
+
+Configuration Options:
+
+comedi_config /dev/comedi0 s526 0x2C0,0x3
+
+*/
+
+#include <linux/comedidev.h>
+#include <linux/ioport.h>
+
+#define S526_SIZE 64
+
+#define S526_START_AI_CONV     0
+#define S526_AI_READ           0
+
+
+/* Ports */
+#define S526_IOSIZE 0x40
+#define S526_NUM_PORTS 27
+
+/* registers */
+#define REG_TCR 0x00
+#define REG_WDC 0x02
+#define REG_DAC 0x04
+#define REG_ADC 0x06
+#define REG_ADD 0x08
+#define REG_DIO 0x0A
+#define REG_IER 0x0C
+#define REG_ISR 0x0E
+#define REG_MSC 0x10
+#define REG_C0L 0x12
+#define REG_C0H 0x14
+#define REG_C0M 0x16
+#define REG_C0C 0x18
+#define REG_C1L 0x1A
+#define REG_C1H 0x1C
+#define REG_C1M 0x1E
+#define REG_C1C 0x20
+#define REG_C2L 0x22
+#define REG_C2H 0x24
+#define REG_C2M 0x26
+#define REG_C2C 0x28
+#define REG_C3L 0x2A
+#define REG_C3H 0x2C
+#define REG_C3M 0x2E
+#define REG_C3C 0x30
+#define REG_EED 0x32
+#define REG_EEC 0x34
+
+
+int s526_ports[] =
+{
+REG_TCR,
+REG_WDC,
+REG_DAC,
+REG_ADC,
+REG_ADD,
+REG_DIO,
+REG_IER,
+REG_ISR,
+REG_MSC,
+REG_C0L,
+REG_C0H,
+REG_C0M,
+REG_C0C,
+REG_C1L,
+REG_C1H,
+REG_C1M,
+REG_C1C,
+REG_C2L,
+REG_C2H,
+REG_C2M,
+REG_C2C,
+REG_C3L,
+REG_C3H,
+REG_C3M,
+REG_C3C,
+REG_EED,
+REG_EEC
+};
+
+typedef struct
+{
+       unsigned short coutSource: 1;
+       unsigned short coutPolarity: 1;
+       unsigned short autoLoadResetRcap: 3;
+       unsigned short hwCtEnableSource: 2;
+       unsigned short ctEnableCtrl: 2;
+       unsigned short clockSource: 2;
+       unsigned short countDir: 1;
+       unsigned short countDirCtrl: 1;
+       unsigned short outputRegLatchCtrl: 1;
+       unsigned short preloadRegSel: 1;
+       unsigned short reserved: 1;  
+} counter_mode_register_t;
+
+union {
+       counter_mode_register_t reg;
+       unsigned short value;
+} cmReg;
+
+#define MAX_GPCT_CONFIG_DATA 6
+
+/* Different Application Classes for GPCT Subdevices */
+/* The list is not exhaustive and needs discussion! */
+typedef enum
+{
+        CountingAndTimeMeasurement,
+        SinglePulseGeneration,
+        PulseTrainGeneration,
+        PositionMeasurement,
+        Miscellaneous
+} S526_GPCT_APP_CLASS;
+
+
+/* Config struct for different GPCT subdevice Application Classes and
+   their options
+*/
+typedef struct s526GPCTConfig
+{
+       S526_GPCT_APP_CLASS app;
+       int data[MAX_GPCT_CONFIG_DATA];
+} s526_gpct_config_t;
+
+static s526_gpct_config_t s526_gpct_config[4];
+static unsigned short s526_ai_config = 0;
+
+/*
+ * Board descriptions for two imaginary boards.  Describing the
+ * boards in this way is optional, and completely driver-dependent.
+ * Some drivers use arrays such as this, other do not.
+ */
+typedef struct s526_board_struct{
+       char *name;
+       int gpct_chans;
+       int gpct_bits;
+       int ad_chans;
+       int ad_bits;
+       int da_chans;
+       int da_bits;
+       int have_dio;
+}s526_board;
+
+static s526_board s526_boards[] = {
+       {
+       name:           "s526",
+       gpct_chans:     4,
+       gpct_bits:      24,
+       ad_chans:       8,
+       ad_bits:        16,
+       da_chans:       4,
+       da_bits:        16,
+       have_dio:       1,
+       }
+};
+
+#define ADDR_REG(reg) (int)((int)((s526_board *)dev->iobase) + reg)
+#define ADDR_CHAN_REG(reg, chan) (int)((int)((s526_board *)dev->iobase) + reg + chan * 8)
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((s526_board *)dev->board_ptr)
+
+/* this structure is for data unique to this hardware driver.  If
+   several hardware drivers keep similar information in this structure,
+   feel free to suggest moving the variable to the comedi_device struct.  */
+typedef struct{
+       int data;
+
+       /* would be useful for a PCI device */
+       struct pci_dev *pci_dev;
+
+       /* Used for AO readback */
+       lsampl_t ao_readback[2];
+}s526_private;
+/*
+ * most drivers define the following macro to make it easy to
+ * access the private structure.
+ */
+#define devpriv ((s526_private *)dev->private)
+
+/*
+ * The comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attach/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int s526_attach(comedi_device *dev,comedi_devconfig *it);
+static int s526_detach(comedi_device *dev);
+static comedi_driver driver_s526={
+       driver_name:    "s526",
+       module:         THIS_MODULE,
+       attach:         s526_attach,
+       detach:         s526_detach,
+/* It is not necessary to implement the following members if you are
+ * writing a driver for a ISA PnP or PCI card */
+        /* Most drivers will support multiple types of boards by
+         * having an array of board structures.  These were defined
+         * in s526_boards[] above.  Note that the element 'name'
+         * was first in the structure -- Comedi uses this fact to
+         * extract the name of the board without knowing any details
+         * about the structure except for its length.
+         * When a device is attached (by comedi_config), the name
+         * of the device is given to Comedi, and Comedi tries to
+         * match it by going through the list of board names.  If
+         * there is a match, the address of the pointer is put
+         * into dev->board_ptr and driver->attach() is called.
+         *
+         * Note that these are not necessary if you can determine
+         * the type of board in software.  ISA PnP, PCI, and PCMCIA
+         * devices are such boards.
+         */
+        board_name:     s526_boards,
+        offset:         sizeof(s526_board),
+        num_names:      sizeof(s526_boards) / sizeof(s526_board),
+};
+
+static int s526_gpct_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int s526_gpct_insn_config(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int s526_gpct_winsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int s526_ai_insn_config(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int s526_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int s526_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int s526_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int s526_dio_insn_bits(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn,lsampl_t *data);
+static int s526_dio_insn_config(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn,lsampl_t *data);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int s526_attach(comedi_device *dev,comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+        int iobase;
+       int i, n;
+//     sampl_t value;
+//      int subdev_channel = 0;
+
+       printk("comedi%d: s526: ", dev->minor);
+
+        iobase=it->options[0];
+        if(check_region(iobase, S526_IOSIZE) < 0){
+                comedi_error(dev,"I/O port conflict");
+                return -EIO;
+        }
+        request_region(iobase, S526_IOSIZE, thisboard->name);
+        dev->iobase=iobase;
+
+       printk("iobase=0x%x\n", dev->iobase);
+
+       /*** make it a little quieter, exw, 8/29/06
+       for (i = 0; i < S526_NUM_PORTS; i++) {
+               printk("0x%02x: 0x%04x\n", ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i])));
+       }
+       ***/
+       
+/*
+ * Initialize dev->board_name.  Note that we can use the "thisboard"
+ * macro now, since we just initialized it in the last line.
+ */
+        thisboard = (s526_board *)&s526_boards[0];
+
+       dev->board_name = thisboard->name;
+
+/*
+ * Allocate the private structure area.  alloc_private() is a
+ * convenient macro defined in comedidev.h.
+ */
+       if(alloc_private(dev, sizeof(s526_private)) < 0)
+               return -ENOMEM;
+
+/*
+ * Allocate the subdevice structures.  alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.
+ */
+        dev->n_subdevices = 4;
+       if(alloc_subdevices(dev, dev->n_subdevices) < 0)
+               return -ENOMEM;
+
+        s=dev->subdevices+0;
+        /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
+        s->type         = COMEDI_SUBD_COUNTER;
+        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+        /* KG: What does SDF_LSAMPL (see multiq3.c) mean? */
+        s->n_chan       = thisboard->gpct_chans;
+        s->maxdata      = 0x00ffffff; /* 24 bit counter */
+        s->insn_read    = s526_gpct_rinsn;
+        s->insn_config  = s526_gpct_insn_config;
+        s->insn_write   = s526_gpct_winsn;
+
+        /* Command are not implemented yet, however they are necessary to
+        allocate the necessary memory for the comedi_async struct (used
+        to trigger the GPCT in case of pulsegenerator function */
+        //s->do_cmd = s526_gpct_cmd;
+        //s->do_cmdtest = s526_gpct_cmdtest;
+        //s->cancel = s526_gpct_cancel;
+
+       s=dev->subdevices+1;
+       //dev->read_subdev=s;
+       /* analog input subdevice */
+       s->type=COMEDI_SUBD_AI;
+       /* we support single-ended (ground) and differential */
+       s->subdev_flags=SDF_READABLE|SDF_DIFF;
+       s->n_chan=8;
+       s->maxdata=0x3fff;
+       s->range_table=&range_bipolar10;
+       s->len_chanlist=16;  /* This is the maximum chanlist length that
+                               the board can handle */
+       s->insn_read   = s526_ai_rinsn;
+        s->insn_config = s526_ai_insn_config;
+
+       s=dev->subdevices+2;
+       /* analog output subdevice */
+       s->type=COMEDI_SUBD_AO;
+       s->subdev_flags=SDF_WRITABLE;
+       s->n_chan=4;
+       s->maxdata=0xffff;
+       s->range_table=&range_bipolar10;
+       s->insn_write = s526_ao_winsn;
+       s->insn_read = s526_ao_rinsn;
+
+       s=dev->subdevices+3;
+       /* digital i/o subdevice */
+       if(thisboard->have_dio){
+               s->type=COMEDI_SUBD_DIO;
+               s->subdev_flags=SDF_READABLE|SDF_WRITABLE;
+               s->n_chan=2;
+               s->maxdata=1;
+               s->range_table=&range_digital;
+               s->insn_bits = s526_dio_insn_bits;
+               s->insn_config = s526_dio_insn_config;
+       }else{
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+       
+       printk("attached\n");
+
+       return 1;
+
+#if 0
+                       // Example of Counter Application 
+                       //One-shot (software trigger)
+                       cmReg.reg.coutSource            = 0; // out RCAP
+                       cmReg.reg.coutPolarity          = 1; // Polarity inverted
+                       cmReg.reg.autoLoadResetRcap     = 1; // Auto load 0:disabled, 1:enabled
+                       cmReg.reg.hwCtEnableSource      = 3; // NOT RCAP
+                       cmReg.reg.ctEnableCtrl          = 2; // Hardware
+                       cmReg.reg.clockSource           = 2; // Internal
+                       cmReg.reg.countDir              = 1; // Down
+                       cmReg.reg.countDirCtrl          = 1; // Software
+                       cmReg.reg.outputRegLatchCtrl    = 0; // latch on read
+                       cmReg.reg.preloadRegSel         = 0; // PR0
+                       cmReg.reg.reserved              = 0;
+
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+                       outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Reset the counter
+                       outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Load the counter from PR0
+
+                       outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel));  // Reset RCAP (fires one-shot)
+
+#else
+                       
+
+                       // Set Counter Mode Register
+                       cmReg.reg.coutSource            = 0; // out RCAP
+                       cmReg.reg.coutPolarity          = 0; // Polarity inverted
+                       cmReg.reg.autoLoadResetRcap     = 0; // Auto load disabled
+                       cmReg.reg.hwCtEnableSource      = 2; // NOT RCAP
+                       cmReg.reg.ctEnableCtrl          = 1; // 1: Software,  >1 : Hardware
+                       cmReg.reg.clockSource           = 3; // x4
+                       cmReg.reg.countDir              = 0; // up
+                       cmReg.reg.countDirCtrl          = 0; // quadrature
+                       cmReg.reg.outputRegLatchCtrl    = 0; // latch on read
+                       cmReg.reg.preloadRegSel         = 0; // PR0
+                       cmReg.reg.reserved              = 0;
+               
+                       n = 0;  
+                       printk("Mode reg=0x%04x, 0x%04x\n", cmReg.value, ADDR_CHAN_REG(REG_C0M, n));
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n));
+                       udelay(1000);
+                       printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n)));
+
+                       // Load the pre-laod register high word 
+//                     value = (sampl_t) (0x55);
+//                     outw(value, ADDR_CHAN_REG(REG_C0H, n));
+
+                       // Load the pre-laod register low word 
+//                     value = (sampl_t)(0xaa55);
+//                     outw(value, ADDR_CHAN_REG(REG_C0L, n));
+
+                       // Write the Counter Control Register
+//                     outw(value, ADDR_CHAN_REG(REG_C0C, 0));
+
+                       
+                       // Reset the counter if it is software preload
+                       if (cmReg.reg.autoLoadResetRcap == 0) {
+                               outw(0x8000, ADDR_CHAN_REG(REG_C0C, n));        // Reset the counter
+                               outw(0x4000, ADDR_CHAN_REG(REG_C0C, n));        // Load the counter from PR0
+                       }
+
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n));
+                       udelay(1000);
+                       printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n)));
+
+#endif
+       printk("Current registres:\n");
+
+       for (i = 0; i < S526_NUM_PORTS; i++) {
+               printk("0x%02x: 0x%04x\n", ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i])));
+       }
+       return 1;
+}
+
+
+/*
+ * _detach is called to deconfigure a device.  It should deallocate
+ * resources.  
+ * This function is also called when _attach() fails, so it should be
+ * careful not to release resources that were not necessarily
+ * allocated by _attach().  dev->private and dev->subdevices are
+ * deallocated automatically by the core.
+ */
+static int s526_detach(comedi_device *dev)
+{
+       printk("comedi%d: s526: remove\n", dev->minor);
+       
+       if (dev->iobase > 0)
+               release_region(dev->iobase, S526_IOSIZE);
+       
+       return 0;
+}
+
+static int s526_gpct_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+        int i; // counts the Data
+        int counter_channel = CR_CHAN(insn->chanspec);
+        unsigned short datalow;
+        unsigned short datahigh;
+
+       // Check if (n > 0)
+       if (insn->n <= 0) 
+       {
+               printk("s526: INSN_READ: n should be > 0\n");
+                return -EINVAL;
+        }
+        // Read the low word first
+        for ( i=0 ; i < insn->n ; i++ )
+        {
+               datalow  = inw(ADDR_CHAN_REG(REG_C0L, counter_channel));
+               datahigh = inw(ADDR_CHAN_REG(REG_C0H, counter_channel));
+               data[i] = (int)(datahigh & 0x00FF);
+               data[i] = (data[i] << 16) | (datalow & 0xFFFF);
+//             printk("s526 GPCT[%d]: %x(0x%04x, 0x%04x)\n", counter_channel, data[i], datahigh, datalow);
+       }
+       return i;
+}
+
+static int s526_gpct_insn_config(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+        int subdev_channel = CR_CHAN(insn->chanspec);// Unpack chanspec
+       int i;
+       sampl_t value;
+
+//        printk("s526: GPCT_INSN_CONFIG: Configuring Channel %d\n", subdev_channel);
+
+       for(i = 0; i < MAX_GPCT_CONFIG_DATA; i++) {
+               s526_gpct_config[subdev_channel].data[i] = insn->data[i];
+//             printk("data[%d]=%x\n", i, insn->data[i]);
+       }
+
+        // Check what type of Counter the user requested, data[0] contains
+        // the Application type
+        switch(insn->data[0])
+        {
+               case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
+                       /* 
+                       data[0]: Application Type 
+                       data[1]: Counter Mode Register Value
+                       data[2]: Pre-load Register Value
+                       data[3]: Conter Control Register
+                       */
+                       printk("s526: GPCT_INSN_CONFIG: Configuring Encoder\n");
+                       s526_gpct_config[subdev_channel].app = PositionMeasurement;
+
+/*
+                       // Example of Counter Application 
+                       //One-shot (software trigger)
+                       cmReg.reg.coutSource            = 0; // out RCAP
+                       cmReg.reg.coutPolarity          = 1; // Polarity inverted
+                       cmReg.reg.autoLoadResetRcap     = 0; // Auto load disabled
+                       cmReg.reg.hwCtEnableSource      = 3; // NOT RCAP
+                       cmReg.reg.ctEnableCtrl          = 2; // Hardware
+                       cmReg.reg.clockSource           = 2; // Internal
+                       cmReg.reg.countDir              = 1; // Down
+                       cmReg.reg.countDirCtrl          = 1; // Software
+                       cmReg.reg.outputRegLatchCtrl    = 0; // latch on read
+                       cmReg.reg.preloadRegSel         = 0; // PR0
+                       cmReg.reg.reserved              = 0;
+
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+                       outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Reset the counter
+                       outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Load the counter from PR0
+
+                       outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel));  // Reset RCAP (fires one-shot)
+                       
+*/
+
+#if 1
+                       // Set Counter Mode Register
+                       cmReg.reg.coutSource            = 0; // out RCAP
+                       cmReg.reg.coutPolarity          = 0; // Polarity inverted
+                       cmReg.reg.autoLoadResetRcap     = 0; // Auto load disabled
+                       cmReg.reg.hwCtEnableSource      = 2; // NOT RCAP
+                       cmReg.reg.ctEnableCtrl          = 1; // 1: Software,  >1 : Hardware
+                       cmReg.reg.clockSource           = 3; // x4
+                       cmReg.reg.countDir              = 0; // up
+                       cmReg.reg.countDirCtrl          = 0; // quadrature
+                       cmReg.reg.outputRegLatchCtrl    = 0; // latch on read
+                       cmReg.reg.preloadRegSel         = 0; // PR0
+                       cmReg.reg.reserved              = 0;
+               
+                       // Set Counter Mode Register
+//                     printk("s526: Counter Mode register=%x\n", cmReg.value);
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Reset the counter if it is software preload
+                       if (cmReg.reg.autoLoadResetRcap == 0) {
+                               outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Reset the counter
+//                             outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Load the counter from PR0
+                       }
+#else
+                       cmReg.reg.countDirCtrl = 0;  // 0 quadrature, 1 software control
+
+                       // data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4
+                       if(insn->data[1] == GPCT_X2) {
+                               cmReg.reg.clockSource = 1;
+                       }else if(insn->data[1] == GPCT_X4) {
+                               cmReg.reg.clockSource = 2;
+                       }else {
+                               cmReg.reg.clockSource = 0;
+                       }
+
+                        // When to take into account the indexpulse:
+                       if(insn->data[2] == GPCT_IndexPhaseLowLow) {
+                       }else if(insn->data[2] == GPCT_IndexPhaseLowHigh) {
+                       }else if(insn->data[2] == GPCT_IndexPhaseHighLow) {
+                       }else if(insn->data[2] == GPCT_IndexPhaseHighHigh) {
+                       }
+
+                       // Take into account the index pulse?
+                       if(insn->data[3] == GPCT_RESET_COUNTER_ON_INDEX)
+                               cmReg.reg.autoLoadResetRcap = 4; // Auto load with INDEX^
+
+
+                       // Set Counter Mode Register
+                       cmReg.value = (sampl_t)(insn->data[1] & 0xFFFF);
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Load the pre-laod register high word 
+                       value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+
+                       // Load the pre-laod register low word 
+                       value = (sampl_t)(insn->data[2] & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       // Write the Counter Control Register
+                       if (insn->data[3] != 0) {
+                               value = (sampl_t)(insn->data[3] & 0xFFFF);
+                               outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
+                       }
+                       
+                       // Reset the counter if it is software preload
+                       if (cmReg.reg.autoLoadResetRcap == 0) {
+                               outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Reset the counter
+                               outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel));   // Load the counter from PR0
+                       }
+#endif
+                       break;
+                
+               case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
+                       /* 
+                       data[0]: Application Type 
+                       data[1]: Counter Mode Register Value
+                       data[2]: Pre-load Register 0 Value
+                       data[3]: Pre-load Register 1 Value
+                       data[4]: Conter Control Register
+                       */
+                       printk("s526: GPCT_INSN_CONFIG: Configuring SPG\n");
+                       s526_gpct_config[subdev_channel].app = SinglePulseGeneration;
+
+                       // Set Counter Mode Register
+                       cmReg.value = (sampl_t)(insn->data[1] & 0xFFFF);
+                       cmReg.reg.preloadRegSel = 0; // PR0
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Load the pre-laod register 0 high word 
+                       value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+
+                       // Load the pre-laod register 0 low word 
+                       value = (sampl_t)(insn->data[2] & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       // Set Counter Mode Register
+                       cmReg.value = (sampl_t)(insn->data[1] & 0xFFFF);
+                       cmReg.reg.preloadRegSel = 1; // PR1
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Load the pre-laod register 1 high word 
+                       value = (sampl_t) ((insn->data[3] >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+
+                       // Load the pre-laod register 1 low word 
+                       value = (sampl_t)(insn->data[3] & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       // Write the Counter Control Register
+                       if (insn->data[3] != 0) {
+                               value = (sampl_t)(insn->data[3] & 0xFFFF);
+                               outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
+                       }
+                       break;
+
+               case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
+                       /* 
+                       data[0]: Application Type 
+                       data[1]: Counter Mode Register Value
+                       data[2]: Pre-load Register 0 Value
+                       data[3]: Pre-load Register 1 Value
+                       data[4]: Conter Control Register
+                       */
+                       printk("s526: GPCT_INSN_CONFIG: Configuring PTG\n");
+                       s526_gpct_config[subdev_channel].app = PulseTrainGeneration;
+
+                       // Set Counter Mode Register
+                       cmReg.value = (sampl_t)(insn->data[1] & 0xFFFF);
+                       cmReg.reg.preloadRegSel = 0; // PR0
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Load the pre-laod register 0 high word 
+                       value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+
+                       // Load the pre-laod register 0 low word 
+                       value = (sampl_t)(insn->data[2] & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       // Set Counter Mode Register
+                       cmReg.value = (sampl_t)(insn->data[1] & 0xFFFF);
+                       cmReg.reg.preloadRegSel = 1; // PR1
+                       outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
+
+                       // Load the pre-laod register 1 high word 
+                       value = (sampl_t) ((insn->data[3] >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+
+                       // Load the pre-laod register 1 low word 
+                       value = (sampl_t)(insn->data[3] & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+
+                       // Write the Counter Control Register
+                       if (insn->data[3] != 0) {
+                               value = (sampl_t)(insn->data[3] & 0xFFFF);
+                               outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
+                       }
+                       break;
+
+               default:
+                       printk("s526: unsupported GPCT_insn_config\n");
+                       return -EINVAL;
+                       break;
+        }
+
+        return insn->n;
+}
+
+static int s526_gpct_winsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+        int subdev_channel = CR_CHAN(insn->chanspec);// Unpack chanspec
+       sampl_t value;
+
+        printk("s526: GPCT_INSN_WRITE on channel %d\n", subdev_channel);
+       cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel));
+       printk("s526: Counter Mode Register: %x\n", cmReg.value);
+        // Check what Application of Counter this channel is configured for
+        switch(s526_gpct_config[subdev_channel].app)
+        {
+               case PositionMeasurement:
+                       printk("S526: INSN_WRITE: PM\n");
+                       outw(0xFFFF & ((*data) >>  16), ADDR_CHAN_REG(REG_C0H, subdev_channel));
+                       outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel));
+                       break;
+
+               case SinglePulseGeneration:
+                       printk("S526: INSN_WRITE: SPG\n");
+                       outw(0xFFFF & ((*data) >>  16), ADDR_CHAN_REG(REG_C0H, subdev_channel));
+                       outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel));
+                       break;
+
+               case PulseTrainGeneration:
+                       /* data[0] contains the PULSE_WIDTH
+                        data[1] contains the PULSE_PERIOD
+                        @pre PULSE_PERIOD > PULSE_WIDTH > 0
+                        The above periods must be expressed as a multiple of the
+                        pulse frequency on the selected source
+                       */
+                       printk("S526: INSN_WRITE: PTG\n");
+                       if ( (insn->data[1] > insn->data[0]) && (insn->data[0] > 0 ) )
+                       {
+                               (s526_gpct_config[subdev_channel]).data[0] = insn->data[0];
+                               (s526_gpct_config[subdev_channel]).data[1] = insn->data[1];
+                       }
+                       else
+                       {
+                               printk("%d \t %d\n",insn->data[1],insn->data[2]);
+                               printk("s526: INSN_WRITE: PTG: Problem with Pulse params\n");
+                               return -EINVAL;
+                       }
+                       
+                       value = (sampl_t)((*data >> 16) & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
+                       value = (sampl_t)(*data & 0xFFFF);
+                       outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
+                       break;
+               default: // Impossible
+                       printk("s526: INSN_WRITE: Functionality %d not implemented yet\n",
+                       s526_gpct_config[subdev_channel].app);
+                       return -EINVAL;
+                       break;
+        }
+       // return the number of samples written
+       return insn->n ;
+}
+
+#define ISR_ADC_DONE 0x4
+static int s526_ai_insn_config(comedi_device *dev,comedi_subdevice *s,
+        comedi_insn *insn, lsampl_t *data)
+{
+       int result = -EINVAL;
+
+        if (insn->n < 1) return result;
+
+       result = insn->n;
+
+       /* data[0] : channels was set in relevant bits.
+          data[1] : delay
+       */
+
+       // Enable ADC interrupt
+       outw(ISR_ADC_DONE, ADDR_REG(REG_IER));
+//     printk("s526: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC)));
+       s526_ai_config = (data[0] & 0x3FF) << 5;
+       if (data[1] > 0)
+               s526_ai_config |= 0x8000; //set the delay
+       
+       s526_ai_config |= 0x0001;  // ADC start bit.
+
+        return result;
+}
+
+
+/*
+ * "instructions" read/write data in "one-shot" or "software-triggered"
+ * mode.
+ */
+static int s526_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int n,i;
+       int chan = CR_CHAN(insn->chanspec);
+       unsigned short value;
+       unsigned int d;
+       unsigned int status;
+
+       value = s526_ai_config | (chan << 1);
+       // outw(value, ADDR_REG(REG_ADC)); do it with ADC start
+
+
+       /* convert n samples */
+       for(n=0; n<insn->n; n++){
+               /* trigger conversion */
+               value |= 0x0001; // ADC start 
+               outw(value, ADDR_REG(REG_ADC));
+//             printk("s526: Wrote 0x%04x to ADC\n", value);
+//             printk("s526: ADC reg=0x%04x\n", inw(ADDR_REG(REG_ADC)));
+
+#define TIMEOUT 100
+               /* wait for conversion to end */
+               for(i=0;i<TIMEOUT;i++){
+                       status = inw(ADDR_REG(REG_ISR));
+                       if (status & ISR_ADC_DONE) {
+                               outw(ISR_ADC_DONE, ADDR_REG(REG_ISR));
+                               break;
+                       }
+               }
+               if(i==TIMEOUT){
+                       /* rt_printk() should be used instead of printk()
+                        * whenever the code can be called from real-time. */
+                       rt_printk("s526: ADC(0x%04x) timeout\n", inw(ADDR_REG(REG_ISR)));
+                       return -ETIMEDOUT;
+               }
+
+               /* read data */
+               d = inw(ADDR_REG(REG_ADD));
+//             printk("AI[%d]=0x%04x\n", n, (unsigned short)(d & 0xFFFF));
+
+               data[n] = d;
+       }
+
+       /* return the number of samples read/written */
+       return n;
+}
+
+
+static int s526_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+       unsigned short val;
+
+//     printk("s526_ao_winsn\n");
+       val = chan << 1;
+//     outw(val, dev->iobase + REG_DAC);
+       outw(val, ADDR_REG(REG_DAC));
+
+       /* Writing a list of values to an AO channel is probably not
+        * very useful, but that's how the interface is defined. */
+       for(i=0;i<insn->n;i++){
+               /* a typical programming sequence */
+//             outw(data[i], dev->iobase + REG_ADD);  // write the data to preload register
+               outw(data[i], ADDR_REG(REG_ADD));  // write the data to preload register
+               devpriv->ao_readback[chan] = data[i];
+//             outw(val + 1, dev->iobase + REG_DAC); // starts the D/A conversion.
+               outw(val + 1, ADDR_REG(REG_DAC)); // starts the D/A conversion.
+       }
+
+       /* return the number of samples read/written */
+       return i;
+}
+
+/* AO subdevices should have a read insn as well as a write insn.
+ * Usually this means copying a value stored in devpriv. */
+static int s526_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+
+       for(i=0;i<insn->n;i++)
+               data[i] = devpriv->ao_readback[chan];
+
+       return i;
+}
+
+/* DIO devices are slightly special.  Although it is possible to
+ * implement the insn_read/insn_write interface, it is much more
+ * useful to applications if you implement the insn_bits interface.
+ * This allows packed reading/writing of the DIO channels.  The
+ * comedi core can convert between insn_bits and insn_read/write */
+static int s526_dio_insn_bits(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+       if(insn->n!=2)return -EINVAL;
+
+       /* The insn data is a mask in data[0] and the new data
+        * in data[1], each channel cooresponding to a bit. */
+       if(data[0]){
+               s->state &= ~data[0];
+               s->state |= data[0]&data[1];
+               /* Write out the new digital output lines */
+               outw(s->state, ADDR_REG(REG_DIO));
+       }
+
+       /* on return, data[1] contains the value of the digital
+        * input and output lines. */
+       data[1]=inw(ADDR_REG(REG_DIO)) & 0xFF; // low 8 bits are the data
+       /* or we could just return the software copy of the output values if
+        * it was a purely digital output subdevice */
+       //data[1]=s->state;
+
+       return 2;
+}
+
+static int s526_dio_insn_config(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+       int chan=CR_CHAN(insn->chanspec);
+       sampl_t value;
+
+       printk("S526 DIO insn_config\n");
+
+       if(insn->n!=1)return -EINVAL;
+
+       value = inw(ADDR_REG(REG_DIO));
+
+       /* The input or output configuration of each digital line is
+        * configured by a special insn_config instruction.  chanspec
+        * contains the channel to be changed, and data[0] contains the
+        * value COMEDI_INPUT or COMEDI_OUTPUT. */
+
+       if(data[0]==COMEDI_OUTPUT){
+               value |= 1 << (chan + 10);  // bit 10/11 set the group 1/2's mode
+               s->io_bits |= (0xF << chan);
+       }else{
+               value &= ~(1 << (chan + 10));  // 1 is output, 0 is input.
+               s->io_bits &= ~(0xF << chan);
+       }
+       outw(value, ADDR_REG(REG_DIO));
+
+       return 1;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_s526);
+