Staging: comedi: add amplc_pc263 driver

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>
This commit is contained in:
Ian Abbott 2009-02-18 15:25:15 -08:00 committed by Greg Kroah-Hartman
parent e948cb5224
commit cfd02b71a7
1 changed files with 431 additions and 0 deletions

View File

@ -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