Staging: comedi: add me4000 driver
authorGuenter Gebhardt <g.gebhardt@meilhaus.com>
Wed, 19 Nov 2008 17:38:19 +0000 (09:38 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 6 Jan 2009 21:52:23 +0000 (13:52 -0800)
This adds the me4000 driver to the comedi staging tree

From: Guenter Gebhardt <g.gebhardt@meilhaus.com>
Cc: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/staging/comedi/drivers/Makefile
drivers/staging/comedi/drivers/me4000.c [new file with mode: 0644]
drivers/staging/comedi/drivers/me4000.h [new file with mode: 0644]

index 4d16634c9341555759561b3388ce86018f9e74b0..bcc9ce816d05a964e16597d6a85b9aaa7d1bd1e7 100644 (file)
@@ -10,6 +10,7 @@ obj-$(CONFIG_COMEDI)                  += comedi_parport.o
 # Comedi PCI drivers
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += mite.o
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += icp_multi.o
+obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += me4000.o
 
 # Comedi USB drivers
 obj-$(CONFIG_COMEDI_USB_DRIVERS)       += usbdux.o
diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c
new file mode 100644 (file)
index 0000000..b432aa7
--- /dev/null
@@ -0,0 +1,2362 @@
+/*
+   comedi/drivers/me4000.c
+   Source code for the Meilhaus ME-4000 board family.
+
+   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: me4000
+Description: Meilhaus ME-4000 series boards
+Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i, ME-4680is
+Author: gg (Guenter Gebhardt <g.gebhardt@meilhaus.com>)
+Updated: Mon, 18 Mar 2002 15:34:01 -0800
+Status: broken (no support for loading firmware)
+
+Supports:
+
+    - Analog Input
+    - Analog Output
+    - Digital I/O
+    - Counter
+
+Configuration Options:
+
+    [0] - PCI bus number (optional)
+    [1] - PCI slot number (optional)
+
+    If bus/slot is not specified, the first available PCI
+    device will be used.
+
+The firmware required by these boards is available in the
+comedi_nonfree_firmware tarball available from
+http://www.comedi.org.  However, the driver's support for
+loading the firmware through comedi_config is currently
+broken.
+
+ */
+
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#include "comedi_pci.h"
+#include "me4000.h"
+#if 0
+/* file removed due to GPL incompatibility */
+#include "me4000_fw.h"
+#endif
+
+/*=============================================================================
+  PCI device table.
+  This is used by modprobe to translate PCI IDs to drivers.
+  ===========================================================================*/
+
+static DEFINE_PCI_DEVICE_TABLE(me4000_pci_table) = {
+       {PCI_VENDOR_ID_MEILHAUS, 0x4650, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+
+       {PCI_VENDOR_ID_MEILHAUS, 0x4660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4661, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4662, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4663, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+
+       {PCI_VENDOR_ID_MEILHAUS, 0x4670, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4671, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4672, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4673, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+
+       {PCI_VENDOR_ID_MEILHAUS, 0x4680, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4681, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4682, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_MEILHAUS, 0x4683, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+
+       {0}
+};
+
+MODULE_DEVICE_TABLE(pci, me4000_pci_table);
+
+static const me4000_board_t me4000_boards[] = {
+       {"ME-4650", 0x4650, {0, 0}, {16, 0, 0, 0}, {4}, {0}},
+
+       {"ME-4660", 0x4660, {0, 0}, {32, 0, 16, 0}, {4}, {3}},
+       {"ME-4660i", 0x4661, {0, 0}, {32, 0, 16, 0}, {4}, {3}},
+       {"ME-4660s", 0x4662, {0, 0}, {32, 8, 16, 0}, {4}, {3}},
+       {"ME-4660is", 0x4663, {0, 0}, {32, 8, 16, 0}, {4}, {3}},
+
+       {"ME-4670", 0x4670, {4, 0}, {32, 0, 16, 1}, {4}, {3}},
+       {"ME-4670i", 0x4671, {4, 0}, {32, 0, 16, 1}, {4}, {3}},
+       {"ME-4670s", 0x4672, {4, 0}, {32, 8, 16, 1}, {4}, {3}},
+       {"ME-4670is", 0x4673, {4, 0}, {32, 8, 16, 1}, {4}, {3}},
+
+       {"ME-4680", 0x4680, {4, 4}, {32, 0, 16, 1}, {4}, {3}},
+       {"ME-4680i", 0x4681, {4, 4}, {32, 0, 16, 1}, {4}, {3}},
+       {"ME-4680s", 0x4682, {4, 4}, {32, 8, 16, 1}, {4}, {3}},
+       {"ME-4680is", 0x4683, {4, 4}, {32, 8, 16, 1}, {4}, {3}},
+
+       {0},
+};
+
+#define ME4000_BOARD_VERSIONS (sizeof(me4000_boards) / sizeof(me4000_board_t) - 1)
+
+/*-----------------------------------------------------------------------------
+  Comedi function prototypes
+  ---------------------------------------------------------------------------*/
+static int me4000_attach(comedi_device * dev, comedi_devconfig * it);
+static int me4000_detach(comedi_device * dev);
+static comedi_driver driver_me4000 = {
+      driver_name:"me4000",
+      module:THIS_MODULE,
+      attach:me4000_attach,
+      detach:me4000_detach,
+};
+
+/*-----------------------------------------------------------------------------
+  Meilhaus function prototypes
+  ---------------------------------------------------------------------------*/
+static int me4000_probe(comedi_device * dev, comedi_devconfig * it);
+static int get_registers(comedi_device * dev, struct pci_dev *pci_dev_p);
+static int init_board_info(comedi_device * dev, struct pci_dev *pci_dev_p);
+static int init_ao_context(comedi_device * dev);
+static int init_ai_context(comedi_device * dev);
+static int init_dio_context(comedi_device * dev);
+static int init_cnt_context(comedi_device * dev);
+static int xilinx_download(comedi_device * dev);
+static int reset_board(comedi_device * dev);
+
+static int me4000_dio_insn_bits(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_dio_insn_config(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int cnt_reset(comedi_device * dev, unsigned int channel);
+
+static int cnt_config(comedi_device * dev,
+       unsigned int channel, unsigned int mode);
+
+static int me4000_cnt_insn_config(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_cnt_insn_write(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_cnt_insn_read(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_ai_insn_read(comedi_device * dev,
+       comedi_subdevice * subdevice, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_ai_cancel(comedi_device * dev, comedi_subdevice * s);
+
+static int ai_check_chanlist(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd);
+
+static int ai_round_cmd_args(comedi_device * dev,
+       comedi_subdevice * s,
+       comedi_cmd * cmd,
+       unsigned int *init_ticks,
+       unsigned int *scan_ticks, unsigned int *chan_ticks);
+
+static int ai_prepare(comedi_device * dev,
+       comedi_subdevice * s,
+       comedi_cmd * cmd,
+       unsigned int init_ticks,
+       unsigned int scan_ticks, unsigned int chan_ticks);
+
+static int ai_write_chanlist(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd);
+
+static irqreturn_t me4000_ai_isr(int irq, void *dev_id PT_REGS_ARG);
+
+static int me4000_ai_do_cmd_test(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd);
+
+static int me4000_ai_do_cmd(comedi_device * dev, comedi_subdevice * s);
+
+static int me4000_ao_insn_write(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+static int me4000_ao_insn_read(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data);
+
+/*-----------------------------------------------------------------------------
+  Meilhaus inline functions
+  ---------------------------------------------------------------------------*/
+
+static inline void me4000_outb(comedi_device * dev, unsigned char value,
+       unsigned long port)
+{
+       PORT_PDEBUG("--> 0x%02X port 0x%04lX\n", value, port);
+       outb(value, port);
+}
+
+static inline void me4000_outl(comedi_device * dev, unsigned long value,
+       unsigned long port)
+{
+       PORT_PDEBUG("--> 0x%08lX port 0x%04lX\n", value, port);
+       outl(value, port);
+}
+
+static inline unsigned long me4000_inl(comedi_device * dev, unsigned long port)
+{
+       unsigned long value;
+       value = inl(port);
+       PORT_PDEBUG("<-- 0x%08lX port 0x%04lX\n", value, port);
+       return value;
+}
+
+static inline unsigned char me4000_inb(comedi_device * dev, unsigned long port)
+{
+       unsigned char value;
+       value = inb(port);
+       PORT_PDEBUG("<-- 0x%08X port 0x%04lX\n", value, port);
+       return value;
+}
+
+static const comedi_lrange me4000_ai_range = {
+       4,
+       {
+                       UNI_RANGE(2.5),
+                       UNI_RANGE(10),
+                       BIP_RANGE(2.5),
+                       BIP_RANGE(10),
+               }
+};
+
+static const comedi_lrange me4000_ao_range = {
+       1,
+       {
+                       BIP_RANGE(10),
+               }
+};
+
+static int me4000_attach(comedi_device * dev, comedi_devconfig * it)
+{
+       comedi_subdevice *s;
+       int result;
+
+       CALL_PDEBUG("In me4000_attach()\n");
+
+       result = me4000_probe(dev, it);
+       if (result)
+               return result;
+
+       /*
+        * Allocate the subdevice structures.  alloc_subdevice() is a
+        * convenient macro defined in comedidev.h.  It relies on
+        * n_subdevices being set correctly.
+        */
+       if (alloc_subdevices(dev, 4) < 0)
+               return -ENOMEM;
+
+    /*=========================================================================
+      Analog input subdevice
+      ========================================================================*/
+
+       s = dev->subdevices + 0;
+
+       if (thisboard->ai.count) {
+               s->type = COMEDI_SUBD_AI;
+               s->subdev_flags =
+                       SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+               s->n_chan = thisboard->ai.count;
+               s->maxdata = 0xFFFF;    // 16 bit ADC
+               s->len_chanlist = ME4000_AI_CHANNEL_LIST_COUNT;
+               s->range_table = &me4000_ai_range;
+               s->insn_read = me4000_ai_insn_read;
+
+               if (info->irq > 0) {
+                       if (comedi_request_irq(info->irq, me4000_ai_isr,
+                                       IRQF_SHARED, "ME-4000", dev)) {
+                               printk("comedi%d: me4000: me4000_attach(): Unable to allocate irq\n", dev->minor);
+                       } else {
+                               dev->read_subdev = s;
+                               s->subdev_flags |= SDF_CMD_READ;
+                               s->cancel = me4000_ai_cancel;
+                               s->do_cmdtest = me4000_ai_do_cmd_test;
+                               s->do_cmd = me4000_ai_do_cmd;
+                       }
+               } else {
+                       printk(KERN_WARNING
+                               "comedi%d: me4000: me4000_attach(): No interrupt available\n",
+                               dev->minor);
+               }
+       } else {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+    /*=========================================================================
+      Analog output subdevice
+      ========================================================================*/
+
+       s = dev->subdevices + 1;
+
+       if (thisboard->ao.count) {
+               s->type = COMEDI_SUBD_AO;
+               s->subdev_flags = SDF_WRITEABLE | SDF_COMMON | SDF_GROUND;
+               s->n_chan = thisboard->ao.count;
+               s->maxdata = 0xFFFF;    // 16 bit DAC
+               s->range_table = &me4000_ao_range;
+               s->insn_write = me4000_ao_insn_write;
+               s->insn_read = me4000_ao_insn_read;
+       } else {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+    /*=========================================================================
+      Digital I/O subdevice
+      ========================================================================*/
+
+       s = dev->subdevices + 2;
+
+       if (thisboard->dio.count) {
+               s->type = COMEDI_SUBD_DIO;
+               s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+               s->n_chan = thisboard->dio.count * 8;
+               s->maxdata = 1;
+               s->range_table = &range_digital;
+               s->insn_bits = me4000_dio_insn_bits;
+               s->insn_config = me4000_dio_insn_config;
+       } else {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+       /*
+        * Check for optoisolated ME-4000 version. If one the first
+        * port is a fixed output port and the second is a fixed input port.
+        */
+       if (!me4000_inl(dev, info->dio_context.dir_reg)) {
+               s->io_bits |= 0xFF;
+               me4000_outl(dev, ME4000_DIO_CTRL_BIT_MODE_0,
+                       info->dio_context.dir_reg);
+       }
+
+    /*=========================================================================
+      Counter subdevice
+      ========================================================================*/
+
+       s = dev->subdevices + 3;
+
+       if (thisboard->cnt.count) {
+               s->type = COMEDI_SUBD_COUNTER;
+               s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+               s->n_chan = thisboard->cnt.count;
+               s->maxdata = 0xFFFF;    // 16 bit counters
+               s->insn_read = me4000_cnt_insn_read;
+               s->insn_write = me4000_cnt_insn_write;
+               s->insn_config = me4000_cnt_insn_config;
+       } else {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+       return 0;
+}
+
+static int me4000_probe(comedi_device * dev, comedi_devconfig * it)
+{
+       struct pci_dev *pci_device;
+       int result, i;
+       me4000_board_t *board;
+
+       CALL_PDEBUG("In me4000_probe()\n");
+
+       /* Allocate private memory */
+       if (alloc_private(dev, sizeof(me4000_info_t)) < 0) {
+               return -ENOMEM;
+       }
+       /*
+        * Probe the device to determine what device in the series it is.
+        */
+       for (pci_device = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
+               pci_device != NULL;
+               pci_device =
+               pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pci_device)) {
+               if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) {
+                       for (i = 0; i < ME4000_BOARD_VERSIONS; i++) {
+                               if (me4000_boards[i].device_id ==
+                                       pci_device->device) {
+                                       /* Was a particular bus/slot requested? */
+                                       if ((it->options[0] != 0)
+                                               || (it->options[1] != 0)) {
+                                               /* Are we on the wrong bus/slot? */
+                                               if (pci_device->bus->number !=
+                                                       it->options[0]
+                                                       || PCI_SLOT(pci_device->
+                                                               devfn) !=
+                                                       it->options[1]) {
+                                                       continue;
+                                               }
+                                       }
+                                       dev->board_ptr = me4000_boards + i;
+                                       board = (me4000_board_t *) dev->
+                                               board_ptr;
+                                       info->pci_dev_p = pci_device;
+                                       goto found;
+                               }
+                       }
+               }
+       }
+
+       printk(KERN_ERR
+               "comedi%d: me4000: me4000_probe(): No supported board found (req. bus/slot : %d/%d)\n",
+               dev->minor, it->options[0], it->options[1]);
+       return -ENODEV;
+
+      found:
+
+       printk(KERN_INFO
+               "comedi%d: me4000: me4000_probe(): Found %s at PCI bus %d, slot %d\n",
+               dev->minor, me4000_boards[i].name, pci_device->bus->number,
+               PCI_SLOT(pci_device->devfn));
+
+       /* Set data in device structure */
+       dev->board_name = board->name;
+
+       /* Enable PCI device and request regions */
+       result = comedi_pci_enable(pci_device, dev->board_name);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot enable PCI device and request I/O regions\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Get the PCI base registers */
+       result = get_registers(dev, pci_device);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot get registers\n",
+                       dev->minor);
+               return result;
+       }
+       /* Initialize board info */
+       result = init_board_info(dev, pci_device);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot init baord info\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Init analog output context */
+       result = init_ao_context(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot init ao context\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Init analog input context */
+       result = init_ai_context(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot init ai context\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Init digital I/O context */
+       result = init_dio_context(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot init dio context\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Init counter context */
+       result = init_cnt_context(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Cannot init cnt context\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Download the xilinx firmware */
+       result = xilinx_download(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Can't download firmware\n",
+                       dev->minor);
+               return result;
+       }
+
+       /* Make a hardware reset */
+       result = reset_board(dev);
+       if (result) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_probe(): Can't reset board\n",
+                       dev->minor);
+               return result;
+       }
+
+       return 0;
+}
+
+static int get_registers(comedi_device * dev, struct pci_dev *pci_dev_p)
+{
+
+       CALL_PDEBUG("In get_registers()\n");
+
+    /*--------------------------- plx regbase ---------------------------------*/
+
+       info->plx_regbase = pci_resource_start(pci_dev_p, 1);
+       if (info->plx_regbase == 0) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: get_registers(): PCI base address 1 is not available\n",
+                       dev->minor);
+               return -ENODEV;
+       }
+       info->plx_regbase_size = pci_resource_len(pci_dev_p, 1);
+
+    /*--------------------------- me4000 regbase ------------------------------*/
+
+       info->me4000_regbase = pci_resource_start(pci_dev_p, 2);
+       if (info->me4000_regbase == 0) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: get_registers(): PCI base address 2 is not available\n",
+                       dev->minor);
+               return -ENODEV;
+       }
+       info->me4000_regbase_size = pci_resource_len(pci_dev_p, 2);
+
+    /*--------------------------- timer regbase ------------------------------*/
+
+       info->timer_regbase = pci_resource_start(pci_dev_p, 3);
+       if (info->timer_regbase == 0) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: get_registers(): PCI base address 3 is not available\n",
+                       dev->minor);
+               return -ENODEV;
+       }
+       info->timer_regbase_size = pci_resource_len(pci_dev_p, 3);
+
+    /*--------------------------- program regbase ------------------------------*/
+
+       info->program_regbase = pci_resource_start(pci_dev_p, 5);
+       if (info->program_regbase == 0) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: get_registers(): PCI base address 5 is not available\n",
+                       dev->minor);
+               return -ENODEV;
+       }
+       info->program_regbase_size = pci_resource_len(pci_dev_p, 5);
+
+       return 0;
+}
+
+static int init_board_info(comedi_device * dev, struct pci_dev *pci_dev_p)
+{
+       int result;
+
+       CALL_PDEBUG("In init_board_info()\n");
+
+       /* Init spin locks */
+       //spin_lock_init(&info->preload_lock);
+       //spin_lock_init(&info->ai_ctrl_lock);
+
+       /* Get the serial number */
+       result = pci_read_config_dword(pci_dev_p, 0x2C, &info->serial_no);
+       if (result != PCIBIOS_SUCCESSFUL) {
+               return result;
+       }
+
+       /* Get the hardware revision */
+       result = pci_read_config_byte(pci_dev_p, 0x08, &info->hw_revision);
+       if (result != PCIBIOS_SUCCESSFUL) {
+               return result;
+       }
+
+       /* Get the vendor id */
+       info->vendor_id = pci_dev_p->vendor;
+
+       /* Get the device id */
+       info->device_id = pci_dev_p->device;
+
+       /* Get the irq assigned to the board */
+       info->irq = pci_dev_p->irq;
+
+       return 0;
+}
+
+static int init_ao_context(comedi_device * dev)
+{
+       int i;
+
+       CALL_PDEBUG("In init_ao_context()\n");
+
+       for (i = 0; i < thisboard->ao.count; i++) {
+               //spin_lock_init(&info->ao_context[i].use_lock);
+               info->ao_context[i].irq = info->irq;
+
+               switch (i) {
+               case 0:
+                       info->ao_context[i].ctrl_reg =
+                               info->me4000_regbase + ME4000_AO_00_CTRL_REG;
+                       info->ao_context[i].status_reg =
+                               info->me4000_regbase + ME4000_AO_00_STATUS_REG;
+                       info->ao_context[i].fifo_reg =
+                               info->me4000_regbase + ME4000_AO_00_FIFO_REG;
+                       info->ao_context[i].single_reg =
+                               info->me4000_regbase + ME4000_AO_00_SINGLE_REG;
+                       info->ao_context[i].timer_reg =
+                               info->me4000_regbase + ME4000_AO_00_TIMER_REG;
+                       info->ao_context[i].irq_status_reg =
+                               info->me4000_regbase + ME4000_IRQ_STATUS_REG;
+                       info->ao_context[i].preload_reg =
+                               info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
+                       break;
+               case 1:
+                       info->ao_context[i].ctrl_reg =
+                               info->me4000_regbase + ME4000_AO_01_CTRL_REG;
+                       info->ao_context[i].status_reg =
+                               info->me4000_regbase + ME4000_AO_01_STATUS_REG;
+                       info->ao_context[i].fifo_reg =
+                               info->me4000_regbase + ME4000_AO_01_FIFO_REG;
+                       info->ao_context[i].single_reg =
+                               info->me4000_regbase + ME4000_AO_01_SINGLE_REG;
+                       info->ao_context[i].timer_reg =
+                               info->me4000_regbase + ME4000_AO_01_TIMER_REG;
+                       info->ao_context[i].irq_status_reg =
+                               info->me4000_regbase + ME4000_IRQ_STATUS_REG;
+                       info->ao_context[i].preload_reg =
+                               info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
+                       break;
+               case 2:
+                       info->ao_context[i].ctrl_reg =
+                               info->me4000_regbase + ME4000_AO_02_CTRL_REG;
+                       info->ao_context[i].status_reg =
+                               info->me4000_regbase + ME4000_AO_02_STATUS_REG;
+                       info->ao_context[i].fifo_reg =
+                               info->me4000_regbase + ME4000_AO_02_FIFO_REG;
+                       info->ao_context[i].single_reg =
+                               info->me4000_regbase + ME4000_AO_02_SINGLE_REG;
+                       info->ao_context[i].timer_reg =
+                               info->me4000_regbase + ME4000_AO_02_TIMER_REG;
+                       info->ao_context[i].irq_status_reg =
+                               info->me4000_regbase + ME4000_IRQ_STATUS_REG;
+                       info->ao_context[i].preload_reg =
+                               info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
+                       break;
+               case 3:
+                       info->ao_context[i].ctrl_reg =
+                               info->me4000_regbase + ME4000_AO_03_CTRL_REG;
+                       info->ao_context[i].status_reg =
+                               info->me4000_regbase + ME4000_AO_03_STATUS_REG;
+                       info->ao_context[i].fifo_reg =
+                               info->me4000_regbase + ME4000_AO_03_FIFO_REG;
+                       info->ao_context[i].single_reg =
+                               info->me4000_regbase + ME4000_AO_03_SINGLE_REG;
+                       info->ao_context[i].timer_reg =
+                               info->me4000_regbase + ME4000_AO_03_TIMER_REG;
+                       info->ao_context[i].irq_status_reg =
+                               info->me4000_regbase + ME4000_IRQ_STATUS_REG;
+                       info->ao_context[i].preload_reg =
+                               info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int init_ai_context(comedi_device * dev)
+{
+
+       CALL_PDEBUG("In init_ai_context()\n");
+
+       info->ai_context.irq = info->irq;
+
+       info->ai_context.ctrl_reg = info->me4000_regbase + ME4000_AI_CTRL_REG;
+       info->ai_context.status_reg =
+               info->me4000_regbase + ME4000_AI_STATUS_REG;
+       info->ai_context.channel_list_reg =
+               info->me4000_regbase + ME4000_AI_CHANNEL_LIST_REG;
+       info->ai_context.data_reg = info->me4000_regbase + ME4000_AI_DATA_REG;
+       info->ai_context.chan_timer_reg =
+               info->me4000_regbase + ME4000_AI_CHAN_TIMER_REG;
+       info->ai_context.chan_pre_timer_reg =
+               info->me4000_regbase + ME4000_AI_CHAN_PRE_TIMER_REG;
+       info->ai_context.scan_timer_low_reg =
+               info->me4000_regbase + ME4000_AI_SCAN_TIMER_LOW_REG;
+       info->ai_context.scan_timer_high_reg =
+               info->me4000_regbase + ME4000_AI_SCAN_TIMER_HIGH_REG;
+       info->ai_context.scan_pre_timer_low_reg =
+               info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG;
+       info->ai_context.scan_pre_timer_high_reg =
+               info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG;
+       info->ai_context.start_reg = info->me4000_regbase + ME4000_AI_START_REG;
+       info->ai_context.irq_status_reg =
+               info->me4000_regbase + ME4000_IRQ_STATUS_REG;
+       info->ai_context.sample_counter_reg =
+               info->me4000_regbase + ME4000_AI_SAMPLE_COUNTER_REG;
+
+       return 0;
+}
+
+static int init_dio_context(comedi_device * dev)
+{
+
+       CALL_PDEBUG("In init_dio_context()\n");
+
+       info->dio_context.dir_reg = info->me4000_regbase + ME4000_DIO_DIR_REG;
+       info->dio_context.ctrl_reg = info->me4000_regbase + ME4000_DIO_CTRL_REG;
+       info->dio_context.port_0_reg =
+               info->me4000_regbase + ME4000_DIO_PORT_0_REG;
+       info->dio_context.port_1_reg =
+               info->me4000_regbase + ME4000_DIO_PORT_1_REG;
+       info->dio_context.port_2_reg =
+               info->me4000_regbase + ME4000_DIO_PORT_2_REG;
+       info->dio_context.port_3_reg =
+               info->me4000_regbase + ME4000_DIO_PORT_3_REG;
+
+       return 0;
+}
+
+static int init_cnt_context(comedi_device * dev)
+{
+
+       CALL_PDEBUG("In init_cnt_context()\n");
+
+       info->cnt_context.ctrl_reg = info->timer_regbase + ME4000_CNT_CTRL_REG;
+       info->cnt_context.counter_0_reg =
+               info->timer_regbase + ME4000_CNT_COUNTER_0_REG;
+       info->cnt_context.counter_1_reg =
+               info->timer_regbase + ME4000_CNT_COUNTER_1_REG;
+       info->cnt_context.counter_2_reg =
+               info->timer_regbase + ME4000_CNT_COUNTER_2_REG;
+
+       return 0;
+}
+
+#define FIRMWARE_NOT_AVAILABLE 1
+#if FIRMWARE_NOT_AVAILABLE
+extern unsigned char *xilinx_firm;
+#endif
+
+static int xilinx_download(comedi_device * dev)
+{
+       u32 value = 0;
+       wait_queue_head_t queue;
+       int idx = 0;
+       int size = 0;
+
+       CALL_PDEBUG("In xilinx_download()\n");
+
+       init_waitqueue_head(&queue);
+
+       /*
+        * Set PLX local interrupt 2 polarity to high.
+        * Interrupt is thrown by init pin of xilinx.
+        */
+       outl(0x10, info->plx_regbase + PLX_INTCSR);
+
+       /* Set /CS and /WRITE of the Xilinx */
+       value = inl(info->plx_regbase + PLX_ICR);
+       value |= 0x100;
+       outl(value, info->plx_regbase + PLX_ICR);
+
+       /* Init Xilinx with CS1 */
+       inb(info->program_regbase + 0xC8);
+
+       /* Wait until /INIT pin is set */
+       udelay(20);
+       if (!inl(info->plx_regbase + PLX_INTCSR) & 0x20) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: xilinx_download(): Can't init Xilinx\n",
+                       dev->minor);
+               return -EIO;
+       }
+
+       /* Reset /CS and /WRITE of the Xilinx */
+       value = inl(info->plx_regbase + PLX_ICR);
+       value &= ~0x100;
+       outl(value, info->plx_regbase + PLX_ICR);
+       if (FIRMWARE_NOT_AVAILABLE) {
+               comedi_error(dev,
+                       "xilinx firmware unavailable due to licensing, aborting");
+               return -EIO;
+       } else {
+               /* Download Xilinx firmware */
+               size = (xilinx_firm[0] << 24) + (xilinx_firm[1] << 16) +
+                       (xilinx_firm[2] << 8) + xilinx_firm[3];
+               udelay(10);
+
+               for (idx = 0; idx < size; idx++) {
+                       outb(xilinx_firm[16 + idx], info->program_regbase);
+                       udelay(10);
+
+                       /* Check if BUSY flag is low */
+                       if (inl(info->plx_regbase + PLX_ICR) & 0x20) {
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: xilinx_download(): Xilinx is still busy (idx = %d)\n",
+                                       dev->minor, idx);
+                               return -EIO;
+                       }
+               }
+       }
+
+       /* If done flag is high download was successful */
+       if (inl(info->plx_regbase + PLX_ICR) & 0x4) {
+       } else {
+               printk(KERN_ERR
+                       "comedi%d: me4000: xilinx_download(): DONE flag is not set\n",
+                       dev->minor);
+               printk(KERN_ERR
+                       "comedi%d: me4000: xilinx_download(): Download not succesful\n",
+                       dev->minor);
+               return -EIO;
+       }
+
+       /* Set /CS and /WRITE */
+       value = inl(info->plx_regbase + PLX_ICR);
+       value |= 0x100;
+       outl(value, info->plx_regbase + PLX_ICR);
+
+       return 0;
+}
+
+static int reset_board(comedi_device * dev)
+{
+       unsigned long icr;
+
+       CALL_PDEBUG("In reset_board()\n");
+
+       /* Make a hardware reset */
+       icr = me4000_inl(dev, info->plx_regbase + PLX_ICR);
+       icr |= 0x40000000;
+       me4000_outl(dev, icr, info->plx_regbase + PLX_ICR);
+       icr &= ~0x40000000;
+       me4000_outl(dev, icr, info->plx_regbase + PLX_ICR);
+
+       /* 0x8000 to the DACs means an output voltage of 0V */
+       me4000_outl(dev, 0x8000,
+               info->me4000_regbase + ME4000_AO_00_SINGLE_REG);
+       me4000_outl(dev, 0x8000,
+               info->me4000_regbase + ME4000_AO_01_SINGLE_REG);
+       me4000_outl(dev, 0x8000,
+               info->me4000_regbase + ME4000_AO_02_SINGLE_REG);
+       me4000_outl(dev, 0x8000,
+               info->me4000_regbase + ME4000_AO_03_SINGLE_REG);
+
+       /* Set both stop bits in the analog input control register */
+       me4000_outl(dev,
+               ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP,
+               info->me4000_regbase + ME4000_AI_CTRL_REG);
+
+       /* Set both stop bits in the analog output control register */
+       me4000_outl(dev,
+               ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
+               info->me4000_regbase + ME4000_AO_00_CTRL_REG);
+       me4000_outl(dev,
+               ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
+               info->me4000_regbase + ME4000_AO_01_CTRL_REG);
+       me4000_outl(dev,
+               ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
+               info->me4000_regbase + ME4000_AO_02_CTRL_REG);
+       me4000_outl(dev,
+               ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
+               info->me4000_regbase + ME4000_AO_03_CTRL_REG);
+
+       /* Enable interrupts on the PLX */
+       me4000_outl(dev, 0x43, info->plx_regbase + PLX_INTCSR);
+
+       /* Set the adustment register for AO demux */
+       me4000_outl(dev, ME4000_AO_DEMUX_ADJUST_VALUE,
+               info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG);
+
+       /* Set digital I/O direction for port 0 to output on isolated versions */
+       if (!(me4000_inl(dev, info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1)) {
+               me4000_outl(dev, 0x1,
+                       info->me4000_regbase + ME4000_DIO_CTRL_REG);
+       }
+
+       return 0;
+}
+
+static int me4000_detach(comedi_device * dev)
+{
+       CALL_PDEBUG("In me4000_detach()\n");
+
+       if (info) {
+               if (info->pci_dev_p) {
+                       reset_board(dev);
+                       if (info->plx_regbase) {
+                               comedi_pci_disable(info->pci_dev_p);
+                       }
+                       pci_dev_put(info->pci_dev_p);
+               }
+       }
+
+       return 0;
+}
+
+/*=============================================================================
+  Analog input section
+  ===========================================================================*/
+
+static int me4000_ai_insn_read(comedi_device * dev,
+       comedi_subdevice * subdevice, comedi_insn * insn, lsampl_t * data)
+{
+
+       int chan = CR_CHAN(insn->chanspec);
+       int rang = CR_RANGE(insn->chanspec);
+       int aref = CR_AREF(insn->chanspec);
+
+       unsigned long entry = 0;
+       unsigned long tmp;
+       long lval;
+
+       CALL_PDEBUG("In me4000_ai_insn_read()\n");
+
+       if (insn->n == 0) {
+               return 0;
+       } else if (insn->n > 1) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_insn_read(): Invalid instruction length %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       switch (rang) {
+       case 0:
+               entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5;
+               break;
+       case 1:
+               entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10;
+               break;
+       case 2:
+               entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5;
+               break;
+       case 3:
+               entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_insn_read(): Invalid range specified\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       switch (aref) {
+       case AREF_GROUND:
+       case AREF_COMMON:
+               if (chan >= thisboard->ai.count) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n",
+                               dev->minor);
+                       return -EINVAL;
+               }
+               entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan;
+               break;
+
+       case AREF_DIFF:
+               if (rang == 0 || rang == 1) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_insn_read(): Range must be bipolar when aref = diff\n",
+                               dev->minor);
+                       return -EINVAL;
+               }
+
+               if (chan >= thisboard->ai.diff_count) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n",
+                               dev->minor);
+                       return -EINVAL;
+               }
+               entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_insn_read(): Invalid aref specified\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       entry |= ME4000_AI_LIST_LAST_ENTRY;
+
+       /* Clear channel list, data fifo and both stop bits */
+       tmp = me4000_inl(dev, info->ai_context.ctrl_reg);
+       tmp &= ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+               ME4000_AI_CTRL_BIT_DATA_FIFO |
+               ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Set the acquisition mode to single */
+       tmp &= ~(ME4000_AI_CTRL_BIT_MODE_0 | ME4000_AI_CTRL_BIT_MODE_1 |
+               ME4000_AI_CTRL_BIT_MODE_2);
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Enable channel list and data fifo */
+       tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO;
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Generate channel list entry */
+       me4000_outl(dev, entry, info->ai_context.channel_list_reg);
+
+       /* Set the timer to maximum sample rate */
+       me4000_outl(dev, ME4000_AI_MIN_TICKS, info->ai_context.chan_timer_reg);
+       me4000_outl(dev, ME4000_AI_MIN_TICKS,
+               info->ai_context.chan_pre_timer_reg);
+
+       /* Start conversion by dummy read */
+       me4000_inl(dev, info->ai_context.start_reg);
+
+       /* Wait until ready */
+       udelay(10);
+       if (!(me4000_inl(dev, info->ai_context.
+                               status_reg) & ME4000_AI_STATUS_BIT_EF_DATA)) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_insn_read(): Value not available after wait\n",
+                       dev->minor);
+               return -EIO;
+       }
+
+       /* Read value from data fifo */
+       lval = me4000_inl(dev, info->ai_context.data_reg) & 0xFFFF;
+       data[0] = lval ^ 0x8000;
+
+       return 1;
+}
+
+static int me4000_ai_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+       unsigned long tmp;
+
+       CALL_PDEBUG("In me4000_ai_cancel()\n");
+
+       /* Stop any running conversion */
+       tmp = me4000_inl(dev, info->ai_context.ctrl_reg);
+       tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Clear the control register */
+       me4000_outl(dev, 0x0, info->ai_context.ctrl_reg);
+
+       return 0;
+}
+
+static int ai_check_chanlist(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd)
+{
+       int aref;
+       int i;
+
+       CALL_PDEBUG("In ai_check_chanlist()\n");
+
+       /* Check whether a channel list is available */
+       if (!cmd->chanlist_len) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: ai_check_chanlist(): No channel list available\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       /* Check the channel list size */
+       if (cmd->chanlist_len > ME4000_AI_CHANNEL_LIST_COUNT) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: ai_check_chanlist(): Channel list is to large\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       /* Check the pointer */
+       if (!cmd->chanlist) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: ai_check_chanlist(): NULL pointer to channel list\n",
+                       dev->minor);
+               return -EFAULT;
+       }
+
+       /* Check whether aref is equal for all entries */
+       aref = CR_AREF(cmd->chanlist[0]);
+       for (i = 0; i < cmd->chanlist_len; i++) {
+               if (CR_AREF(cmd->chanlist[i]) != aref) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: ai_check_chanlist(): Mode is not equal for all entries\n",
+                               dev->minor);
+                       return -EINVAL;
+               }
+       }
+
+       /* Check whether channels are available for this ending */
+       if (aref == SDF_DIFF) {
+               for (i = 0; i < cmd->chanlist_len; i++) {
+                       if (CR_CHAN(cmd->chanlist[i]) >=
+                               thisboard->ai.diff_count) {
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n",
+                                       dev->minor);
+                               return -EINVAL;
+                       }
+               }
+       } else {
+               for (i = 0; i < cmd->chanlist_len; i++) {
+                       if (CR_CHAN(cmd->chanlist[i]) >= thisboard->ai.count) {
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n",
+                                       dev->minor);
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       /* Check if bipolar is set for all entries when in differential mode */
+       if (aref == SDF_DIFF) {
+               for (i = 0; i < cmd->chanlist_len; i++) {
+                       if (CR_RANGE(cmd->chanlist[i]) != 1 &&
+                               CR_RANGE(cmd->chanlist[i]) != 2) {
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: ai_check_chanlist(): Bipolar is not selected in differential mode\n",
+                                       dev->minor);
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static int ai_round_cmd_args(comedi_device * dev,
+       comedi_subdevice * s,
+       comedi_cmd * cmd,
+       unsigned int *init_ticks,
+       unsigned int *scan_ticks, unsigned int *chan_ticks)
+{
+
+       int rest;
+
+       CALL_PDEBUG("In ai_round_cmd_args()\n");
+
+       *init_ticks = 0;
+       *scan_ticks = 0;
+       *chan_ticks = 0;
+
+       PDEBUG("ai_round_cmd_arg(): start_arg = %d\n", cmd->start_arg);
+       PDEBUG("ai_round_cmd_arg(): scan_begin_arg = %d\n",
+               cmd->scan_begin_arg);
+       PDEBUG("ai_round_cmd_arg(): convert_arg = %d\n", cmd->convert_arg);
+
+       if (cmd->start_arg) {
+               *init_ticks = (cmd->start_arg * 33) / 1000;
+               rest = (cmd->start_arg * 33) % 1000;
+
+               if (cmd->flags & TRIG_ROUND_NEAREST) {
+                       if (rest > 33) {
+                               (*init_ticks)++;
+                       }
+               } else if (cmd->flags & TRIG_ROUND_UP) {
+                       if (rest)
+                               (*init_ticks)++;
+               }
+       }
+
+       if (cmd->scan_begin_arg) {
+               *scan_ticks = (cmd->scan_begin_arg * 33) / 1000;
+               rest = (cmd->scan_begin_arg * 33) % 1000;
+
+               if (cmd->flags & TRIG_ROUND_NEAREST) {
+                       if (rest > 33) {
+                               (*scan_ticks)++;
+                       }
+               } else if (cmd->flags & TRIG_ROUND_UP) {
+                       if (rest)
+                               (*scan_ticks)++;
+               }
+       }
+
+       if (cmd->convert_arg) {
+               *chan_ticks = (cmd->convert_arg * 33) / 1000;
+               rest = (cmd->convert_arg * 33) % 1000;
+
+               if (cmd->flags & TRIG_ROUND_NEAREST) {
+                       if (rest > 33) {
+                               (*chan_ticks)++;
+                       }
+               } else if (cmd->flags & TRIG_ROUND_UP) {
+                       if (rest)
+                               (*chan_ticks)++;
+               }
+       }
+
+       PDEBUG("ai_round_cmd_args(): init_ticks = %d\n", *init_ticks);
+       PDEBUG("ai_round_cmd_args(): scan_ticks = %d\n", *scan_ticks);
+       PDEBUG("ai_round_cmd_args(): chan_ticks = %d\n", *chan_ticks);
+
+       return 0;
+}
+
+static void ai_write_timer(comedi_device * dev,
+       unsigned int init_ticks,
+       unsigned int scan_ticks, unsigned int chan_ticks)
+{
+
+       CALL_PDEBUG("In ai_write_timer()\n");
+
+       me4000_outl(dev, init_ticks - 1,
+               info->ai_context.scan_pre_timer_low_reg);
+       me4000_outl(dev, 0x0, info->ai_context.scan_pre_timer_high_reg);
+
+       if (scan_ticks) {
+               me4000_outl(dev, scan_ticks - 1,
+                       info->ai_context.scan_timer_low_reg);
+               me4000_outl(dev, 0x0, info->ai_context.scan_timer_high_reg);
+       }
+
+       me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_pre_timer_reg);
+       me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_timer_reg);
+}
+
+static int ai_prepare(comedi_device * dev,
+       comedi_subdevice * s,
+       comedi_cmd * cmd,
+       unsigned int init_ticks,
+       unsigned int scan_ticks, unsigned int chan_ticks)
+{
+
+       unsigned long tmp = 0;
+
+       CALL_PDEBUG("In ai_prepare()\n");
+
+       /* Write timer arguments */
+       ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks);
+
+       /* Reset control register */
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Start sources */
+       if ((cmd->start_src == TRIG_EXT &&
+                       cmd->scan_begin_src == TRIG_TIMER &&
+                       cmd->convert_src == TRIG_TIMER) ||
+               (cmd->start_src == TRIG_EXT &&
+                       cmd->scan_begin_src == TRIG_FOLLOW &&
+                       cmd->convert_src == TRIG_TIMER)) {
+               tmp = ME4000_AI_CTRL_BIT_MODE_1 |
+                       ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+                       ME4000_AI_CTRL_BIT_DATA_FIFO;
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_TIMER) {
+               tmp = ME4000_AI_CTRL_BIT_MODE_2 |
+                       ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+                       ME4000_AI_CTRL_BIT_DATA_FIFO;
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_EXT) {
+               tmp = ME4000_AI_CTRL_BIT_MODE_0 |
+                       ME4000_AI_CTRL_BIT_MODE_1 |
+                       ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+                       ME4000_AI_CTRL_BIT_DATA_FIFO;
+       } else {
+               tmp = ME4000_AI_CTRL_BIT_MODE_0 |
+                       ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+                       ME4000_AI_CTRL_BIT_DATA_FIFO;
+       }
+
+       /* Stop triggers */
+       if (cmd->stop_src == TRIG_COUNT) {
+               me4000_outl(dev, cmd->chanlist_len * cmd->stop_arg,
+                       info->ai_context.sample_counter_reg);
+               tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ;
+       } else if (cmd->stop_src == TRIG_NONE &&
+               cmd->scan_end_src == TRIG_COUNT) {
+               me4000_outl(dev, cmd->scan_end_arg,
+                       info->ai_context.sample_counter_reg);
+               tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ;
+       } else {
+               tmp |= ME4000_AI_CTRL_BIT_HF_IRQ;
+       }
+
+       /* Write the setup to the control register */
+       me4000_outl(dev, tmp, info->ai_context.ctrl_reg);
+
+       /* Write the channel list */
+       ai_write_chanlist(dev, s, cmd);
+
+       return 0;
+}
+
+static int ai_write_chanlist(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd)
+{
+       unsigned int entry;
+       unsigned int chan;
+       unsigned int rang;
+       unsigned int aref;
+       int i;
+
+       CALL_PDEBUG("In ai_write_chanlist()\n");
+
+       for (i = 0; i < cmd->chanlist_len; i++) {
+               chan = CR_CHAN(cmd->chanlist[i]);
+               rang = CR_RANGE(cmd->chanlist[i]);
+               aref = CR_AREF(cmd->chanlist[i]);
+
+               entry = chan;
+
+               if (rang == 0) {
+                       entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5;
+               } else if (rang == 1) {
+                       entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10;
+               } else if (rang == 2) {
+                       entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5;
+               } else {
+                       entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10;
+               }
+
+               if (aref == SDF_DIFF) {
+                       entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL;
+               } else {
+                       entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED;
+               }
+
+               me4000_outl(dev, entry, info->ai_context.channel_list_reg);
+       }
+
+       return 0;
+}
+
+static int me4000_ai_do_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+       int err;
+       unsigned int init_ticks = 0;
+       unsigned int scan_ticks = 0;
+       unsigned int chan_ticks = 0;
+       comedi_cmd *cmd = &s->async->cmd;
+
+       CALL_PDEBUG("In me4000_ai_do_cmd()\n");
+
+       /* Reset the analog input */
+       err = me4000_ai_cancel(dev, s);
+       if (err)
+               return err;
+
+       /* Round the timer arguments */
+       err = ai_round_cmd_args(dev,
+               s, cmd, &init_ticks, &scan_ticks, &chan_ticks);
+       if (err)
+               return err;
+
+       /* Prepare the AI for acquisition */
+       err = ai_prepare(dev, s, cmd, init_ticks, scan_ticks, chan_ticks);
+       if (err)
+               return err;
+
+       /* Start acquistion by dummy read */
+       me4000_inl(dev, info->ai_context.start_reg);
+
+       return 0;
+}
+
+/*
+ * me4000_ai_do_cmd_test():
+ *
+ * The demo cmd.c in ./comedilib/demo specifies 6 return values:
+ * - success
+ * - invalid source
+ * - source conflict
+ * - invalid argument
+ * - argument conflict
+ * - invalid chanlist
+ * So I tried to adopt this scheme.
+ */
+static int me4000_ai_do_cmd_test(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd)
+{
+
+       unsigned int init_ticks;
+       unsigned int chan_ticks;
+       unsigned int scan_ticks;
+       int err = 0;
+
+       CALL_PDEBUG("In me4000_ai_do_cmd_test()\n");
+
+       PDEBUG("me4000_ai_do_cmd_test(): subdev         = %d\n", cmd->subdev);
+       PDEBUG("me4000_ai_do_cmd_test(): flags          = %08X\n", cmd->flags);
+       PDEBUG("me4000_ai_do_cmd_test(): start_src      = %08X\n",
+               cmd->start_src);
+       PDEBUG("me4000_ai_do_cmd_test(): start_arg      = %d\n",
+               cmd->start_arg);
+       PDEBUG("me4000_ai_do_cmd_test(): scan_begin_src = %08X\n",
+               cmd->scan_begin_src);
+       PDEBUG("me4000_ai_do_cmd_test(): scan_begin_arg = %d\n",
+               cmd->scan_begin_arg);
+       PDEBUG("me4000_ai_do_cmd_test(): convert_src    = %08X\n",
+               cmd->convert_src);
+       PDEBUG("me4000_ai_do_cmd_test(): convert_arg    = %d\n",
+               cmd->convert_arg);
+       PDEBUG("me4000_ai_do_cmd_test(): scan_end_src   = %08X\n",
+               cmd->scan_end_src);
+       PDEBUG("me4000_ai_do_cmd_test(): scan_end_arg   = %d\n",
+               cmd->scan_end_arg);
+       PDEBUG("me4000_ai_do_cmd_test(): stop_src       = %08X\n",
+               cmd->stop_src);
+       PDEBUG("me4000_ai_do_cmd_test(): stop_arg       = %d\n", cmd->stop_arg);
+       PDEBUG("me4000_ai_do_cmd_test(): chanlist       = %d\n",
+               (unsigned int)cmd->chanlist);
+       PDEBUG("me4000_ai_do_cmd_test(): chanlist_len   = %d\n",
+               cmd->chanlist_len);
+
+       /* Only rounding flags are implemented */
+       cmd->flags &= TRIG_ROUND_NEAREST | TRIG_ROUND_UP | TRIG_ROUND_DOWN;
+
+       /* Round the timer arguments */
+       ai_round_cmd_args(dev, s, cmd, &init_ticks, &scan_ticks, &chan_ticks);
+
+       /*
+        * Stage 1. Check if the trigger sources are generally valid.
+        */
+       switch (cmd->start_src) {
+       case TRIG_NOW:
+       case TRIG_EXT:
+               break;
+       case TRIG_ANY:
+               cmd->start_src &= TRIG_NOW | TRIG_EXT;
+               err++;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start source\n",
+                       dev->minor);
+               cmd->start_src = TRIG_NOW;
+               err++;
+       }
+       switch (cmd->scan_begin_src) {
+       case TRIG_FOLLOW:
+       case TRIG_TIMER:
+       case TRIG_EXT:
+               break;
+       case TRIG_ANY:
+               cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT;
+               err++;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan begin source\n",
+                       dev->minor);
+               cmd->scan_begin_src = TRIG_FOLLOW;
+               err++;
+       }
+       switch (cmd->convert_src) {
+       case TRIG_TIMER:
+       case TRIG_EXT:
+               break;
+       case TRIG_ANY:
+               cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
+               err++;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert source\n",
+                       dev->minor);
+               cmd->convert_src = TRIG_TIMER;
+               err++;
+       }
+       switch (cmd->scan_end_src) {
+       case TRIG_NONE:
+       case TRIG_COUNT:
+               break;
+       case TRIG_ANY:
+               cmd->scan_end_src &= TRIG_NONE | TRIG_COUNT;
+               err++;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end source\n",
+                       dev->minor);
+               cmd->scan_end_src = TRIG_NONE;
+               err++;
+       }
+       switch (cmd->stop_src) {
+       case TRIG_NONE:
+       case TRIG_COUNT:
+               break;
+       case TRIG_ANY:
+               cmd->stop_src &= TRIG_NONE | TRIG_COUNT;
+               err++;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop source\n",
+                       dev->minor);
+               cmd->stop_src = TRIG_NONE;
+               err++;
+       }
+       if (err) {
+               return 1;
+       }
+
+       /*
+        * Stage 2. Check for trigger source conflicts.
+        */
+       if (cmd->start_src == TRIG_NOW &&
+               cmd->scan_begin_src == TRIG_TIMER &&
+               cmd->convert_src == TRIG_TIMER) {
+       } else if (cmd->start_src == TRIG_NOW &&
+               cmd->scan_begin_src == TRIG_FOLLOW &&
+               cmd->convert_src == TRIG_TIMER) {
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_TIMER &&
+               cmd->convert_src == TRIG_TIMER) {
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_FOLLOW &&
+               cmd->convert_src == TRIG_TIMER) {
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_TIMER) {
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_EXT) {
+       } else {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start trigger combination\n",
+                       dev->minor);
+               cmd->start_src = TRIG_NOW;
+               cmd->scan_begin_src = TRIG_FOLLOW;
+               cmd->convert_src = TRIG_TIMER;
+               err++;
+       }
+
+       if (cmd->stop_src == TRIG_NONE && cmd->scan_end_src == TRIG_NONE) {
+       } else if (cmd->stop_src == TRIG_COUNT &&
+               cmd->scan_end_src == TRIG_NONE) {
+       } else if (cmd->stop_src == TRIG_NONE &&
+               cmd->scan_end_src == TRIG_COUNT) {
+       } else if (cmd->stop_src == TRIG_COUNT &&
+               cmd->scan_end_src == TRIG_COUNT) {
+       } else {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop trigger combination\n",
+                       dev->minor);
+               cmd->stop_src = TRIG_NONE;
+               cmd->scan_end_src = TRIG_NONE;
+               err++;
+       }
+       if (err) {
+               return 2;
+       }
+
+       /*
+        * Stage 3. Check if arguments are generally valid.
+        */
+       if (cmd->chanlist_len < 1) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): No channel list\n",
+                       dev->minor);
+               cmd->chanlist_len = 1;
+               err++;
+       }
+       if (init_ticks < 66) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Start arg to low\n",
+                       dev->minor);
+               cmd->start_arg = 2000;
+               err++;
+       }
+       if (scan_ticks && scan_ticks < 67) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Scan begin arg to low\n",
+                       dev->minor);
+               cmd->scan_begin_arg = 2031;
+               err++;
+       }
+       if (chan_ticks < 66) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_do_cmd_test(): Convert arg to low\n",
+                       dev->minor);
+               cmd->convert_arg = 2000;
+               err++;
+       }
+       if (err) {
+               return 3;
+       }
+
+       /*
+        * Stage 4. Check for argument conflicts.
+        */
+       if (cmd->start_src == TRIG_NOW &&
+               cmd->scan_begin_src == TRIG_TIMER &&
+               cmd->convert_src == TRIG_TIMER) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+               if (chan_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
+                               dev->minor);
+                       cmd->convert_arg = 2000;        // 66 ticks at least
+                       err++;
+               }
+               if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
+                               dev->minor);
+                       cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;      // At least one tick more
+                       err++;
+               }
+       } else if (cmd->start_src == TRIG_NOW &&
+               cmd->scan_begin_src == TRIG_FOLLOW &&
+               cmd->convert_src == TRIG_TIMER) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+               if (chan_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
+                               dev->minor);
+                       cmd->convert_arg = 2000;        // 66 ticks at least
+                       err++;
+               }
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_TIMER &&
+               cmd->convert_src == TRIG_TIMER) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+               if (chan_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
+                               dev->minor);
+                       cmd->convert_arg = 2000;        // 66 ticks at least
+                       err++;
+               }
+               if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
+                               dev->minor);
+                       cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;      // At least one tick more
+                       err++;
+               }
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_FOLLOW &&
+               cmd->convert_src == TRIG_TIMER) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+               if (chan_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
+                               dev->minor);
+                       cmd->convert_arg = 2000;        // 66 ticks at least
+                       err++;
+               }
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_TIMER) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+               if (chan_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
+                               dev->minor);
+                       cmd->convert_arg = 2000;        // 66 ticks at least
+                       err++;
+               }
+       } else if (cmd->start_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_EXT &&
+               cmd->convert_src == TRIG_EXT) {
+
+               /* Check timer arguments */
+               if (init_ticks < ME4000_AI_MIN_TICKS) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
+                               dev->minor);
+                       cmd->start_arg = 2000;  // 66 ticks at least
+                       err++;
+               }
+       }
+       if (cmd->stop_src == TRIG_COUNT) {
+               if (cmd->stop_arg == 0) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop arg\n",
+                               dev->minor);
+                       cmd->stop_arg = 1;
+                       err++;
+               }
+       }
+       if (cmd->scan_end_src == TRIG_COUNT) {
+               if (cmd->scan_end_arg == 0) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
+                               dev->minor);
+                       cmd->scan_end_arg = 1;
+                       err++;
+               }
+       }
+       if (err) {
+               return 4;
+       }
+
+       /*
+        * Stage 5. Check the channel list.
+        */
+       if (ai_check_chanlist(dev, s, cmd))
+               return 5;
+
+       return 0;
+}
+
+static irqreturn_t me4000_ai_isr(int irq, void *dev_id PT_REGS_ARG)
+{
+       unsigned int tmp;
+       comedi_device *dev = dev_id;
+       comedi_subdevice *s = dev->subdevices;
+       me4000_ai_context_t *ai_context = &info->ai_context;
+       int i;
+       int c = 0;
+       long lval;
+
+       ISR_PDEBUG("me4000_ai_isr() is executed\n");
+
+       if (!dev->attached) {
+               ISR_PDEBUG("me4000_ai_isr() premature interrupt\n");
+               return IRQ_NONE;
+       }
+
+       /* Reset all events */
+       s->async->events = 0;
+
+       /* Check if irq number is right */
+       if (irq != ai_context->irq) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ai_isr(): Incorrect interrupt num: %d\n",
+                       dev->minor, irq);
+               return IRQ_HANDLED;
+       }
+
+       if (me4000_inl(dev,
+                       ai_context->
+                       irq_status_reg) & ME4000_IRQ_STATUS_BIT_AI_HF) {
+               ISR_PDEBUG
+                       ("me4000_ai_isr(): Fifo half full interrupt occured\n");
+
+               /* Read status register to find out what happened */
+               tmp = me4000_inl(dev, ai_context->ctrl_reg);
+
+               if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
+                       !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) &&
+                       (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+                       ISR_PDEBUG("me4000_ai_isr(): Fifo full\n");
+                       c = ME4000_AI_FIFO_COUNT;
+
+                       /* FIFO overflow, so stop conversion and disable all interrupts */
+                       tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+                       tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+                               ME4000_AI_CTRL_BIT_SC_IRQ);
+                       me4000_outl(dev, tmp, ai_context->ctrl_reg);
+
+                       s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_isr(): FIFO overflow\n",
+                               dev->minor);
+               } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
+                       && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
+                       && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+                       ISR_PDEBUG("me4000_ai_isr(): Fifo half full\n");
+
+                       s->async->events |= COMEDI_CB_BLOCK;
+
+                       c = ME4000_AI_FIFO_COUNT / 2;
+               } else {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_isr(): Can't determine state of fifo\n",
+                               dev->minor);
+                       c = 0;
+
+                       /* Undefined state, so stop conversion and disable all interrupts */
+                       tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+                       tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+                               ME4000_AI_CTRL_BIT_SC_IRQ);
+                       me4000_outl(dev, tmp, ai_context->ctrl_reg);
+
+                       s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_ai_isr(): Undefined FIFO state\n",
+                               dev->minor);
+               }
+
+               ISR_PDEBUG("me4000_ai_isr(): Try to read %d values\n", c);
+
+               for (i = 0; i < c; i++) {
+                       /* Read value from data fifo */
+                       lval = inl(ai_context->data_reg) & 0xFFFF;
+                       lval ^= 0x8000;
+
+                       if (!comedi_buf_put(s->async, lval)) {
+                               /* Buffer overflow, so stop conversion and disable all interrupts */
+                               tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+                               tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+                                       ME4000_AI_CTRL_BIT_SC_IRQ);
+                               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+
+                               s->async->events |= COMEDI_CB_OVERFLOW;
+
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n",
+                                       dev->minor);
+
+                               break;
+                       }
+               }
+
+               /* Work is done, so reset the interrupt */
+               ISR_PDEBUG("me4000_ai_isr(): Reset fifo half full interrupt\n");
+               tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+               tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+       }
+
+       if (me4000_inl(dev,
+                       ai_context->
+                       irq_status_reg) & ME4000_IRQ_STATUS_BIT_SC) {
+               ISR_PDEBUG
+                       ("me4000_ai_isr(): Sample counter interrupt occured\n");
+
+               s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA;
+
+               /* Acquisition is complete, so stop conversion and disable all interrupts */
+               tmp = me4000_inl(dev, ai_context->ctrl_reg);
+               tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+               tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ);
+               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+
+               /* Poll data until fifo empty */
+               while (inl(ai_context->ctrl_reg) & ME4000_AI_STATUS_BIT_EF_DATA) {
+                       /* Read value from data fifo */
+                       lval = inl(ai_context->data_reg) & 0xFFFF;
+                       lval ^= 0x8000;
+
+                       if (!comedi_buf_put(s->async, lval)) {
+                               printk(KERN_ERR
+                                       "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n",
+                                       dev->minor);
+                               s->async->events |= COMEDI_CB_OVERFLOW;
+                               break;
+                       }
+               }
+
+               /* Work is done, so reset the interrupt */
+               ISR_PDEBUG
+                       ("me4000_ai_isr(): Reset interrupt from sample counter\n");
+               tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+               tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+               me4000_outl(dev, tmp, ai_context->ctrl_reg);
+       }
+
+       ISR_PDEBUG("me4000_ai_isr(): Events = 0x%X\n", s->async->events);
+
+       if (s->async->events)
+               comedi_event(dev, s);
+
+       return IRQ_HANDLED;
+}
+
+/*=============================================================================
+  Analog output section
+  ===========================================================================*/
+
+static int me4000_ao_insn_write(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+
+       int chan = CR_CHAN(insn->chanspec);
+       int rang = CR_RANGE(insn->chanspec);
+       int aref = CR_AREF(insn->chanspec);
+       unsigned long tmp;
+
+       CALL_PDEBUG("In me4000_ao_insn_write()\n");
+
+       if (insn->n == 0) {
+               return 0;
+       } else if (insn->n > 1) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ao_insn_write(): Invalid instruction length %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       if (chan >= thisboard->ao.count) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ao_insn_write(): Invalid channel %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       if (rang != 0) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ao_insn_write(): Invalid range %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       if (aref != AREF_GROUND && aref != AREF_COMMON) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_ao_insn_write(): Invalid aref %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       /* Stop any running conversion */
+       tmp = me4000_inl(dev, info->ao_context[chan].ctrl_reg);
+       tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP;
+       me4000_outl(dev, tmp, info->ao_context[chan].ctrl_reg);
+
+       /* Clear control register and set to single mode */
+       me4000_outl(dev, 0x0, info->ao_context[chan].ctrl_reg);
+
+       /* Write data value */
+       me4000_outl(dev, data[0], info->ao_context[chan].single_reg);
+
+       /* Store in the mirror */
+       info->ao_context[chan].mirror = data[0];
+
+       return 1;
+}
+
+static int me4000_ao_insn_read(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       int chan = CR_CHAN(insn->chanspec);
+
+       if (insn->n == 0) {
+               return 0;
+       } else if (insn->n > 1) {
+               printk("comedi%d: me4000: me4000_ao_insn_read(): Invalid instruction length\n", dev->minor);
+               return -EINVAL;
+       }
+
+       data[0] = info->ao_context[chan].mirror;
+
+       return 1;
+}
+
+/*=============================================================================
+  Digital I/O section
+  ===========================================================================*/
+
+static int me4000_dio_insn_bits(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+
+       CALL_PDEBUG("In me4000_dio_insn_bits()\n");
+
+       /* Length of data must be 2 (mask and new data, see below) */
+       if (insn->n == 0) {
+               return 0;
+       }
+       if (insn->n != 2) {
+               printk("comedi%d: me4000: me4000_dio_insn_bits(): Invalid instruction length\n", dev->minor);
+               return -EINVAL;
+       }
+
+       /*
+        * The insn data consists of a mask in data[0] and the new data
+        * in data[1]. The mask defines which bits we are concerning about.
+        * The new data must be anded with the mask.
+        * Each channel corresponds to a bit.
+        */
+       if (data[0]) {
+               /* Check if requested ports are configured for output */
+               if ((s->io_bits & data[0]) != data[0])
+                       return -EIO;
+
+               s->state &= ~data[0];
+               s->state |= data[0] & data[1];
+
+               /* Write out the new digital output lines */
+               me4000_outl(dev, (s->state >> 0) & 0xFF,
+                       info->dio_context.port_0_reg);
+               me4000_outl(dev, (s->state >> 8) & 0xFF,
+                       info->dio_context.port_1_reg);
+               me4000_outl(dev, (s->state >> 16) & 0xFF,
+                       info->dio_context.port_2_reg);
+               me4000_outl(dev, (s->state >> 24) & 0xFF,
+                       info->dio_context.port_3_reg);
+       }
+
+       /* On return, data[1] contains the value of
+          the digital input and output lines. */
+       data[1] =
+               ((me4000_inl(dev, info->dio_context.port_0_reg) & 0xFF) << 0) |
+               ((me4000_inl(dev, info->dio_context.port_1_reg) & 0xFF) << 8) |
+               ((me4000_inl(dev, info->dio_context.port_2_reg) & 0xFF) << 16) |
+               ((me4000_inl(dev, info->dio_context.port_3_reg) & 0xFF) << 24);
+
+       return 2;
+}
+
+static int me4000_dio_insn_config(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       unsigned long tmp;
+       int chan = CR_CHAN(insn->chanspec);
+
+       CALL_PDEBUG("In me4000_dio_insn_config()\n");
+
+       if (data[0] == INSN_CONFIG_DIO_QUERY) {
+               data[1] =
+                       (s->
+                       io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
+               return insn->n;
+       }
+
+       /*
+        * 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.
+        * On the ME-4000 it is only possible to switch port wise (8 bit)
+        */
+
+       tmp = me4000_inl(dev, info->dio_context.ctrl_reg);
+
+       if (data[0] == COMEDI_OUTPUT) {
+               if (chan < 8) {
+                       s->io_bits |= 0xFF;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 |
+                               ME4000_DIO_CTRL_BIT_MODE_1);
+                       tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
+               } else if (chan < 16) {
+                       /*
+                        * Chech for optoisolated ME-4000 version. If one the first
+                        * port is a fixed output port and the second is a fixed input port.
+                        */
+                       if (!me4000_inl(dev, info->dio_context.dir_reg))
+                               return -ENODEV;
+
+                       s->io_bits |= 0xFF00;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 |
+                               ME4000_DIO_CTRL_BIT_MODE_3);
+                       tmp |= ME4000_DIO_CTRL_BIT_MODE_2;
+               } else if (chan < 24) {
+                       s->io_bits |= 0xFF0000;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 |
+                               ME4000_DIO_CTRL_BIT_MODE_5);
+                       tmp |= ME4000_DIO_CTRL_BIT_MODE_4;
+               } else if (chan < 32) {
+                       s->io_bits |= 0xFF000000;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 |
+                               ME4000_DIO_CTRL_BIT_MODE_7);
+                       tmp |= ME4000_DIO_CTRL_BIT_MODE_6;
+               } else {
+                       return -EINVAL;
+               }
+       } else {
+               if (chan < 8) {
+                       /*
+                        * Chech for optoisolated ME-4000 version. If one the first
+                        * port is a fixed output port and the second is a fixed input port.
+                        */
+                       if (!me4000_inl(dev, info->dio_context.dir_reg))
+                               return -ENODEV;
+
+                       s->io_bits &= ~0xFF;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 |
+                               ME4000_DIO_CTRL_BIT_MODE_1);
+               } else if (chan < 16) {
+                       s->io_bits &= ~0xFF00;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 |
+                               ME4000_DIO_CTRL_BIT_MODE_3);
+               } else if (chan < 24) {
+                       s->io_bits &= ~0xFF0000;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 |
+                               ME4000_DIO_CTRL_BIT_MODE_5);
+               } else if (chan < 32) {
+                       s->io_bits &= ~0xFF000000;
+                       tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 |
+                               ME4000_DIO_CTRL_BIT_MODE_7);
+               } else {
+                       return -EINVAL;
+               }
+       }
+
+       me4000_outl(dev, tmp, info->dio_context.ctrl_reg);
+
+       return 1;
+}
+
+/*=============================================================================
+  Counter section
+  ===========================================================================*/
+
+static int cnt_reset(comedi_device * dev, unsigned int channel)
+{
+
+       CALL_PDEBUG("In cnt_reset()\n");
+
+       switch (channel) {
+       case 0:
+               me4000_outb(dev, 0x30, info->cnt_context.ctrl_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg);
+               break;
+       case 1:
+               me4000_outb(dev, 0x70, info->cnt_context.ctrl_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg);
+               break;
+       case 2:
+               me4000_outb(dev, 0xB0, info->cnt_context.ctrl_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg);
+               me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg);
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: cnt_reset(): Invalid channel\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int cnt_config(comedi_device * dev, unsigned int channel,
+       unsigned int mode)
+{
+       int tmp = 0;
+
+       CALL_PDEBUG("In cnt_config()\n");
+
+       switch (channel) {
+       case 0:
+               tmp |= ME4000_CNT_COUNTER_0;
+               break;
+       case 1:
+               tmp |= ME4000_CNT_COUNTER_1;
+               break;
+       case 2:
+               tmp |= ME4000_CNT_COUNTER_2;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: cnt_config(): Invalid channel\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       switch (mode) {
+       case 0:
+               tmp |= ME4000_CNT_MODE_0;
+               break;
+       case 1:
+               tmp |= ME4000_CNT_MODE_1;
+               break;
+       case 2:
+               tmp |= ME4000_CNT_MODE_2;
+               break;
+       case 3:
+               tmp |= ME4000_CNT_MODE_3;
+               break;
+       case 4:
+               tmp |= ME4000_CNT_MODE_4;
+               break;
+       case 5:
+               tmp |= ME4000_CNT_MODE_5;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: cnt_config(): Invalid counter mode\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       /* Write the control word */
+       tmp |= 0x30;
+       me4000_outb(dev, tmp, info->cnt_context.ctrl_reg);
+
+       return 0;
+}
+
+static int me4000_cnt_insn_config(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+
+       int err;
+
+       CALL_PDEBUG("In me4000_cnt_insn_config()\n");
+
+       switch (data[0]) {
+       case GPCT_RESET:
+               if (insn->n != 1) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n",
+                               dev->minor, insn->n);
+                       return -EINVAL;
+               }
+
+               err = cnt_reset(dev, insn->chanspec);
+               if (err)
+                       return err;
+               break;
+       case GPCT_SET_OPERATION:
+               if (insn->n != 2) {
+                       printk(KERN_ERR
+                               "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n",
+                               dev->minor, insn->n);
+                       return -EINVAL;
+               }
+
+               err = cnt_config(dev, insn->chanspec, data[1]);
+               if (err)
+                       return err;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction\n",
+                       dev->minor);
+               return -EINVAL;
+       }
+
+       return 2;
+}
+
+static int me4000_cnt_insn_read(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+
+       unsigned short tmp;
+
+       CALL_PDEBUG("In me4000_cnt_insn_read()\n");
+
+       if (insn->n == 0) {
+               return 0;
+       }
+       if (insn->n > 1) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_cnt_insn_read(): Invalid instruction length %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       switch (insn->chanspec) {
+       case 0:
+               tmp = me4000_inb(dev, info->cnt_context.counter_0_reg);
+               data[0] = tmp;
+               tmp = me4000_inb(dev, info->cnt_context.counter_0_reg);
+               data[0] |= tmp << 8;
+               break;
+       case 1:
+               tmp = me4000_inb(dev, info->cnt_context.counter_1_reg);
+               data[0] = tmp;
+               tmp = me4000_inb(dev, info->cnt_context.counter_1_reg);
+               data[0] |= tmp << 8;
+               break;
+       case 2:
+               tmp = me4000_inb(dev, info->cnt_context.counter_2_reg);
+               data[0] = tmp;
+               tmp = me4000_inb(dev, info->cnt_context.counter_2_reg);
+               data[0] |= tmp << 8;
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_cnt_insn_read(): Invalid channel %d\n",
+                       dev->minor, insn->chanspec);
+               return -EINVAL;
+       }
+
+       return 1;
+}
+
+static int me4000_cnt_insn_write(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+
+       unsigned short tmp;
+
+       CALL_PDEBUG("In me4000_cnt_insn_write()\n");
+
+       if (insn->n == 0) {
+               return 0;
+       } else if (insn->n > 1) {
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_cnt_insn_write(): Invalid instruction length %d\n",
+                       dev->minor, insn->n);
+               return -EINVAL;
+       }
+
+       switch (insn->chanspec) {
+       case 0:
+               tmp = data[0] & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_0_reg);
+               tmp = (data[0] >> 8) & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_0_reg);
+               break;
+       case 1:
+               tmp = data[0] & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_1_reg);
+               tmp = (data[0] >> 8) & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_1_reg);
+               break;
+       case 2:
+               tmp = data[0] & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_2_reg);
+               tmp = (data[0] >> 8) & 0xFF;
+               me4000_outb(dev, tmp, info->cnt_context.counter_2_reg);
+               break;
+       default:
+               printk(KERN_ERR
+                       "comedi%d: me4000: me4000_cnt_insn_write(): Invalid channel %d\n",
+                       dev->minor, insn->chanspec);
+               return -EINVAL;
+       }
+
+       return 1;
+}
+
+COMEDI_PCI_INITCLEANUP(driver_me4000, me4000_pci_table);
diff --git a/drivers/staging/comedi/drivers/me4000.h b/drivers/staging/comedi/drivers/me4000.h
new file mode 100644 (file)
index 0000000..f12b887
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+    me4000.h
+    Register descriptions and defines for the ME-4000 board family
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1998-9 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.
+
+*/
+
+#ifndef _ME4000_H_
+#define _ME4000_H_
+
+/*=============================================================================
+  Debug section
+  ===========================================================================*/
+
+#undef ME4000_CALL_DEBUG       // Debug function entry and exit
+#undef ME4000_PORT_DEBUG       // Debug port access
+#undef ME4000_ISR_DEBUG                // Debug the interrupt service routine
+#undef ME4000_DEBUG            // General purpose debug masseges
+
+#ifdef ME4000_CALL_DEBUG
+#undef CALL_PDEBUG
+#define CALL_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args)
+#else
+# define CALL_PDEBUG(fmt, args...)     // no debugging, do nothing
+#endif
+
+#ifdef ME4000_PORT_DEBUG
+#undef PORT_PDEBUG
+#define PORT_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor,  ##args)
+#else
+#define PORT_PDEBUG(fmt, args...)      // no debugging, do nothing
+#endif
+
+#ifdef ME4000_ISR_DEBUG
+#undef ISR_PDEBUG
+#define ISR_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor,  ##args)
+#else
+#define ISR_PDEBUG(fmt, args...)       // no debugging, do nothing
+#endif
+
+#ifdef ME4000_DEBUG
+#undef PDEBUG
+#define PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor,  ##args)
+#else
+#define PDEBUG(fmt, args...)   // no debugging, do nothing
+#endif
+
+/*=============================================================================
+  PCI vendor and device IDs
+  ===========================================================================*/
+
+#define PCI_VENDOR_ID_MEILHAUS 0x1402
+
+#define PCI_DEVICE_ID_MEILHAUS_ME4650  0x4650  // Low Cost version
+
+#define PCI_DEVICE_ID_MEILHAUS_ME4660  0x4660  // Standard version
+#define PCI_DEVICE_ID_MEILHAUS_ME4660I 0x4661  // Isolated version
+#define PCI_DEVICE_ID_MEILHAUS_ME4660S 0x4662  // Standard version with Sample and Hold
+#define PCI_DEVICE_ID_MEILHAUS_ME4660IS        0x4663  // Isolated version with Sample and Hold
+
+#define PCI_DEVICE_ID_MEILHAUS_ME4670  0x4670  // Standard version
+#define PCI_DEVICE_ID_MEILHAUS_ME4670I 0x4671  // Isolated version
+#define PCI_DEVICE_ID_MEILHAUS_ME4670S 0x4672  // Standard version with Sample and Hold
+#define PCI_DEVICE_ID_MEILHAUS_ME4670IS        0x4673  // Isolated version with Sample and Hold
+
+#define PCI_DEVICE_ID_MEILHAUS_ME4680  0x4680  // Standard version
+#define PCI_DEVICE_ID_MEILHAUS_ME4680I 0x4681  // Isolated version
+#define PCI_DEVICE_ID_MEILHAUS_ME4680S 0x4682  // Standard version with Sample and Hold
+#define PCI_DEVICE_ID_MEILHAUS_ME4680IS        0x4683  // Isolated version with Sample and Hold
+
+/*=============================================================================
+  ME-4000 base register offsets
+  ===========================================================================*/
+
+#define ME4000_AO_00_CTRL_REG                  0x00    // R/W
+#define ME4000_AO_00_STATUS_REG                        0x04    // R/_
+#define ME4000_AO_00_FIFO_REG                  0x08    // _/W
+#define ME4000_AO_00_SINGLE_REG                        0x0C    // R/W
+#define ME4000_AO_00_TIMER_REG                 0x10    // _/W
+
+#define ME4000_AO_01_CTRL_REG                  0x18    // R/W
+#define ME4000_AO_01_STATUS_REG                        0x1C    // R/_
+#define ME4000_AO_01_FIFO_REG                  0x20    // _/W
+#define ME4000_AO_01_SINGLE_REG                        0x24    // R/W
+#define ME4000_AO_01_TIMER_REG                 0x28    // _/W
+
+#define ME4000_AO_02_CTRL_REG                  0x30    // R/W
+#define ME4000_AO_02_STATUS_REG                        0x34    // R/_
+#define ME4000_AO_02_FIFO_REG                  0x38    // _/W
+#define ME4000_AO_02_SINGLE_REG                        0x3C    // R/W
+#define ME4000_AO_02_TIMER_REG                 0x40    // _/W
+
+#define ME4000_AO_03_CTRL_REG                  0x48    // R/W
+#define ME4000_AO_03_STATUS_REG                        0x4C    // R/_
+#define ME4000_AO_03_FIFO_REG                  0x50    // _/W
+#define ME4000_AO_03_SINGLE_REG                        0x54    // R/W
+#define ME4000_AO_03_TIMER_REG                 0x58    // _/W
+
+#define ME4000_AI_CTRL_REG                     0x74    // _/W
+#define ME4000_AI_STATUS_REG                   0x74    // R/_
+#define ME4000_AI_CHANNEL_LIST_REG             0x78    // _/W
+#define ME4000_AI_DATA_REG                     0x7C    // R/_
+#define ME4000_AI_CHAN_TIMER_REG               0x80    // _/W
+#define ME4000_AI_CHAN_PRE_TIMER_REG           0x84    // _/W
+#define ME4000_AI_SCAN_TIMER_LOW_REG           0x88    // _/W
+#define ME4000_AI_SCAN_TIMER_HIGH_REG          0x8C    // _/W
+#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG       0x90    // _/W
+#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG      0x94    // _/W
+#define ME4000_AI_START_REG                    0x98    // R/_
+
+#define ME4000_IRQ_STATUS_REG                  0x9C    // R/_
+
+#define ME4000_DIO_PORT_0_REG                  0xA0    // R/W
+#define ME4000_DIO_PORT_1_REG                  0xA4    // R/W
+#define ME4000_DIO_PORT_2_REG                  0xA8    // R/W
+#define ME4000_DIO_PORT_3_REG                  0xAC    // R/W
+#define ME4000_DIO_DIR_REG                     0xB0    // R/W
+
+#define ME4000_AO_LOADSETREG_XX                        0xB4    // R/W
+
+#define ME4000_DIO_CTRL_REG                    0xB8    // R/W
+
+#define ME4000_AO_DEMUX_ADJUST_REG             0xBC    // -/W
+
+#define ME4000_AI_SAMPLE_COUNTER_REG           0xC0    // _/W
+
+/*=============================================================================
+  Value to adjust Demux
+  ===========================================================================*/
+
+#define ME4000_AO_DEMUX_ADJUST_VALUE            0x4C
+
+/*=============================================================================
+  Counter base register offsets
+  ===========================================================================*/
+
+#define ME4000_CNT_COUNTER_0_REG               0x00
+#define ME4000_CNT_COUNTER_1_REG               0x01
+#define ME4000_CNT_COUNTER_2_REG               0x02
+#define ME4000_CNT_CTRL_REG                    0x03
+
+/*=============================================================================
+  PLX base register offsets
+  ===========================================================================*/
+
+#define PLX_INTCSR     0x4C    // Interrupt control and status register
+#define PLX_ICR                0x50    // Initialization control register
+
+/*=============================================================================
+  Bits for the PLX_ICSR register
+  ===========================================================================*/
+
+#define PLX_INTCSR_LOCAL_INT1_EN             0x01      // If set, local interrupt 1 is enabled (r/w)
+#define PLX_INTCSR_LOCAL_INT1_POL            0x02      // If set, local interrupt 1 polarity is active high (r/w)
+#define PLX_INTCSR_LOCAL_INT1_STATE          0x04      // If set, local interrupt 1 is active (r/_)
+#define PLX_INTCSR_LOCAL_INT2_EN             0x08      // If set, local interrupt 2 is enabled (r/w)
+#define PLX_INTCSR_LOCAL_INT2_POL            0x10      // If set, local interrupt 2 polarity is active high (r/w)
+#define PLX_INTCSR_LOCAL_INT2_STATE          0x20      // If set, local interrupt 2 is active  (r/_)
+#define PLX_INTCSR_PCI_INT_EN                0x40      // If set, PCI interrupt is enabled (r/w)
+#define PLX_INTCSR_SOFT_INT                  0x80      // If set, a software interrupt is generated (r/w)
+
+/*=============================================================================
+  Bits for the PLX_ICR register
+  ===========================================================================*/
+
+#define PLX_ICR_BIT_EEPROM_CLOCK_SET           0x01000000
+#define PLX_ICR_BIT_EEPROM_CHIP_SELECT         0x02000000
+#define PLX_ICR_BIT_EEPROM_WRITE               0x04000000
+#define PLX_ICR_BIT_EEPROM_READ                        0x08000000
+#define PLX_ICR_BIT_EEPROM_VALID               0x10000000
+
+#define PLX_ICR_MASK_EEPROM                    0x1F000000
+
+#define EEPROM_DELAY                           1
+
+/*=============================================================================
+  Bits for the ME4000_AO_CTRL_REG register
+  ===========================================================================*/
+
+#define ME4000_AO_CTRL_BIT_MODE_0              0x001
+#define ME4000_AO_CTRL_BIT_MODE_1              0x002
+#define ME4000_AO_CTRL_MASK_MODE               0x003
+#define ME4000_AO_CTRL_BIT_STOP                        0x004
+#define ME4000_AO_CTRL_BIT_ENABLE_FIFO         0x008
+#define ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG      0x010
+#define ME4000_AO_CTRL_BIT_EX_TRIG_EDGE                0x020
+#define ME4000_AO_CTRL_BIT_IMMEDIATE_STOP      0x080
+#define ME4000_AO_CTRL_BIT_ENABLE_DO           0x100
+#define ME4000_AO_CTRL_BIT_ENABLE_IRQ          0x200
+#define ME4000_AO_CTRL_BIT_RESET_IRQ           0x400
+
+/*=============================================================================
+  Bits for the ME4000_AO_STATUS_REG register
+  ===========================================================================*/
+
+#define ME4000_AO_STATUS_BIT_FSM               0x01
+#define ME4000_AO_STATUS_BIT_FF                        0x02
+#define ME4000_AO_STATUS_BIT_HF                        0x04
+#define ME4000_AO_STATUS_BIT_EF                        0x08
+
+/*=============================================================================
+  Bits for the ME4000_AI_CTRL_REG register
+  ===========================================================================*/
+
+#define ME4000_AI_CTRL_BIT_MODE_0              0x00000001
+#define ME4000_AI_CTRL_BIT_MODE_1              0x00000002
+#define ME4000_AI_CTRL_BIT_MODE_2              0x00000004
+#define ME4000_AI_CTRL_BIT_SAMPLE_HOLD         0x00000008
+#define ME4000_AI_CTRL_BIT_IMMEDIATE_STOP      0x00000010
+#define ME4000_AI_CTRL_BIT_STOP                        0x00000020
+#define ME4000_AI_CTRL_BIT_CHANNEL_FIFO                0x00000040
+#define ME4000_AI_CTRL_BIT_DATA_FIFO           0x00000080
+#define ME4000_AI_CTRL_BIT_FULLSCALE           0x00000100
+#define ME4000_AI_CTRL_BIT_OFFSET              0x00000200
+#define ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG      0x00000400
+#define ME4000_AI_CTRL_BIT_EX_TRIG             0x00000800
+#define ME4000_AI_CTRL_BIT_EX_TRIG_FALLING     0x00001000
+#define ME4000_AI_CTRL_BIT_EX_IRQ              0x00002000
+#define ME4000_AI_CTRL_BIT_EX_IRQ_RESET                0x00004000
+#define ME4000_AI_CTRL_BIT_LE_IRQ              0x00008000
+#define ME4000_AI_CTRL_BIT_LE_IRQ_RESET                0x00010000
+#define ME4000_AI_CTRL_BIT_HF_IRQ              0x00020000
+#define ME4000_AI_CTRL_BIT_HF_IRQ_RESET                0x00040000
+#define ME4000_AI_CTRL_BIT_SC_IRQ              0x00080000
+#define ME4000_AI_CTRL_BIT_SC_IRQ_RESET                0x00100000
+#define ME4000_AI_CTRL_BIT_SC_RELOAD           0x00200000
+#define ME4000_AI_CTRL_BIT_EX_TRIG_BOTH                0x80000000
+
+/*=============================================================================
+  Bits for the ME4000_AI_STATUS_REG register
+  ===========================================================================*/
+
+#define ME4000_AI_STATUS_BIT_EF_CHANNEL                0x00400000
+#define ME4000_AI_STATUS_BIT_HF_CHANNEL                0x00800000
+#define ME4000_AI_STATUS_BIT_FF_CHANNEL                0x01000000
+#define ME4000_AI_STATUS_BIT_EF_DATA           0x02000000
+#define ME4000_AI_STATUS_BIT_HF_DATA           0x04000000
+#define ME4000_AI_STATUS_BIT_FF_DATA           0x08000000
+#define ME4000_AI_STATUS_BIT_LE                        0x10000000
+#define ME4000_AI_STATUS_BIT_FSM               0x20000000
+
+/*=============================================================================
+  Bits for the ME4000_IRQ_STATUS_REG register
+  ===========================================================================*/
+
+#define ME4000_IRQ_STATUS_BIT_EX               0x01
+#define ME4000_IRQ_STATUS_BIT_LE               0x02
+#define ME4000_IRQ_STATUS_BIT_AI_HF            0x04
+#define ME4000_IRQ_STATUS_BIT_AO_0_HF          0x08
+#define ME4000_IRQ_STATUS_BIT_AO_1_HF          0x10
+#define ME4000_IRQ_STATUS_BIT_AO_2_HF          0x20
+#define ME4000_IRQ_STATUS_BIT_AO_3_HF          0x40
+#define ME4000_IRQ_STATUS_BIT_SC               0x80
+
+/*=============================================================================
+  Bits for the ME4000_DIO_CTRL_REG register
+  ===========================================================================*/
+
+#define ME4000_DIO_CTRL_BIT_MODE_0             0x0001
+#define ME4000_DIO_CTRL_BIT_MODE_1             0x0002
+#define ME4000_DIO_CTRL_BIT_MODE_2             0x0004
+#define ME4000_DIO_CTRL_BIT_MODE_3             0x0008
+#define ME4000_DIO_CTRL_BIT_MODE_4             0x0010
+#define ME4000_DIO_CTRL_BIT_MODE_5             0x0020
+#define ME4000_DIO_CTRL_BIT_MODE_6             0x0040
+#define ME4000_DIO_CTRL_BIT_MODE_7             0x0080
+
+#define ME4000_DIO_CTRL_BIT_FUNCTION_0         0x0100
+#define ME4000_DIO_CTRL_BIT_FUNCTION_1         0x0200
+
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_0                0x0400
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_1                0x0800
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_2                0x1000
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_3                0x2000
+
+/*=============================================================================
+  Information about the hardware capabilities
+  ===========================================================================*/
+
+typedef struct me4000_ao_info {
+       int count;
+       int fifo_count;
+} me4000_ao_info_t;
+
+typedef struct me4000_ai_info {
+       int count;
+       int sh_count;
+       int diff_count;
+       int ex_trig_analog;
+} me4000_ai_info_t;
+
+typedef struct me4000_dio_info {
+       int count;
+} me4000_dio_info_t;
+
+typedef struct me4000_cnt_info {
+       int count;
+} me4000_cnt_info_t;
+
+typedef struct me4000_board {
+       const char *name;
+       unsigned short device_id;
+       me4000_ao_info_t ao;
+       me4000_ai_info_t ai;
+       me4000_dio_info_t dio;
+       me4000_cnt_info_t cnt;
+} me4000_board_t;
+
+#define thisboard ((const me4000_board_t *)dev->board_ptr)
+
+/*=============================================================================
+  Global board and subdevice information structures
+  ===========================================================================*/
+
+typedef struct me4000_ao_context {
+       int irq;
+
+       unsigned long mirror;   // Store the last written value
+
+       unsigned long ctrl_reg;
+       unsigned long status_reg;
+       unsigned long fifo_reg;
+       unsigned long single_reg;
+       unsigned long timer_reg;
+       unsigned long irq_status_reg;
+       unsigned long preload_reg;
+} me4000_ao_context_t;
+
+typedef struct me4000_ai_context {
+       int irq;
+
+       unsigned long ctrl_reg;
+       unsigned long status_reg;
+       unsigned long channel_list_reg;
+       unsigned long data_reg;
+       unsigned long chan_timer_reg;
+       unsigned long chan_pre_timer_reg;
+       unsigned long scan_timer_low_reg;
+       unsigned long scan_timer_high_reg;
+       unsigned long scan_pre_timer_low_reg;
+       unsigned long scan_pre_timer_high_reg;
+       unsigned long start_reg;
+       unsigned long irq_status_reg;
+       unsigned long sample_counter_reg;
+} me4000_ai_context_t;
+
+typedef struct me4000_dio_context {
+       unsigned long dir_reg;
+       unsigned long ctrl_reg;
+       unsigned long port_0_reg;
+       unsigned long port_1_reg;
+       unsigned long port_2_reg;
+       unsigned long port_3_reg;
+} me4000_dio_context_t;
+
+typedef struct me4000_cnt_context {
+       unsigned long ctrl_reg;
+       unsigned long counter_0_reg;
+       unsigned long counter_1_reg;
+       unsigned long counter_2_reg;
+} me4000_cnt_context_t;
+
+typedef struct me4000_info {
+       unsigned long plx_regbase;      // PLX configuration space base address
+       unsigned long me4000_regbase;   // Base address of the ME4000
+       unsigned long timer_regbase;    // Base address of the timer circuit
+       unsigned long program_regbase;  // Base address to set the program pin for the xilinx
+
+       unsigned long plx_regbase_size; // PLX register set space
+       unsigned long me4000_regbase_size;      // ME4000 register set space
+       unsigned long timer_regbase_size;       // Timer circuit register set space
+       unsigned long program_regbase_size;     // Size of program base address of the ME4000
+
+       unsigned int serial_no; // Serial number of the board
+       unsigned char hw_revision;      // Hardware revision of the board
+       unsigned short vendor_id;       // Meilhaus vendor id
+       unsigned short device_id;       // Device id
+
+       struct pci_dev *pci_dev_p;      // General PCI information
+
+       unsigned int irq;       // IRQ assigned from the PCI BIOS
+
+       struct me4000_ai_context ai_context;    // Analog input  specific context
+       struct me4000_ao_context ao_context[4]; // Vector with analog output specific context
+       struct me4000_dio_context dio_context;  // Digital I/O specific context
+       struct me4000_cnt_context cnt_context;  // Counter specific context
+} me4000_info_t;
+
+#define info   ((me4000_info_t *)dev->private)
+
+/*-----------------------------------------------------------------------------
+  Defines for analog input
+ ----------------------------------------------------------------------------*/
+
+/* General stuff */
+#define ME4000_AI_FIFO_COUNT                   2048
+
+#define ME4000_AI_MIN_TICKS                    66
+#define ME4000_AI_MIN_SAMPLE_TIME              2000    // Minimum sample time [ns]
+#define ME4000_AI_BASE_FREQUENCY               (unsigned int) 33E6
+
+/* Channel list defines and masks */
+#define ME4000_AI_CHANNEL_LIST_COUNT           1024
+
+#define ME4000_AI_LIST_INPUT_SINGLE_ENDED      0x000
+#define ME4000_AI_LIST_INPUT_DIFFERENTIAL      0x020
+
+#define ME4000_AI_LIST_RANGE_BIPOLAR_10                0x000
+#define ME4000_AI_LIST_RANGE_BIPOLAR_2_5       0x040
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_10       0x080
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_2_5      0x0C0
+
+#define ME4000_AI_LIST_LAST_ENTRY              0x100
+
+/*-----------------------------------------------------------------------------
+  Defines for counters
+ ----------------------------------------------------------------------------*/
+
+#define ME4000_CNT_COUNTER_0  0x00
+#define ME4000_CNT_COUNTER_1  0x40
+#define ME4000_CNT_COUNTER_2  0x80
+
+#define ME4000_CNT_MODE_0     0x00     // Change state if zero crossing
+#define ME4000_CNT_MODE_1     0x02     // Retriggerable One-Shot
+#define ME4000_CNT_MODE_2     0x04     // Asymmetrical divider
+#define ME4000_CNT_MODE_3     0x06     // Symmetrical divider
+#define ME4000_CNT_MODE_4     0x08     // Counter start by software trigger
+#define ME4000_CNT_MODE_5     0x0A     // Counter start by hardware trigger
+
+#endif