Commit cfd02b71 authored by Ian Abbott's avatar Ian Abbott Committed by Greg Kroah-Hartman
Browse files

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: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent e948cb52
Loading
Loading
Loading
Loading
+431 −0
Original line number Diff line number Diff line
/*
    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