From: Ian Abbott Date: Wed, 18 Feb 2009 23:25:15 +0000 (-0800) Subject: Staging: comedi: add amplc_pc263 driver X-Git-Tag: v2.6.30-rc1~202^2~374 X-Git-Url: http://bbs.cooldavid.org/git/?a=commitdiff_plain;h=cfd02b71a7e2d66912b9331294d04a7e93f8f845;p=net-next-2.6.git Staging: comedi: add amplc_pc263 driver Driver for Amplicon PC263 and PCI263 relay boards From: Ian Abbott Cc: David Schleef Cc: Frank Mori Hess Cc: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/staging/comedi/drivers/amplc_pc263.c b/drivers/staging/comedi/drivers/amplc_pc263.c new file mode 100644 index 00000000000..868d35d82ee --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pc263.c @@ -0,0 +1,431 @@ +/* + comedi/drivers/amplc_pc263.c + Driver for Amplicon PC263 and PCI263 relay boards. + + Copyright (C) 2002 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + 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 +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