Staging: comedi: add amplc_pc263 driver
authorIan Abbott <abbotti@mev.co.uk>
Wed, 18 Feb 2009 23:25:15 +0000 (15:25 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 3 Apr 2009 21:53:47 +0000 (14:53 -0700)
Driver for Amplicon PC263 and PCI263 relay boards

From: Ian Abbott <abbotti@mev.co.uk>
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/amplc_pc263.c [new file with mode: 0644]

diff --git a/drivers/staging/comedi/drivers/amplc_pc263.c b/drivers/staging/comedi/drivers/amplc_pc263.c
new file mode 100644 (file)
index 0000000..868d35d
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+    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
+Description: Amplicon PC263, PCI263
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PC263 (pc263), PCI263 (pci263 or amplc_pc263)
+Updated: Wed, 22 Oct 2008 14:10:53 +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 "../comedidev.h"
+
+#include "comedi_pci.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
+#define PCI_DEVICE_ID_INVALID 0xffff
+
+/* 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, anypci_model };
+
+typedef struct pc263_board_struct {
+       const char *name;
+       const char *fancy_name;
+       unsigned short devid;
+       enum pc263_bustype bustype;
+       enum pc263_model model;
+} pc263_board;
+static const pc263_board pc263_boards[] = {
+       {
+             name:     "pc263",
+             fancy_name:"PC263",
+             bustype:  isa_bustype,
+             model:    pc263_model,
+               },
+#ifdef CONFIG_COMEDI_PCI
+       {
+             name:     "pci263",
+             fancy_name:"PCI263",
+             devid:    PCI_DEVICE_ID_AMPLICON_PCI263,
+             bustype:  pci_bustype,
+             model:    pci263_model,
+               },
+#endif
+#ifdef CONFIG_COMEDI_PCI
+       {
+             name:     PC263_DRIVER_NAME,
+             fancy_name:PC263_DRIVER_NAME,
+             devid:    PCI_DEVICE_ID_INVALID,
+             bustype:  pci_bustype,
+             model:    anypci_model,   /* wildcard */
+               },
+#endif
+};
+
+#ifdef CONFIG_COMEDI_PCI
+static DEFINE_PCI_DEVICE_TABLE(pc263_pci_table) = {
+       {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263, PCI_ANY_ID,
+               PCI_ANY_ID, 0, 0, 0},
+       {0}
+};
+
+MODULE_DEVICE_TABLE(pci, pc263_pci_table);
+#endif /* CONFIG_COMEDI_PCI */
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((const 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.  */
+#ifdef CONFIG_COMEDI_PCI
+typedef struct {
+       /* PCI device. */
+       struct pci_dev *pci_dev;
+} pc263_private;
+
+#define devpriv ((pc263_private *)dev->private)
+#endif /* CONFIG_COMEDI_PCI */
+
+/*
+ * 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[0].name,
+      offset:sizeof(pc263_board),
+      num_names:sizeof(pc263_boards) / sizeof(pc263_board),
+};
+
+static int pc263_request_region(unsigned minor, 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);
+
+/*
+ * This function looks for a PCI device matching the requested board name,
+ * bus and slot.
+ */
+#ifdef CONFIG_COMEDI_PCI
+static int
+pc263_find_pci(comedi_device * dev, int bus, int slot,
+       struct pci_dev **pci_dev_p)
+{
+       struct pci_dev *pci_dev = NULL;
+
+       *pci_dev_p = NULL;
+
+       /* Look for matching PCI device. */
+       for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
+               pci_dev != NULL;
+               pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
+                       PCI_ANY_ID, 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 (thisboard->model == anypci_model) {
+                       /* Match any supported model. */
+                       int i;
+
+                       for (i = 0; i < ARRAY_SIZE(pc263_boards); i++) {
+                               if (pc263_boards[i].bustype != pci_bustype)
+                                       continue;
+                               if (pci_dev->device == pc263_boards[i].devid) {
+                                       /* Change board_ptr to matched board. */
+                                       dev->board_ptr = &pc263_boards[i];
+                                       break;
+                               }
+                       }
+                       if (i == ARRAY_SIZE(pc263_boards))
+                               continue;
+               } else {
+                       /* Match specific model name. */
+                       if (pci_dev->device != thisboard->devid)
+                               continue;
+               }
+
+               /* Found a match. */
+               *pci_dev_p = pci_dev;
+               return 0;
+       }
+       /* No match found. */
+       if (bus || slot) {
+               printk(KERN_ERR
+                       "comedi%d: error! no %s found at pci %02x:%02x!\n",
+                       dev->minor, thisboard->name, bus, slot);
+       } else {
+               printk(KERN_ERR "comedi%d: error! no %s found!\n",
+                       dev->minor, thisboard->name);
+       }
+       return -EIO;
+}
+#endif
+
+/*
+ * 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;
+       unsigned long iobase = 0;
+#ifdef CONFIG_COMEDI_PCI
+       struct pci_dev *pci_dev = NULL;
+       int bus = 0, slot = 0;
+#endif
+       int ret;
+
+       printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
+               PC263_DRIVER_NAME);
+/*
+ * Allocate the private structure area.  alloc_private() is a
+ * convenient macro defined in comedidev.h.
+ */
+#ifdef CONFIG_COMEDI_PCI
+       if ((ret = alloc_private(dev, sizeof(pc263_private))) < 0) {
+               printk(KERN_ERR "comedi%d: error! out of memory!\n",
+                       dev->minor);
+               return ret;
+       }
+#endif
+       /* Process options. */
+       switch (thisboard->bustype) {
+       case isa_bustype:
+               iobase = it->options[0];
+               break;
+#ifdef CONFIG_COMEDI_PCI
+       case pci_bustype:
+               bus = it->options[0];
+               slot = it->options[1];
+
+               if ((ret = pc263_find_pci(dev, bus, slot, &pci_dev)) < 0)
+                       return ret;
+               devpriv->pci_dev = pci_dev;
+               break;
+#endif /* CONFIG_COMEDI_PCI */
+       default:
+               printk(KERN_ERR
+                       "comedi%d: %s: BUG! cannot determine board type!\n",
+                       dev->minor, PC263_DRIVER_NAME);
+               return -EINVAL;
+               break;
+       }
+
+/*
+ * Initialize dev->board_name.
+ */
+       dev->board_name = thisboard->name;
+
+       /* Enable device and reserve I/O spaces. */
+#ifdef CONFIG_COMEDI_PCI
+       if (pci_dev) {
+               if ((ret = comedi_pci_enable(pci_dev, PC263_DRIVER_NAME)) < 0) {
+                       printk(KERN_ERR
+                               "comedi%d: error! cannot enable PCI device and request regions!\n",
+                               dev->minor);
+                       return ret;
+               }
+               iobase = pci_resource_start(pci_dev, 2);
+       } else
+#endif
+       {
+               ret = pc263_request_region(dev->minor, iobase, PC263_IO_SIZE);
+               if (ret < 0) {
+                       return ret;
+               }
+       }
+       dev->iobase = iobase;
+
+/*
+ * Allocate the subdevice structures.  alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.
+ */
+       if ((ret = alloc_subdevices(dev, 1)) < 0) {
+               printk(KERN_ERR "comedi%d: error! out of memory!\n",
+                       dev->minor);
+               return ret;
+       }
+
+       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);
+
+       printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
+       if (thisboard->bustype == isa_bustype) {
+               printk("(base %#lx) ", iobase);
+       } else {
+#ifdef CONFIG_COMEDI_PCI
+               printk("(pci %s) ", pci_name(pci_dev));
+#endif
+       }
+
+       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(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
+               PC263_DRIVER_NAME);
+
+#ifdef CONFIG_COMEDI_PCI
+       if (devpriv)
+#endif
+       {
+#ifdef CONFIG_COMEDI_PCI
+               if (devpriv->pci_dev) {
+                       if (dev->iobase) {
+                               comedi_pci_disable(devpriv->pci_dev);
+                       }
+                       pci_dev_put(devpriv->pci_dev);
+               } else
+#endif
+               {
+                       if (dev->iobase) {
+                               release_region(dev->iobase, PC263_IO_SIZE);
+                       }
+               }
+       }
+       if (dev->board_name) {
+               printk(KERN_INFO "comedi%d: %s removed\n",
+                       dev->minor, dev->board_name);
+       }
+       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 minor, unsigned long from,
+       unsigned long extent)
+{
+       if (!from || !request_region(from, extent, PC263_DRIVER_NAME)) {
+               printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
+                       minor, from, extent);
+               return -EIO;
+       }
+       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.
+ */
+#ifdef CONFIG_COMEDI_PCI
+COMEDI_PCI_INITCLEANUP(driver_amplc_pc263, pc263_pci_table);
+#else
+COMEDI_INITCLEANUP(driver_amplc_pc263);
+#endif