--- /dev/null
+/*
+ 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);
+
--- /dev/null
+/*
+ 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);
+