new driver from Ian Abbott <abbotti@mev.co.uk>
authorDavid Schleef <ds@schleef.org>
Wed, 25 Sep 2002 01:50:43 +0000 (01:50 +0000)
committerDavid Schleef <ds@schleef.org>
Wed, 25 Sep 2002 01:50:43 +0000 (01:50 +0000)
comedi/drivers/amplc_pc236.c [new file with mode: 0644]
comedi/drivers/amplc_pc263.c [new file with mode: 0644]

diff --git a/comedi/drivers/amplc_pc236.c b/comedi/drivers/amplc_pc236.c
new file mode 100644 (file)
index 0000000..b5d753b
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+    comedi/drivers/amplc_pc236.c
+    Driver for Amplicon PC36AT and PCI236 DIO boards.
+
+    Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+    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: amplc_pc236.o
+Description: Driver for Amplicon PC36AT and PCI236 DIO boards
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236)
+Updated: Fri, 23 Aug 2002 11:41:11 +0100
+Status: works
+
+Configuration options - PC36AT:
+  [0] - I/O port base address
+  [1] - IRQ (optional)
+
+Configuration options - PCI236:
+  [0] - PCI bus of device (optional)
+  [1] - PCI slot of device (optional)
+  If bus/slot is not specified, the first available PCI device will be
+  used.
+
+The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
+as subdevice 0.
+
+Subdevice 1 pretends to be a digital input device, but it always returns
+0 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
+a rising edge on port C bit 7 acts as an external trigger, which can be
+used to wake up tasks.  This is like the comedi_parport device, but the
+only way to physically disable the interrupt on the PC36AT is to remove
+the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
+unused.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <linux/comedidev.h>
+
+#include "8255.h"
+
+#define PC236_DRIVER_NAME      "amplc_pc236"
+
+/* PCI236 PCI configuration register information */
+#define PCI_VENDOR_ID_AMPLICON 0x14dc
+#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
+
+
+/* PC36AT / PCI236 registers */
+
+#define PC236_IO_SIZE          4
+#define PC236_LCR_IO_SIZE      128
+
+/*
+ * PLX PCI9052 INTCSR register (used for PCI236 card).
+ */
+#define PLX9052_INTCSR 0x4C    /* Offset in Local Configuration Registers */
+/* Local Interrupt 1 Enable */
+#define PLX9052_INTCSR_LI1ENAB_MASK            0x0001  /* (post-shift) */
+#define PLX9052_INTCSR_LI1ENAB_SHIFT           0
+#define PLX9052_INTCSR_LI1ENAB_VAL_DISABLED    0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1ENAB_VAL_ENABLED     1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1ENAB_SHIFTED_DISABLED        \
+       (PLX9052_INTCSR_LI1ENAB_VAL_DISABLED << PLX9052_INTCSR_LI1ENAB_SHIFT)
+#define PLX9052_INTCSR_LI1ENAB_SHIFTED_ENABLED \
+       (PLX9052_INTCSR_LI1ENAB_VAL_ENABLED << PLX9052_INTCSR_LI1ENAB_SHIFT)
+/* Local Interrupt 1 Polarity */
+#define PLX9052_INTCSR_LI1POL_MASK             0x0002  /* (post-shift) */
+#define PLX9052_INTCSR_LI1POL_SHIFT            1
+#define PLX9052_INTCSR_LI1POL_VAL_LOW          0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1POL_VAL_HIGH         1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1POL_SHIFTED_LOW      \
+       (PLX9052_INTCSR_LI1POL_VAL_LOW << PLX9052_INTCSR_LI1POL_SHIFT)
+#define PLX9052_INTCSR_LI1POL_SHIFTED_HIGH     \
+       (PLX9052_INTCSR_LI1POL_VAL_HIGH << PLX9052_INTCSR_LI1POL_SHIFT)
+/* Local Interrupt 1 Status (read-only) */
+#define PLX9052_INTCSR_LI1STAT_MASK            0x0004  /* (post-shift) */
+#define PLX9052_INTCSR_LI1STAT_SHIFT           2
+#define PLX9052_INTCSR_LI1STAT_VAL_INACTIVE    0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1STAT_VAL_ACTIVE      1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1STAT_SHIFTED_INACTIVE        \
+       (PLX9052_INTCSR_LI1STAT_VAL_INACTIVE << PLX9052_INTCSR_LI1STAT_SHIFT)
+#define PLX9052_INTCSR_LI1STAT_SHIFTED_ACTIVE  \
+       (PLX9052_INTCSR_LI1STAT_VAL_ACTIVE << PLX9052_INTCSR_LI1STAT_SHIFT)
+/* Local Interrupt 2 Enable */
+#define PLX9052_INTCSR_LI2ENAB_MASK            0x0008  /* (post-shift) */
+#define PLX9052_INTCSR_LI2ENAB_SHIFT           3
+#define PLX9052_INTCSR_LI2ENAB_VAL_DISABLED    0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2ENAB_VAL_ENABLED     1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2ENAB_SHIFTED_DISABLED        \
+       (PLX9052_INTCSR_LI2ENAB_VAL_DISABLED << PLX9052_INTCSR_LI2ENAB_SHIFT)
+#define PLX9052_INTCSR_LI2ENAB_SHIFTED_ENABLED \
+       (PLX9052_INTCSR_LI2ENAB_VAL_ENABLED << PLX9052_INTCSR_LI2ENAB_SHIFT)
+/* Local Interrupt 2 Polarity */
+#define PLX9052_INTCSR_LI2POL_MASK             0x0010  /* (post-shift) */
+#define PLX9052_INTCSR_LI2POL_SHIFT            4
+#define PLX9052_INTCSR_LI2POL_VAL_LOW          0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2POL_VAL_HIGH         1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2POL_SHIFTED_LOW      \
+       (PLX9052_INTCSR_LI2POL_VAL_LOW << PLX9052_INTCSR_LI2POL_SHIFT)
+#define PLX9052_INTCSR_LI2POL_SHIFTED_HIGH     \
+       (PLX9052_INTCSR_LI2POL_VAL_HIGH << PLX9052_INTCSR_LI2POL_SHIFT)
+/* Local Interrupt 2 Status (read-only) */
+#define PLX9052_INTCSR_LI2STAT_MASK            0x0020  /* (post-shift) */
+#define PLX9052_INTCSR_LI2STAT_SHIFT           5
+#define PLX9052_INTCSR_LI2STAT_VAL_INACTIVE    0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2STAT_VAL_ACTIVE      1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2STAT_SHIFTED_INACTIVE        \
+       (PLX9052_INTCSR_LI2STAT_VAL_INACTIVE << PLX9052_INTCSR_LI2STAT_SHIFT)
+#define PLX9052_INTCSR_LI2STAT_SHIFTED_ACTIVE  \
+       (PLX9052_INTCSR_LI2STAT_VAL_ACTIVE << PLX9052_INTCSR_LI2STAT_SHIFT)
+/* PCI Interrupt Enable */
+#define PLX9052_INTCSR_PCIENAB_MASK            0x0040  /* (post-shift) */
+#define PLX9052_INTCSR_PCIENAB_SHIFT           6
+#define PLX9052_INTCSR_PCIENAB_VAL_DISABLED    0       /* (pre-shift) */
+#define PLX9052_INTCSR_PCIENAB_VAL_ENABLED     1       /* (pre-shift) */
+#define PLX9052_INTCSR_PCIENAB_SHIFTED_DISABLED        \
+       (PLX9052_INTCSR_PCIENAB_VAL_DISABLED << PLX9052_INTCSR_PCIENAB_SHIFT)
+#define PLX9052_INTCSR_PCIENAB_SHIFTED_ENABLED \
+       (PLX9052_INTCSR_PCIENAB_VAL_ENABLED << PLX9052_INTCSR_PCIENAB_SHIFT)
+/* Software Interrupt */
+#define PLX9052_INTCSR_SOFTINT_MASK            0x0080  /* (post-shift) */
+#define PLX9052_INTCSR_SOFTINT_SHIFT           7
+#define PLX9052_INTCSR_SOFTINT_VAL_UNASSERTED  0       /* (pre-shift) */
+#define PLX9052_INTCSR_SOFTINT_VAL_ASSERTED    1       /* (pre-shift) */
+#define PLX9052_INTCSR_SOFTINT_SHIFTED_UNASSERTED \
+       (PLX9052_INTCSR_SOFTINT_VAL_UNASSERTED << PLX9052_INTCSR_SOFTINT_SHIFT)
+#define PLX9052_INTCSR_SOFTINT_SHIFTED_ASSERTED        \
+       (PLX9052_INTCSR_SOFTINT_VAL_ASSERTED << PLX9052_INTCSR_SOFTINT_SHIFT)
+/* Local Interrupt 1 Select Enable */
+#define PLX9052_INTCSR_LI1SEL_MASK             0x0100  /* (post-shift) */
+#define PLX9052_INTCSR_LI1SEL_SHIFT            8
+#define PLX9052_INTCSR_LI1SEL_VAL_LEVEL                0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1SEL_VAL_EDGE         1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1SEL_SHIFTED_LEVEL    \
+       (PLX9052_INTCSR_LI1SEL_VAL_LEVEL << PLX9052_INTCSR_LI1SEL_SHIFT)
+#define PLX9052_INTCSR_LI1SEL_SHIFTED_EDGE     \
+       (PLX9052_INTCSR_LI1SEL_VAL_EDGE << PLX9052_INTCSR_LI1SEL_SHIFT)
+/* Local Interrupt 2 Select Enable */
+#define PLX9052_INTCSR_LI2SEL_MASK             0x0200  /* (post-shift) */
+#define PLX9052_INTCSR_LI2SEL_SHIFT            9
+#define PLX9052_INTCSR_LI2SEL_VAL_LEVEL                0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2SEL_VAL_EDGE         1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2SEL_SHIFTED_LEVEL    \
+       (PLX9052_INTCSR_LI2SEL_VAL_LEVEL << PLX9052_INTCSR_LI2SEL_SHIFT)
+#define PLX9052_INTCSR_LI2SEL_SHIFTED_EDGE     \
+       (PLX9052_INTCSR_LI2SEL_VAL_EDGE << PLX9052_INTCSR_LI2SEL_SHIFT)
+/* Local Edge Triggerable Interrupt 1 Clear Bit */
+#define PLX9052_INTCSR_LI1CLRINT_MASK          0x0400  /* (post-shift) */
+#define PLX9052_INTCSR_LI1CLRINT_SHIFT         10
+#define PLX9052_INTCSR_LI1CLRINT_VAL_UNASSERTED        0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1CLRINT_VAL_ASSERTED  1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI1CLRINT_SHIFTED_UNASSERTED \
+       (PLX9052_INTCSR_LI1CLRINT_VAL_UNASSERTED << PLX9052_INTCSR_LI1CLRINT_SHIFT)
+#define PLX9052_INTCSR_LI1CLRINT_SHIFTED_ASSERTED \
+       (PLX9052_INTCSR_LI1CLRINT_VAL_ASSERTED << PLX9052_INTCSR_LI1CLRINT_SHIFT)
+/* Local Edge Triggerable Interrupt 2 Clear Bit */
+#define PLX9052_INTCSR_LI2CLRINT_MASK          0x0800  /* (post-shift) */
+#define PLX9052_INTCSR_LI2CLRINT_SHIFT         11
+#define PLX9052_INTCSR_LI2CLRINT_VAL_UNASSERTED        0       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2CLRINT_VAL_ASSERTED  1       /* (pre-shift) */
+#define PLX9052_INTCSR_LI2CLRINT_SHIFTED_UNASSERTED \
+       (PLX9052_INTCSR_LI2CLRINT_VAL_UNASSERTED << PLX9052_INTCSR_LI2CLRINT_SHIFT)
+#define PLX9052_INTCSR_LI2CLRINT_SHIFTED_ASSERTED \
+       (PLX9052_INTCSR_LI2CLRINT_VAL_ASSERTED << PLX9052_INTCSR_LI2CLRINT_SHIFT)
+/* ISA Interface Mode Enable (read-only over PCI bus) */
+#define PLX9052_INTCSR_ISAMODE_MASK            0x1000  /* (post-shift) */
+#define PLX9052_INTCSR_ISAMODE_SHIFT           12
+#define PLX9052_INTCSR_ISAMODE_VAL_DISABLED    0       /* (pre-shift) */
+#define PLX9052_INTCSR_ISAMODE_VAL_ENABLED     1       /* (pre-shift) */
+#define PLX9052_INTCSR_ISAMODE_SHIFTED_DISABLED        \
+       (PLX9052_INTCSR_ISAMODE_VAL_DISABLED << PLX9052_INTCSR_ISAMODE_SHIFT)
+#define PLX9052_INTCSR_ISAMODE_SHIFTED_ENABLED \
+       (PLX9052_INTCSR_ISAMODE_VAL_ENABLED << PLX9052_INTCSR_ISAMODE_SHIFT)
+/*
+ * INTCSR values for PCI236.
+ */
+/* Disable interrupt, also clear any interrupt there */
+#define PCI236_INTR_DISABLE ( PLX9052_INTCSR_LI1ENAB_SHIFTED_DISABLED \
+        | PLX9052_INTCSR_LI1POL_SHIFTED_HIGH \
+        | PLX9052_INTCSR_LI2POL_SHIFTED_HIGH \
+        | PLX9052_INTCSR_PCIENAB_SHIFTED_DISABLED \
+        | PLX9052_INTCSR_LI1SEL_SHIFTED_EDGE \
+        | PLX9052_INTCSR_LI1CLRINT_SHIFTED_ASSERTED )
+/* Enable interrupt, also clear any interrupt there. */
+#define PCI236_INTR_ENABLE ( PLX9052_INTCSR_LI1ENAB_SHIFTED_ENABLED \
+        | PLX9052_INTCSR_LI1POL_SHIFTED_HIGH \
+        | PLX9052_INTCSR_LI2POL_SHIFTED_HIGH \
+        | PLX9052_INTCSR_PCIENAB_SHIFTED_ENABLED \
+        | PLX9052_INTCSR_LI1SEL_SHIFTED_EDGE \
+        | PLX9052_INTCSR_LI1CLRINT_SHIFTED_ASSERTED )
+
+/*
+ * Board descriptions for Amplicon PC36AT and PCI236.
+ */
+
+enum pc236_bustype {isa_bustype, pci_bustype};
+enum pc236_model {pc36at_model, pci236_model};
+
+typedef struct pc236_board_struct{
+       char *name;
+       char *fancy_name;
+       enum pc236_bustype bustype;
+       enum pc236_model model;
+}pc236_board;
+static pc236_board pc236_boards[] = {
+       {
+       name:           "pc36at",
+       fancy_name:     "PC36AT",
+       bustype:        isa_bustype,
+       model:          pc36at_model,
+       },
+       {
+       name:           "pci236",
+       fancy_name:     "PCI236",
+       bustype:        pci_bustype,
+       model:          pci236_model,
+       },
+};
+
+static struct pci_device_id pc236_pci_table[] __devinitdata = {
+       { PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236, PCI_ANY_ID, PCI_ANY_ID, 0, 0, pci236_model },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pc236_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((pc236_board *)dev->board_ptr)
+
+/* this structure is for data unique to this hardware driver.  If
+   several hardware drivers keep similar information in this structure,
+   feel free to suggest moving the variable to the comedi_device struct.  */
+typedef struct{
+       /* PCI device */
+       struct pci_dev *pci_dev;
+       int lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
+       int enable_irq;
+       int share_irq;
+}pc236_private;
+
+#define devpriv ((pc236_private *)dev->private)
+
+/*
+ * The comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attach/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int pc236_attach(comedi_device *dev,comedi_devconfig *it);
+static int pc236_detach(comedi_device *dev);
+static comedi_driver driver_amplc_pc236={
+       driver_name:    PC236_DRIVER_NAME,
+       module:         THIS_MODULE,
+       attach:         pc236_attach,
+       detach:         pc236_detach,
+       board_name:     pc236_boards,
+       offset:         sizeof(pc236_board),
+       num_names:      sizeof(pc236_boards) / sizeof(pc236_board),
+};
+
+static int pc236_request_region(unsigned long from, unsigned long extent);
+static void pc236_intr_disable(comedi_device *dev);
+static void pc236_intr_enable(comedi_device *dev);
+static int pc236_intr_check(comedi_device *dev);
+static int pc236_intr_insn(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data);
+static int pc236_intr_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd);
+static int pc236_intr_cmd(comedi_device *dev,comedi_subdevice *s);
+static int pc236_intr_cancel(comedi_device *dev,comedi_subdevice *s);
+static void pc236_interrupt(int irq,void *d,struct pt_regs *regs);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int pc236_attach(comedi_device *dev,comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       struct pci_dev *pci_dev = NULL;
+       int iobase = 0, irq = 0;
+       int lcr_iobase = 0;
+       int bus = 0, slot = 0;
+       struct pci_device_id *pci_id;
+       int share_irq = 0;
+       int ret;
+
+       printk("comedi%d: %s: ",dev->minor, PC236_DRIVER_NAME);
+
+       /* Get card bus position and base address. */
+       switch (thisboard->bustype) {
+       case isa_bustype:
+               iobase = it->options[0];
+               irq = it->options[1];
+               share_irq = 0;
+               break;
+       case pci_bustype:
+               bus = it->options[0];
+               slot = it->options[1];
+               share_irq = 1;
+
+               /* Look for PCI table entry for this model. */
+               for (pci_id = pc236_pci_table; pci_id->vendor != 0; pci_id++) {
+                       if (pci_id->driver_data == thisboard->model)
+                               break;
+               }
+               if (pci_id->driver_data != thisboard->model) {
+                       printk("bug! cannot determine board type!\n");
+                       return -EINVAL;
+               }
+
+               /* Look for matching PCI device. */
+               pci_for_each_dev(pci_dev) {
+                       /* If bus/slot specified, check them. */
+                       if (bus || slot) {
+                               if (bus != pci_dev->bus->number
+                                               || slot != PCI_SLOT(pci_dev->devfn))
+                                       continue;
+                       }
+                       if (pci_dev->vendor != pci_id->vendor)
+                               continue;
+                       if (pci_dev->device != pci_id->device)
+                               continue;
+                       if (pci_id->subvendor != PCI_ANY_ID) {
+                               if (pci_dev->subsystem_vendor != pci_id->subvendor)
+                                       continue;
+                       }
+                       if (pci_id->subdevice != PCI_ANY_ID) {
+                               if (pci_dev->subsystem_device != pci_id->subdevice)
+                                       continue;
+                       }
+                       if (((pci_dev->class ^ pci_id->class) & pci_id->class_mask) != 0)
+                               continue;
+                       /* Found a match. */
+                       break;
+               }
+               if (!pci_dev) {
+                       printk("no %s found!\n", thisboard->fancy_name);
+                       return -EIO;
+               }
+               if ((ret=pci_enable_device(pci_dev)) < 0) {
+                       printk("error enabling PCI device!\n");
+                       return ret;
+               }
+               lcr_iobase = pci_resource_start(pci_dev, 1);
+               iobase = pci_resource_start(pci_dev, 2);
+               irq = pci_dev->irq;
+               break;
+       default:
+               printk("bug! cannot determine board type!\n");
+               return -EINVAL;
+               break;
+       }
+
+/*
+ * Initialize dev->board_name.
+ */
+       dev->board_name = thisboard->name;
+       printk("%s ", dev->board_name);
+
+/*
+ * Allocate the private structure area.  alloc_private() is a
+ * convenient macro defined in comedidev.h.
+ */
+       if ((ret=alloc_private(dev,sizeof(pc236_private))) < 0) {
+               printk("out of memory!\n");
+               return ret; 
+       }
+
+       devpriv->pci_dev = pci_dev;
+       devpriv->share_irq = share_irq;
+
+       /* Reserve I/O spaces. */
+       if (lcr_iobase) {
+               if ((ret=pc236_request_region(lcr_iobase, PC236_LCR_IO_SIZE)) < 0) {
+                       return ret;
+               }
+               devpriv->lcr_iobase = lcr_iobase;
+       }
+       if ((ret=pc236_request_region(iobase, PC236_IO_SIZE)) < 0) {
+               return ret;
+       }
+       dev->iobase = iobase;
+
+/*
+ * Allocate the subdevice structures.  alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.  It relies on
+ * n_subdevices being set correctly.
+ */
+       dev->n_subdevices = 2;
+       if ((ret=alloc_subdevices(dev)) < 0) {
+               printk("out of memory!\n");
+               return ret;
+       }
+
+       s = dev->subdevices+0;
+       /* digital i/o subdevice (8255) */
+       if ((ret=subdev_8255_init(dev, s, NULL, iobase)) < 0) {
+               printk("out of memory!\n");
+               return ret;
+       }
+       s = dev->subdevices+1;
+       dev->read_subdev = s;
+       s->type = COMEDI_SUBD_UNUSED;
+       pc236_intr_disable(dev);
+       if (irq) {
+               unsigned long flags = share_irq ? SA_SHIRQ : 0;
+
+               if (comedi_request_irq(irq, pc236_interrupt, flags,
+                                       PC236_DRIVER_NAME, dev) >= 0) {
+                       dev->irq = irq;
+                       s->type = COMEDI_SUBD_DI;
+                       s->subdev_flags = SDF_READABLE;
+                       s->n_chan = 1;
+                       s->maxdata = 1;
+                       s->range_table = &range_digital;
+                       s->insn_bits = pc236_intr_insn;
+                       s->do_cmdtest = pc236_intr_cmdtest;
+                       s->do_cmd = pc236_intr_cmd;
+                       s->cancel = pc236_intr_cancel;
+               }
+       }
+       if (thisboard->bustype == isa_bustype) {
+               printk("(base %#x) ", iobase);
+       } else {
+               printk("(pci %02x:%02x.%x) ", pci_dev->bus->number,
+                               PCI_SLOT(pci_dev->devfn),
+                               PCI_FUNC(pci_dev->devfn));
+       }
+       if (irq) {
+               printk("(irq %d%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
+       } else {
+               printk("(no irq) ");
+       }
+       
+       printk("attached\n");
+
+       return 1;
+}
+
+
+/*
+ * _detach is called to deconfigure a device.  It should deallocate
+ * resources.  
+ * This function is also called when _attach() fails, so it should be
+ * careful not to release resources that were not necessarily
+ * allocated by _attach().  dev->private and dev->subdevices are
+ * deallocated automatically by the core.
+ */
+static int pc236_detach(comedi_device *dev)
+{
+       printk("comedi%d: %s: remove\n", dev->minor, PC236_DRIVER_NAME);
+       if (devpriv) {
+               pc236_intr_disable(dev);
+       }
+       if (dev->irq) comedi_free_irq(dev->irq, dev);
+       if (dev->subdevices) {
+               subdev_8255_cleanup(dev, dev->subdevices+0);
+       }
+       if (devpriv) {
+               if (devpriv->lcr_iobase)
+                       release_region(devpriv->lcr_iobase, PC236_LCR_IO_SIZE);
+       }
+       if (dev->iobase) release_region(dev->iobase, PC236_IO_SIZE);
+       
+       return 0;
+}
+
+/*
+ * This function checks and requests an I/O region, reporting an error
+ * if there is a conflict.
+ */
+static int pc236_request_region(unsigned long from, unsigned long extent)
+{
+       if (check_region(from, extent) < 0) {
+               printk("I/O port conflict (%#lx,%lu)!\n", from, extent);
+               return -EIO;
+       }
+       request_region(from, extent, PC236_DRIVER_NAME);
+       return 0;
+}
+
+/*
+ * This function is called to mark the interrupt as disabled (no command
+ * configured on subdevice 1) and to physically disable the interrupt
+ * (not possible on the PC36AT, except by removing the IRQ jumper!).
+ */
+static void pc236_intr_disable(comedi_device *dev)
+{
+       unsigned long flags;
+
+       comedi_spin_lock_irqsave(&dev->spinlock, flags);
+       devpriv->enable_irq = 0;
+       if (devpriv->lcr_iobase)
+               outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
+       comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+/*
+ * This function is called to mark the interrupt as enabled (a command
+ * configured on subdevice 1) and to physically enable the interrupt
+ * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
+ */
+static void pc236_intr_enable(comedi_device *dev)
+{
+       unsigned long flags;
+
+       comedi_spin_lock_irqsave(&dev->spinlock, flags);
+       devpriv->enable_irq = 1;
+       if (devpriv->lcr_iobase)
+               outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
+       comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+/*
+ * This function is called when an interrupt occurs to check whether
+ * the interrupt has been marked as enabled and was generated by the
+ * board.  If so, the function prepares the hardware for the next
+ * interrupt.
+ * Returns 0 if the interrupt should be ignored.
+ */
+static int pc236_intr_check(comedi_device *dev)
+{
+       int retval = 0;
+       unsigned long flags;
+
+       comedi_spin_lock_irqsave(&dev->spinlock, flags);
+       if (devpriv->enable_irq) {
+               retval = 1;
+               if (devpriv->lcr_iobase) {
+                       if ((inl(devpriv->lcr_iobase+PLX9052_INTCSR)
+                                               & PLX9052_INTCSR_LI1STAT_MASK)
+                                       == PLX9052_INTCSR_LI1STAT_SHIFTED_INACTIVE) {
+                               retval = 0;
+                       } else {
+                               /* Clear interrupt and keep it enabled. */
+                               outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
+                       }
+               }
+       }
+       comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+       return retval;
+}
+
+/*
+ * Input from subdevice 1.
+ * Copied from the comedi_parport driver.
+ */
+static int pc236_intr_insn(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+       data[1] = 0;
+       return 2;
+}
+
+/*
+ * Subdevice 1 command test.
+ * Copied from the comedi_parport driver.
+ */
+static int pc236_intr_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd)
+{
+       int err=0;
+       int tmp;
+
+       /* step 1 */
+
+       tmp=cmd->start_src;
+       cmd->start_src &= TRIG_NOW;
+       if(!cmd->start_src || tmp!=cmd->start_src)err++;
+
+       tmp=cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_EXT;
+       if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
+
+       tmp=cmd->convert_src;
+       cmd->convert_src &= TRIG_FOLLOW;
+       if(!cmd->convert_src || tmp!=cmd->convert_src)err++;
+
+       tmp=cmd->scan_end_src;
+       cmd->scan_end_src &= TRIG_COUNT;
+       if(!cmd->scan_end_src || tmp!=cmd->scan_end_src)err++;
+
+       tmp=cmd->stop_src;
+       cmd->stop_src &= TRIG_NONE;
+       if(!cmd->stop_src || tmp!=cmd->stop_src)err++;
+
+       if(err)return 1;
+
+       /* step 2: ignored */
+
+       if(err)return 2;
+
+       /* step 3: */
+
+       if(cmd->start_arg!=0){
+               cmd->start_arg = 0;
+               err++;
+       }
+       if(cmd->scan_begin_arg!=0){
+               cmd->scan_begin_arg = 0;
+               err++;
+       }
+       if(cmd->convert_arg!=0){
+               cmd->convert_arg = 0;
+               err++;
+       }
+       if(cmd->scan_end_arg!=1){
+               cmd->scan_end_arg = 1;
+               err++;
+       }
+       if(cmd->stop_arg!=0){
+               cmd->stop_arg = 0;
+               err++;
+       }
+
+       if(err)return 3;
+
+       /* step 4: ignored */
+
+       if(err)return 4;
+
+       return 0;
+}
+
+/*
+ * Subdevice 1 command.
+ */
+static int pc236_intr_cmd(comedi_device *dev,comedi_subdevice *s)
+{
+       pc236_intr_enable(dev);
+
+       return 0;
+}
+
+/*
+ * Subdevice 1 cancel command.
+ */
+static int pc236_intr_cancel(comedi_device *dev,comedi_subdevice *s)
+{
+       pc236_intr_disable(dev);
+
+       return 0;
+}
+
+/*
+ * Interrupt service routine.
+ * Based on the comedi_parport driver.
+ */
+static void pc236_interrupt(int irq,void *d,struct pt_regs *regs)
+{
+       comedi_device *dev=d;
+       comedi_subdevice *s=dev->subdevices+1;
+
+       if(!pc236_intr_check(dev)) 
+               return;
+
+       *(sampl_t *)(s->async->data+s->async->buf_int_ptr)=0;
+       s->async->buf_int_ptr+=sizeof(sampl_t);
+       s->async->buf_int_count+=sizeof(sampl_t);
+       if(s->async->buf_int_ptr>=s->async->data_len){
+               s->async->buf_int_ptr=0;
+               s->async->events |= COMEDI_CB_EOBUF;
+       }
+       s->async->events |= COMEDI_CB_EOS;
+       
+       comedi_event(dev,s,s->async->events);
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_amplc_pc236);
+
diff --git a/comedi/drivers/amplc_pc263.c b/comedi/drivers/amplc_pc263.c
new file mode 100644 (file)
index 0000000..10a2191
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+    comedi/drivers/amplc_pc263.c
+    Driver for Amplicon PC263 and PCI263 relay boards.
+
+    Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+    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: amplc_pc263.o
+Description: Driver for Amplicon PC263 and PCI263 Relay boards
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PC263 (pc263), PCI263 (pci263)
+Updated: Tue, 20 Aug 2002 11:41:01 +0100
+Status: works
+
+Configuration options - PC263:
+  [0] - I/O port base address
+
+Configuration options - PCI263:
+  [0] - PCI bus of device (optional)
+  [1] - PCI slot of device (optional)
+  If bus/slot is not specified, the first available PCI device will be
+  used.
+
+Each board appears as one subdevice, with 16 digital outputs, each
+connected to a reed-relay. Relay contacts are closed when output is 1.
+The state of the outputs can be read.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <linux/comedidev.h>
+
+#define PC263_DRIVER_NAME      "amplc_pc263"
+
+/* PCI263 PCI configuration register information */
+#define PCI_VENDOR_ID_AMPLICON 0x14dc
+#define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c
+
+
+/* PC263 / PCI263 registers */
+#define PC263_IO_SIZE  2
+
+
+/*
+ * Board descriptions for Amplicon PC263 / PCI263.
+ */
+
+enum pc263_bustype {isa_bustype, pci_bustype};
+enum pc263_model {pc263_model, pci263_model};
+
+typedef struct pc263_board_struct{
+       char *name;
+       char *fancy_name;
+       enum pc263_bustype bustype;
+       enum pc263_model model;
+}pc263_board;
+static pc263_board pc263_boards[] = {
+       {
+       name:           "pc263",
+       fancy_name:     "PC263",
+       bustype:        isa_bustype,
+       model:          pc263_model,
+       },
+       {
+       name:           "pci263",
+       fancy_name:     "PCI263",
+       bustype:        pci_bustype,
+       model:          pci263_model,
+       },
+};
+
+static struct pci_device_id pc263_pci_table[] __devinitdata = {
+       { PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263, PCI_ANY_ID, PCI_ANY_ID, 0, 0, pci263_model },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pc263_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((pc263_board *)dev->board_ptr)
+
+/* this structure is for data unique to this hardware driver.  If
+   several hardware drivers keep similar information in this structure,
+   feel free to suggest moving the variable to the comedi_device struct.  */
+typedef struct{
+       /* PCI device. */
+       struct pci_dev *pci_dev;
+}pc263_private;
+
+#define devpriv ((pc263_private *)dev->private)
+
+/*
+ * The comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attach/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int pc263_attach(comedi_device *dev,comedi_devconfig *it);
+static int pc263_detach(comedi_device *dev);
+static comedi_driver driver_amplc_pc263={
+       driver_name:    PC263_DRIVER_NAME,
+       module:         THIS_MODULE,
+       attach:         pc263_attach,
+       detach:         pc263_detach,
+       board_name:     pc263_boards,
+       offset:         sizeof(pc263_board),
+       num_names:      sizeof(pc263_boards) / sizeof(pc263_board),
+};
+
+static int pc263_request_region(unsigned long from, unsigned long extent);
+static int pc263_dio_insn_bits(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data);
+static int pc263_dio_insn_config(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int pc263_attach(comedi_device *dev,comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       struct pci_dev *pci_dev = NULL;
+       int iobase = 0;
+       int bus = 0, slot = 0;
+       struct pci_device_id *pci_id;
+       int ret;
+
+       printk("comedi%d: %s: ",dev->minor, PC263_DRIVER_NAME);
+
+       /* Get card bus position and base address. */
+       switch (thisboard->bustype) {
+       case isa_bustype:
+               iobase = it->options[0];
+               break;
+       case pci_bustype:
+               bus = it->options[0];
+               slot = it->options[1];
+
+               /* Look for PCI table entry for this model. */
+               for (pci_id = pc263_pci_table; pci_id->vendor != 0; pci_id++) {
+                       if (pci_id->driver_data == thisboard->model)
+                               break;
+               }
+               if (pci_id->driver_data != thisboard->model) {
+                       printk("bug! cannot determine board type!\n");
+                       return -EINVAL;
+               }
+
+               /* Look for matching PCI device. */
+               pci_for_each_dev(pci_dev) {
+                       /* If bus/slot specified, check them. */
+                       if (bus || slot) {
+                               if (bus != pci_dev->bus->number
+                                               || slot != PCI_SLOT(pci_dev->devfn))
+                                       continue;
+                       }
+                       if (pci_dev->vendor != pci_id->vendor)
+                               continue;
+                       if (pci_dev->device != pci_id->device)
+                               continue;
+                       if (pci_id->subvendor != PCI_ANY_ID) {
+                               if (pci_dev->subsystem_vendor != pci_id->subvendor)
+                                       continue;
+                       }
+                       if (pci_id->subdevice != PCI_ANY_ID) {
+                               if (pci_dev->subsystem_device != pci_id->subdevice)
+                                       continue;
+                       }
+                       if (((pci_dev->class ^ pci_id->class) & pci_id->class_mask) != 0)
+                               continue;
+                       /* Found a match. */
+                       break;
+               }
+               if (!pci_dev) {
+                       printk("no %s found!\n", thisboard->fancy_name);
+                       return -EIO;
+               }
+               if ((ret=pci_enable_device(pci_dev)) < 0) {
+                       printk("error enabling PCI device!\n");
+                       return ret;
+               }
+               iobase = pci_resource_start(pci_dev, 2);
+               break;
+       default:
+               printk("bug! cannot determine board type!\n");
+               return -EINVAL;
+               break;
+       }
+
+/*
+ * Initialize dev->board_name.
+ */
+       dev->board_name = thisboard->name;
+       printk("%s ", dev->board_name);
+
+/*
+ * Allocate the private structure area.  alloc_private() is a
+ * convenient macro defined in comedidev.h.
+ */
+       if ((ret=alloc_private(dev,sizeof(pc263_private))) < 0) {
+               printk("out of memory!\n");
+               return ret;
+       }
+
+       devpriv->pci_dev = pci_dev;
+
+       /* Reserve I/O space. */
+       if ((ret=pc263_request_region(iobase, PC263_IO_SIZE)) < 0) {
+               return ret;
+       }
+       dev->iobase = iobase;
+
+/*
+ * Allocate the subdevice structures.  alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.  It relies on
+ * n_subdevices being set correctly.
+ */
+       dev->n_subdevices = 1;
+       if ((ret=alloc_subdevices(dev)) < 0) {
+               printk("out of memory!\n");
+               return -ENOMEM;
+       }
+
+       s = dev->subdevices+0;
+       /* digital i/o subdevice */
+       s->type = COMEDI_SUBD_DIO;
+       s->subdev_flags = SDF_READABLE|SDF_WRITABLE|SDF_RT;
+       s->n_chan = 16;
+       s->maxdata = 1;
+       s->range_table = &range_digital;
+       s->insn_bits = pc263_dio_insn_bits;
+       s->insn_config = pc263_dio_insn_config;
+       /* all outputs */
+       s->io_bits = 0xffff;
+       /* read initial relay state */
+       s->state = inb(dev->iobase);
+       s->state = s->state | (inb(dev->iobase) << 8);
+
+       if (thisboard->bustype == isa_bustype) {
+               printk("(base %#x) ", iobase);
+       } else {
+               printk("(pci %02x:%02x.%x) ", pci_dev->bus->number,
+                               PCI_SLOT(pci_dev->devfn),
+                               PCI_FUNC(pci_dev->devfn));
+       }
+       
+       printk("attached\n");
+
+       return 1;
+}
+
+
+/*
+ * _detach is called to deconfigure a device.  It should deallocate
+ * resources.  
+ * This function is also called when _attach() fails, so it should be
+ * careful not to release resources that were not necessarily
+ * allocated by _attach().  dev->private and dev->subdevices are
+ * deallocated automatically by the core.
+ */
+static int pc263_detach(comedi_device *dev)
+{
+       printk("comedi%d: %s: remove\n", dev->minor, PC263_DRIVER_NAME);
+
+       if (dev->iobase)
+               release_region(dev->iobase, PC263_IO_SIZE);
+       
+       return 0;
+}
+
+/*
+ * This function checks and requests an I/O region, reporting an error
+ * if there is a conflict.
+ */
+static int pc263_request_region(unsigned long from, unsigned long extent)
+{
+       if (check_region(from, extent) < 0) {
+               printk("I/O port conflict (%#lx,%lu)!\n", from, extent);
+               return -EIO;
+       }
+       request_region(from, extent, PC263_DRIVER_NAME);
+       return 0;
+}
+
+/* DIO devices are slightly special.  Although it is possible to
+ * implement the insn_read/insn_write interface, it is much more
+ * useful to applications if you implement the insn_bits interface.
+ * This allows packed reading/writing of the DIO channels.  The
+ * comedi core can convert between insn_bits and insn_read/write */
+static int pc263_dio_insn_bits(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+       if(insn->n!=2)return -EINVAL;
+
+       /* The insn data is a mask in data[0] and the new data
+        * in data[1], each channel cooresponding to a bit. */
+       if(data[0]){
+               s->state &= ~data[0];
+               s->state |= data[0]&data[1];
+               /* Write out the new digital output lines */
+               outb(s->state & 0xFF, dev->iobase);
+               outb(s->state >> 8, dev->iobase + 1);
+       }
+
+       /* on return, data[1] contains the value of the digital
+        * input and output lines. */
+       /* or we could just return the software copy of the output values if
+        * it was a purely digital output subdevice */
+       data[1]=s->state;
+
+       return 2;
+}
+
+static int pc263_dio_insn_config(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+       if(insn->n!=1)return -EINVAL;
+       return 1;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_amplc_pc263);
+