/drivers/staging/comedi/drivers/amplc_dio200.c
https://bitbucket.org/wisechild/galaxy-nexus · C · 1556 lines · 994 code · 163 blank · 399 comment · 161 complexity · f168ab3375ca4c7d0d393c1c2009aa0a MD5 · raw file
- /*
- comedi/drivers/amplc_dio200.c
- Driver for Amplicon PC272E and PCI272 DIO boards.
- (Support for other boards in Amplicon 200 series may be added at
- a later date, e.g. PCI215.)
- Copyright (C) 2005 MEV Ltd. <http://www.mev.co.uk/>
- COMEDI - Linux Control and Measurement Device Interface
- Copyright (C) 1998,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_dio200
- Description: Amplicon 200 Series Digital I/O
- Author: Ian Abbott <abbotti@mev.co.uk>
- Devices: [Amplicon] PC212E (pc212e), PC214E (pc214e), PC215E (pc215e),
- PCI215 (pci215 or amplc_dio200), PC218E (pc218e), PC272E (pc272e),
- PCI272 (pci272 or amplc_dio200)
- Updated: Wed, 22 Oct 2008 13:36:02 +0100
- Status: works
- Configuration options - PC212E, PC214E, PC215E, PC218E, PC272E:
- [0] - I/O port base address
- [1] - IRQ (optional, but commands won't work without it)
- Configuration options - PCI215, PCI272:
- [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.
- Passing a zero for an option is the same as leaving it unspecified.
- SUBDEVICES
- PC218E PC212E PC215E/PCI215
- ------------- ------------- -------------
- Subdevices 7 6 5
- 0 CTR-X1 PPI-X PPI-X
- 1 CTR-X2 CTR-Y1 PPI-Y
- 2 CTR-Y1 CTR-Y2 CTR-Z1
- 3 CTR-Y2 CTR-Z1 CTR-Z2
- 4 CTR-Z1 CTR-Z2 INTERRUPT
- 5 CTR-Z2 INTERRUPT
- 6 INTERRUPT
- PC214E PC272E/PCI272
- ------------- -------------
- Subdevices 4 4
- 0 PPI-X PPI-X
- 1 PPI-Y PPI-Y
- 2 CTR-Z1* PPI-Z
- 3 INTERRUPT* INTERRUPT
- Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels
- are configurable as inputs or outputs in four groups:
- Port A - channels 0 to 7
- Port B - channels 8 to 15
- Port CL - channels 16 to 19
- Port CH - channels 20 to 23
- Only mode 0 of the 8255 chips is supported.
- Each CTR is a 8254 chip providing 3 16-bit counter channels. Each
- channel is configured individually with INSN_CONFIG instructions. The
- specific type of configuration instruction is specified in data[0].
- Some configuration instructions expect an additional parameter in
- data[1]; others return a value in data[1]. The following configuration
- instructions are supported:
- INSN_CONFIG_SET_COUNTER_MODE. Sets the counter channel's mode and
- BCD/binary setting specified in data[1].
- INSN_CONFIG_8254_READ_STATUS. Reads the status register value for the
- counter channel into data[1].
- INSN_CONFIG_SET_CLOCK_SRC. Sets the counter channel's clock source as
- specified in data[1] (this is a hardware-specific value). Not
- supported on PC214E. For the other boards, valid clock sources are
- 0 to 7 as follows:
- 0. CLK n, the counter channel's dedicated CLK input from the SK1
- connector. (N.B. for other values, the counter channel's CLKn
- pin on the SK1 connector is an output!)
- 1. Internal 10 MHz clock.
- 2. Internal 1 MHz clock.
- 3. Internal 100 kHz clock.
- 4. Internal 10 kHz clock.
- 5. Internal 1 kHz clock.
- 6. OUT n-1, the output of counter channel n-1 (see note 1 below).
- 7. Ext Clock, the counter chip's dedicated Ext Clock input from
- the SK1 connector. This pin is shared by all three counter
- channels on the chip.
- INSN_CONFIG_GET_CLOCK_SRC. Returns the counter channel's current
- clock source in data[1]. For internal clock sources, data[2] is set
- to the period in ns.
- INSN_CONFIG_SET_GATE_SRC. Sets the counter channel's gate source as
- specified in data[2] (this is a hardware-specific value). Not
- supported on PC214E. For the other boards, valid gate sources are 0
- to 7 as follows:
- 0. VCC (internal +5V d.c.), i.e. gate permanently enabled.
- 1. GND (internal 0V d.c.), i.e. gate permanently disabled.
- 2. GAT n, the counter channel's dedicated GAT input from the SK1
- connector. (N.B. for other values, the counter channel's GATn
- pin on the SK1 connector is an output!)
- 3. /OUT n-2, the inverted output of counter channel n-2 (see note
- 2 below).
- 4. Reserved.
- 5. Reserved.
- 6. Reserved.
- 7. Reserved.
- INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate
- source in data[2].
- Clock and gate interconnection notes:
- 1. Clock source OUT n-1 is the output of the preceding channel on the
- same counter subdevice if n > 0, or the output of channel 2 on the
- preceding counter subdevice (see note 3) if n = 0.
- 2. Gate source /OUT n-2 is the inverted output of channel 0 on the
- same counter subdevice if n = 2, or the inverted output of channel n+1
- on the preceding counter subdevice (see note 3) if n < 2.
- 3. The counter subdevices are connected in a ring, so the highest
- counter subdevice precedes the lowest.
- The 'INTERRUPT' subdevice pretends to be a digital input subdevice. The
- digital inputs come from the interrupt status register. The number of
- channels matches the number of interrupt sources. The PC214E does not
- have an interrupt status register; see notes on 'INTERRUPT SOURCES'
- below.
- INTERRUPT SOURCES
- PC218E PC212E PC215E/PCI215
- ------------- ------------- -------------
- Sources 6 6 6
- 0 CTR-X1-OUT PPI-X-C0 PPI-X-C0
- 1 CTR-X2-OUT PPI-X-C3 PPI-X-C3
- 2 CTR-Y1-OUT CTR-Y1-OUT PPI-Y-C0
- 3 CTR-Y2-OUT CTR-Y2-OUT PPI-Y-C3
- 4 CTR-Z1-OUT CTR-Z1-OUT CTR-Z1-OUT
- 5 CTR-Z2-OUT CTR-Z2-OUT CTR-Z2-OUT
- PC214E PC272E/PCI272
- ------------- -------------
- Sources 1 6
- 0 JUMPER-J5 PPI-X-C0
- 1 PPI-X-C3
- 2 PPI-Y-C0
- 3 PPI-Y-C3
- 4 PPI-Z-C0
- 5 PPI-Z-C3
- When an interrupt source is enabled in the interrupt source enable
- register, a rising edge on the source signal latches the corresponding
- bit to 1 in the interrupt status register.
- When the interrupt status register value as a whole (actually, just the
- 6 least significant bits) goes from zero to non-zero, the board will
- generate an interrupt. For level-triggered hardware interrupts (PCI
- card), the interrupt will remain asserted until the interrupt status
- register is cleared to zero. For edge-triggered hardware interrupts
- (ISA card), no further interrupts will occur until the interrupt status
- register is cleared to zero. To clear a bit to zero in the interrupt
- status register, the corresponding interrupt source must be disabled
- in the interrupt source enable register (there is no separate interrupt
- clear register).
- The PC214E does not have an interrupt source enable register or an
- interrupt status register; its 'INTERRUPT' subdevice has a single
- channel and its interrupt source is selected by the position of jumper
- J5.
- COMMANDS
- The driver supports a read streaming acquisition command on the
- 'INTERRUPT' subdevice. The channel list selects the interrupt sources
- to be enabled. All channels will be sampled together (convert_src ==
- TRIG_NOW). The scan begins a short time after the hardware interrupt
- occurs, subject to interrupt latencies (scan_begin_src == TRIG_EXT,
- scan_begin_arg == 0). The value read from the interrupt status register
- is packed into a short value, one bit per requested channel, in the
- order they appear in the channel list.
- */
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include "../comedidev.h"
- #include "comedi_pci.h"
- #include "8255.h"
- #include "8253.h"
- #define DIO200_DRIVER_NAME "amplc_dio200"
- /* PCI IDs */
- #define PCI_VENDOR_ID_AMPLICON 0x14dc
- #define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a
- #define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b
- #define PCI_DEVICE_ID_INVALID 0xffff
- /* 200 series registers */
- #define DIO200_IO_SIZE 0x20
- #define DIO200_XCLK_SCE 0x18 /* Group X clock selection register */
- #define DIO200_YCLK_SCE 0x19 /* Group Y clock selection register */
- #define DIO200_ZCLK_SCE 0x1a /* Group Z clock selection register */
- #define DIO200_XGAT_SCE 0x1b /* Group X gate selection register */
- #define DIO200_YGAT_SCE 0x1c /* Group Y gate selection register */
- #define DIO200_ZGAT_SCE 0x1d /* Group Z gate selection register */
- #define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */
- /*
- * Macros for constructing value for DIO_200_?CLK_SCE and
- * DIO_200_?GAT_SCE registers:
- *
- * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2.
- * 'chan' is the channel: 0, 1 or 2.
- * 'source' is the signal source: 0 to 7.
- */
- #define CLK_SCE(which, chan, source) (((which) << 5) | ((chan) << 3) | (source))
- #define GAT_SCE(which, chan, source) (((which) << 5) | ((chan) << 3) | (source))
- /*
- * Periods of the internal clock sources in nanoseconds.
- */
- static const unsigned clock_period[8] = {
- 0, /* dedicated clock input/output pin */
- 100, /* 10 MHz */
- 1000, /* 1 MHz */
- 10000, /* 100 kHz */
- 100000, /* 10 kHz */
- 1000000, /* 1 kHz */
- 0, /* OUT N-1 */
- 0 /* group clock input pin */
- };
- /*
- * Board descriptions.
- */
- enum dio200_bustype { isa_bustype, pci_bustype };
- enum dio200_model {
- pc212e_model,
- pc214e_model,
- pc215e_model, pci215_model,
- pc218e_model,
- pc272e_model, pci272_model,
- anypci_model
- };
- enum dio200_layout {
- pc212_layout,
- pc214_layout,
- pc215_layout,
- pc218_layout,
- pc272_layout
- };
- struct dio200_board {
- const char *name;
- unsigned short devid;
- enum dio200_bustype bustype;
- enum dio200_model model;
- enum dio200_layout layout;
- };
- static const struct dio200_board dio200_boards[] = {
- {
- .name = "pc212e",
- .bustype = isa_bustype,
- .model = pc212e_model,
- .layout = pc212_layout,
- },
- {
- .name = "pc214e",
- .bustype = isa_bustype,
- .model = pc214e_model,
- .layout = pc214_layout,
- },
- {
- .name = "pc215e",
- .bustype = isa_bustype,
- .model = pc215e_model,
- .layout = pc215_layout,
- },
- #ifdef CONFIG_COMEDI_PCI
- {
- .name = "pci215",
- .devid = PCI_DEVICE_ID_AMPLICON_PCI215,
- .bustype = pci_bustype,
- .model = pci215_model,
- .layout = pc215_layout,
- },
- #endif
- {
- .name = "pc218e",
- .bustype = isa_bustype,
- .model = pc218e_model,
- .layout = pc218_layout,
- },
- {
- .name = "pc272e",
- .bustype = isa_bustype,
- .model = pc272e_model,
- .layout = pc272_layout,
- },
- #ifdef CONFIG_COMEDI_PCI
- {
- .name = "pci272",
- .devid = PCI_DEVICE_ID_AMPLICON_PCI272,
- .bustype = pci_bustype,
- .model = pci272_model,
- .layout = pc272_layout,
- },
- #endif
- #ifdef CONFIG_COMEDI_PCI
- {
- .name = DIO200_DRIVER_NAME,
- .devid = PCI_DEVICE_ID_INVALID,
- .bustype = pci_bustype,
- .model = anypci_model, /* wildcard */
- },
- #endif
- };
- /*
- * Layout descriptions - some ISA and PCI board descriptions share the same
- * layout.
- */
- enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254 };
- #define DIO200_MAX_SUBDEVS 7
- #define DIO200_MAX_ISNS 6
- struct dio200_layout_struct {
- unsigned short n_subdevs; /* number of subdevices */
- unsigned char sdtype[DIO200_MAX_SUBDEVS]; /* enum dio200_sdtype */
- unsigned char sdinfo[DIO200_MAX_SUBDEVS]; /* depends on sdtype */
- char has_int_sce; /* has interrupt enable/status register */
- char has_clk_gat_sce; /* has clock/gate selection registers */
- };
- static const struct dio200_layout_struct dio200_layouts[] = {
- [pc212_layout] = {
- .n_subdevs = 6,
- .sdtype = {sd_8255, sd_8254, sd_8254, sd_8254,
- sd_8254,
- sd_intr},
- .sdinfo = {0x00, 0x08, 0x0C, 0x10, 0x14,
- 0x3F},
- .has_int_sce = 1,
- .has_clk_gat_sce = 1,
- },
- [pc214_layout] = {
- .n_subdevs = 4,
- .sdtype = {sd_8255, sd_8255, sd_8254,
- sd_intr},
- .sdinfo = {0x00, 0x08, 0x10, 0x01},
- .has_int_sce = 0,
- .has_clk_gat_sce = 0,
- },
- [pc215_layout] = {
- .n_subdevs = 5,
- .sdtype = {sd_8255, sd_8255, sd_8254,
- sd_8254,
- sd_intr},
- .sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F},
- .has_int_sce = 1,
- .has_clk_gat_sce = 1,
- },
- [pc218_layout] = {
- .n_subdevs = 7,
- .sdtype = {sd_8254, sd_8254, sd_8255, sd_8254,
- sd_8254,
- sd_intr},
- .sdinfo = {0x00, 0x04, 0x08, 0x0C, 0x10,
- 0x14,
- 0x3F},
- .has_int_sce = 1,
- .has_clk_gat_sce = 1,
- },
- [pc272_layout] = {
- .n_subdevs = 4,
- .sdtype = {sd_8255, sd_8255, sd_8255,
- sd_intr},
- .sdinfo = {0x00, 0x08, 0x10, 0x3F},
- .has_int_sce = 1,
- .has_clk_gat_sce = 0,
- },
- };
- /*
- * PCI driver table.
- */
- #ifdef CONFIG_COMEDI_PCI
- static DEFINE_PCI_DEVICE_TABLE(dio200_pci_table) = {
- {
- PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215,
- PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
- PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272,
- PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
- 0}
- };
- MODULE_DEVICE_TABLE(pci, dio200_pci_table);
- #endif /* CONFIG_COMEDI_PCI */
- /*
- * Useful for shorthand access to the particular board structure
- */
- #define thisboard ((const struct dio200_board *)dev->board_ptr)
- #define thislayout (&dio200_layouts[((struct dio200_board *) \
- dev->board_ptr)->layout])
- /* 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 struct comedi_device struct.
- */
- struct dio200_private {
- #ifdef CONFIG_COMEDI_PCI
- struct pci_dev *pci_dev; /* PCI device */
- #endif
- int intr_sd;
- };
- #define devpriv ((struct dio200_private *)dev->private)
- struct dio200_subdev_8254 {
- unsigned long iobase; /* Counter base address */
- unsigned long clk_sce_iobase; /* CLK_SCE base address */
- unsigned long gat_sce_iobase; /* GAT_SCE base address */
- int which; /* Bit 5 of CLK_SCE or GAT_SCE */
- int has_clk_gat_sce;
- unsigned clock_src[3]; /* Current clock sources */
- unsigned gate_src[3]; /* Current gate sources */
- spinlock_t spinlock;
- };
- struct dio200_subdev_intr {
- unsigned long iobase;
- spinlock_t spinlock;
- int active;
- int has_int_sce;
- unsigned int valid_isns;
- unsigned int enabled_isns;
- unsigned int stopcount;
- int continuous;
- };
- /*
- * The struct 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 dio200_attach(struct comedi_device *dev,
- struct comedi_devconfig *it);
- static int dio200_detach(struct comedi_device *dev);
- static struct comedi_driver driver_amplc_dio200 = {
- .driver_name = DIO200_DRIVER_NAME,
- .module = THIS_MODULE,
- .attach = dio200_attach,
- .detach = dio200_detach,
- .board_name = &dio200_boards[0].name,
- .offset = sizeof(struct dio200_board),
- .num_names = ARRAY_SIZE(dio200_boards),
- };
- #ifdef CONFIG_COMEDI_PCI
- static int __devinit driver_amplc_dio200_pci_probe(struct pci_dev *dev,
- const struct pci_device_id
- *ent)
- {
- return comedi_pci_auto_config(dev, driver_amplc_dio200.driver_name);
- }
- static void __devexit driver_amplc_dio200_pci_remove(struct pci_dev *dev)
- {
- comedi_pci_auto_unconfig(dev);
- }
- static struct pci_driver driver_amplc_dio200_pci_driver = {
- .id_table = dio200_pci_table,
- .probe = &driver_amplc_dio200_pci_probe,
- .remove = __devexit_p(&driver_amplc_dio200_pci_remove)
- };
- static int __init driver_amplc_dio200_init_module(void)
- {
- int retval;
- retval = comedi_driver_register(&driver_amplc_dio200);
- if (retval < 0)
- return retval;
- driver_amplc_dio200_pci_driver.name =
- (char *)driver_amplc_dio200.driver_name;
- return pci_register_driver(&driver_amplc_dio200_pci_driver);
- }
- static void __exit driver_amplc_dio200_cleanup_module(void)
- {
- pci_unregister_driver(&driver_amplc_dio200_pci_driver);
- comedi_driver_unregister(&driver_amplc_dio200);
- }
- module_init(driver_amplc_dio200_init_module);
- module_exit(driver_amplc_dio200_cleanup_module);
- #else
- static int __init driver_amplc_dio200_init_module(void)
- {
- return comedi_driver_register(&driver_amplc_dio200);
- }
- static void __exit driver_amplc_dio200_cleanup_module(void)
- {
- comedi_driver_unregister(&driver_amplc_dio200);
- }
- module_init(driver_amplc_dio200_init_module);
- module_exit(driver_amplc_dio200_cleanup_module);
- #endif
- /*
- * This function looks for a PCI device matching the requested board name,
- * bus and slot.
- */
- #ifdef CONFIG_COMEDI_PCI
- static int
- dio200_find_pci(struct 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(dio200_boards); i++) {
- if (dio200_boards[i].bustype != pci_bustype)
- continue;
- if (pci_dev->device == dio200_boards[i].devid) {
- /* Change board_ptr to matched board. */
- dev->board_ptr = &dio200_boards[i];
- break;
- }
- }
- if (i == ARRAY_SIZE(dio200_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
- /*
- * This function checks and requests an I/O region, reporting an error
- * if there is a conflict.
- */
- static int
- dio200_request_region(unsigned minor, unsigned long from, unsigned long extent)
- {
- if (!from || !request_region(from, extent, DIO200_DRIVER_NAME)) {
- printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
- minor, from, extent);
- return -EIO;
- }
- return 0;
- }
- /*
- * 'insn_bits' function for an 'INTERRUPT' subdevice.
- */
- static int
- dio200_subdev_intr_insn_bits(struct comedi_device *dev,
- struct comedi_subdevice *s,
- struct comedi_insn *insn, unsigned int *data)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- if (subpriv->has_int_sce) {
- /* Just read the interrupt status register. */
- data[1] = inb(subpriv->iobase) & subpriv->valid_isns;
- } else {
- /* No interrupt status register. */
- data[0] = 0;
- }
- return 2;
- }
- /*
- * Called to stop acquisition for an 'INTERRUPT' subdevice.
- */
- static void dio200_stop_intr(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- subpriv->active = 0;
- subpriv->enabled_isns = 0;
- if (subpriv->has_int_sce)
- outb(0, subpriv->iobase);
- }
- /*
- * Called to start acquisition for an 'INTERRUPT' subdevice.
- */
- static int dio200_start_intr(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- unsigned int n;
- unsigned isn_bits;
- struct dio200_subdev_intr *subpriv = s->private;
- struct comedi_cmd *cmd = &s->async->cmd;
- int retval = 0;
- if (!subpriv->continuous && subpriv->stopcount == 0) {
- /* An empty acquisition! */
- s->async->events |= COMEDI_CB_EOA;
- subpriv->active = 0;
- retval = 1;
- } else {
- /* Determine interrupt sources to enable. */
- isn_bits = 0;
- if (cmd->chanlist) {
- for (n = 0; n < cmd->chanlist_len; n++)
- isn_bits |= (1U << CR_CHAN(cmd->chanlist[n]));
- }
- isn_bits &= subpriv->valid_isns;
- /* Enable interrupt sources. */
- subpriv->enabled_isns = isn_bits;
- if (subpriv->has_int_sce)
- outb(isn_bits, subpriv->iobase);
- }
- return retval;
- }
- /*
- * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice.
- */
- static int
- dio200_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s,
- unsigned int trignum)
- {
- struct dio200_subdev_intr *subpriv;
- unsigned long flags;
- int event = 0;
- if (trignum != 0)
- return -EINVAL;
- subpriv = s->private;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- s->async->inttrig = NULL;
- if (subpriv->active)
- event = dio200_start_intr(dev, s);
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- if (event)
- comedi_event(dev, s);
- return 1;
- }
- /*
- * This is called from the interrupt service routine to handle a read
- * scan on an 'INTERRUPT' subdevice.
- */
- static int dio200_handle_read_intr(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- unsigned triggered;
- unsigned intstat;
- unsigned cur_enabled;
- unsigned int oldevents;
- unsigned long flags;
- triggered = 0;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- oldevents = s->async->events;
- if (subpriv->has_int_sce) {
- /*
- * Collect interrupt sources that have triggered and disable
- * them temporarily. Loop around until no extra interrupt
- * sources have triggered, at which point, the valid part of
- * the interrupt status register will read zero, clearing the
- * cause of the interrupt.
- *
- * Mask off interrupt sources already seen to avoid infinite
- * loop in case of misconfiguration.
- */
- cur_enabled = subpriv->enabled_isns;
- while ((intstat = (inb(subpriv->iobase) & subpriv->valid_isns
- & ~triggered)) != 0) {
- triggered |= intstat;
- cur_enabled &= ~triggered;
- outb(cur_enabled, subpriv->iobase);
- }
- } else {
- /*
- * No interrupt status register. Assume the single interrupt
- * source has triggered.
- */
- triggered = subpriv->enabled_isns;
- }
- if (triggered) {
- /*
- * Some interrupt sources have triggered and have been
- * temporarily disabled to clear the cause of the interrupt.
- *
- * Reenable them NOW to minimize the time they are disabled.
- */
- cur_enabled = subpriv->enabled_isns;
- if (subpriv->has_int_sce)
- outb(cur_enabled, subpriv->iobase);
- if (subpriv->active) {
- /*
- * The command is still active.
- *
- * Ignore interrupt sources that the command isn't
- * interested in (just in case there's a race
- * condition).
- */
- if (triggered & subpriv->enabled_isns) {
- /* Collect scan data. */
- short val;
- unsigned int n, ch, len;
- val = 0;
- len = s->async->cmd.chanlist_len;
- for (n = 0; n < len; n++) {
- ch = CR_CHAN(s->async->cmd.chanlist[n]);
- if (triggered & (1U << ch))
- val |= (1U << n);
- }
- /* Write the scan to the buffer. */
- if (comedi_buf_put(s->async, val)) {
- s->async->events |= (COMEDI_CB_BLOCK |
- COMEDI_CB_EOS);
- } else {
- /* Error! Stop acquisition. */
- dio200_stop_intr(dev, s);
- s->async->events |= COMEDI_CB_ERROR
- | COMEDI_CB_OVERFLOW;
- comedi_error(dev, "buffer overflow");
- }
- /* Check for end of acquisition. */
- if (!subpriv->continuous) {
- /* stop_src == TRIG_COUNT */
- if (subpriv->stopcount > 0) {
- subpriv->stopcount--;
- if (subpriv->stopcount == 0) {
- s->async->events |=
- COMEDI_CB_EOA;
- dio200_stop_intr(dev,
- s);
- }
- }
- }
- }
- }
- }
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- if (oldevents != s->async->events)
- comedi_event(dev, s);
- return (triggered != 0);
- }
- /*
- * 'cancel' function for an 'INTERRUPT' subdevice.
- */
- static int dio200_subdev_intr_cancel(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- unsigned long flags;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- if (subpriv->active)
- dio200_stop_intr(dev, s);
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- return 0;
- }
- /*
- * 'do_cmdtest' function for an 'INTERRUPT' subdevice.
- */
- static int
- dio200_subdev_intr_cmdtest(struct comedi_device *dev,
- struct comedi_subdevice *s, struct comedi_cmd *cmd)
- {
- int err = 0;
- unsigned int tmp;
- /* step 1: make sure trigger sources are trivially valid */
- tmp = cmd->start_src;
- cmd->start_src &= (TRIG_NOW | TRIG_INT);
- 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_NOW;
- 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_COUNT | TRIG_NONE);
- if (!cmd->stop_src || tmp != cmd->stop_src)
- err++;
- if (err)
- return 1;
- /* step 2: make sure trigger sources are unique and mutually
- compatible */
- /* these tests are true if more than one _src bit is set */
- if ((cmd->start_src & (cmd->start_src - 1)) != 0)
- err++;
- if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0)
- err++;
- if ((cmd->convert_src & (cmd->convert_src - 1)) != 0)
- err++;
- if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0)
- err++;
- if ((cmd->stop_src & (cmd->stop_src - 1)) != 0)
- err++;
- if (err)
- return 2;
- /* step 3: make sure arguments are trivially compatible */
- /* cmd->start_src == TRIG_NOW || cmd->start_src == TRIG_INT */
- if (cmd->start_arg != 0) {
- cmd->start_arg = 0;
- err++;
- }
- /* cmd->scan_begin_src == TRIG_EXT */
- if (cmd->scan_begin_arg != 0) {
- cmd->scan_begin_arg = 0;
- err++;
- }
- /* cmd->convert_src == TRIG_NOW */
- if (cmd->convert_arg != 0) {
- cmd->convert_arg = 0;
- err++;
- }
- /* cmd->scan_end_src == TRIG_COUNT */
- if (cmd->scan_end_arg != cmd->chanlist_len) {
- cmd->scan_end_arg = cmd->chanlist_len;
- err++;
- }
- switch (cmd->stop_src) {
- case TRIG_COUNT:
- /* any count allowed */
- break;
- case TRIG_NONE:
- if (cmd->stop_arg != 0) {
- cmd->stop_arg = 0;
- err++;
- }
- break;
- default:
- break;
- }
- if (err)
- return 3;
- /* step 4: fix up any arguments */
- /* if (err) return 4; */
- return 0;
- }
- /*
- * 'do_cmd' function for an 'INTERRUPT' subdevice.
- */
- static int dio200_subdev_intr_cmd(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct comedi_cmd *cmd = &s->async->cmd;
- struct dio200_subdev_intr *subpriv = s->private;
- unsigned long flags;
- int event = 0;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- subpriv->active = 1;
- /* Set up end of acquisition. */
- switch (cmd->stop_src) {
- case TRIG_COUNT:
- subpriv->continuous = 0;
- subpriv->stopcount = cmd->stop_arg;
- break;
- default:
- /* TRIG_NONE */
- subpriv->continuous = 1;
- subpriv->stopcount = 0;
- break;
- }
- /* Set up start of acquisition. */
- switch (cmd->start_src) {
- case TRIG_INT:
- s->async->inttrig = dio200_inttrig_start_intr;
- break;
- default:
- /* TRIG_NOW */
- event = dio200_start_intr(dev, s);
- break;
- }
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- if (event)
- comedi_event(dev, s);
- return 0;
- }
- /*
- * This function initializes an 'INTERRUPT' subdevice.
- */
- static int
- dio200_subdev_intr_init(struct comedi_device *dev, struct comedi_subdevice *s,
- unsigned long iobase, unsigned valid_isns,
- int has_int_sce)
- {
- struct dio200_subdev_intr *subpriv;
- subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
- if (!subpriv) {
- printk(KERN_ERR "comedi%d: error! out of memory!\n",
- dev->minor);
- return -ENOMEM;
- }
- subpriv->iobase = iobase;
- subpriv->has_int_sce = has_int_sce;
- subpriv->valid_isns = valid_isns;
- spin_lock_init(&subpriv->spinlock);
- if (has_int_sce)
- outb(0, subpriv->iobase); /* Disable interrupt sources. */
- s->private = subpriv;
- s->type = COMEDI_SUBD_DI;
- s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
- if (has_int_sce) {
- s->n_chan = DIO200_MAX_ISNS;
- s->len_chanlist = DIO200_MAX_ISNS;
- } else {
- /* No interrupt source register. Support single channel. */
- s->n_chan = 1;
- s->len_chanlist = 1;
- }
- s->range_table = &range_digital;
- s->maxdata = 1;
- s->insn_bits = dio200_subdev_intr_insn_bits;
- s->do_cmdtest = dio200_subdev_intr_cmdtest;
- s->do_cmd = dio200_subdev_intr_cmd;
- s->cancel = dio200_subdev_intr_cancel;
- return 0;
- }
- /*
- * This function cleans up an 'INTERRUPT' subdevice.
- */
- static void
- dio200_subdev_intr_cleanup(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- kfree(subpriv);
- }
- /*
- * Interrupt service routine.
- */
- static irqreturn_t dio200_interrupt(int irq, void *d)
- {
- struct comedi_device *dev = d;
- int handled;
- if (!dev->attached)
- return IRQ_NONE;
- if (devpriv->intr_sd >= 0) {
- handled = dio200_handle_read_intr(dev,
- dev->subdevices +
- devpriv->intr_sd);
- } else {
- handled = 0;
- }
- return IRQ_RETVAL(handled);
- }
- /*
- * Handle 'insn_read' for an '8254' counter subdevice.
- */
- static int
- dio200_subdev_8254_read(struct comedi_device *dev, struct comedi_subdevice *s,
- struct comedi_insn *insn, unsigned int *data)
- {
- struct dio200_subdev_8254 *subpriv = s->private;
- int chan = CR_CHAN(insn->chanspec);
- unsigned long flags;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- data[0] = i8254_read(subpriv->iobase, 0, chan);
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- return 1;
- }
- /*
- * Handle 'insn_write' for an '8254' counter subdevice.
- */
- static int
- dio200_subdev_8254_write(struct comedi_device *dev, struct comedi_subdevice *s,
- struct comedi_insn *insn, unsigned int *data)
- {
- struct dio200_subdev_8254 *subpriv = s->private;
- int chan = CR_CHAN(insn->chanspec);
- unsigned long flags;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- i8254_write(subpriv->iobase, 0, chan, data[0]);
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- return 1;
- }
- /*
- * Set gate source for an '8254' counter subdevice channel.
- */
- static int
- dio200_set_gate_src(struct dio200_subdev_8254 *subpriv,
- unsigned int counter_number, unsigned int gate_src)
- {
- unsigned char byte;
- if (!subpriv->has_clk_gat_sce)
- return -1;
- if (counter_number > 2)
- return -1;
- if (gate_src > 7)
- return -1;
- subpriv->gate_src[counter_number] = gate_src;
- byte = GAT_SCE(subpriv->which, counter_number, gate_src);
- outb(byte, subpriv->gat_sce_iobase);
- return 0;
- }
- /*
- * Get gate source for an '8254' counter subdevice channel.
- */
- static int
- dio200_get_gate_src(struct dio200_subdev_8254 *subpriv,
- unsigned int counter_number)
- {
- if (!subpriv->has_clk_gat_sce)
- return -1;
- if (counter_number > 2)
- return -1;
- return subpriv->gate_src[counter_number];
- }
- /*
- * Set clock source for an '8254' counter subdevice channel.
- */
- static int
- dio200_set_clock_src(struct dio200_subdev_8254 *subpriv,
- unsigned int counter_number, unsigned int clock_src)
- {
- unsigned char byte;
- if (!subpriv->has_clk_gat_sce)
- return -1;
- if (counter_number > 2)
- return -1;
- if (clock_src > 7)
- return -1;
- subpriv->clock_src[counter_number] = clock_src;
- byte = CLK_SCE(subpriv->which, counter_number, clock_src);
- outb(byte, subpriv->clk_sce_iobase);
- return 0;
- }
- /*
- * Get clock source for an '8254' counter subdevice channel.
- */
- static int
- dio200_get_clock_src(struct dio200_subdev_8254 *subpriv,
- unsigned int counter_number, unsigned int *period_ns)
- {
- unsigned clock_src;
- if (!subpriv->has_clk_gat_sce)
- return -1;
- if (counter_number > 2)
- return -1;
- clock_src = subpriv->clock_src[counter_number];
- *period_ns = clock_period[clock_src];
- return clock_src;
- }
- /*
- * Handle 'insn_config' for an '8254' counter subdevice.
- */
- static int
- dio200_subdev_8254_config(struct comedi_device *dev, struct comedi_subdevice *s,
- struct comedi_insn *insn, unsigned int *data)
- {
- struct dio200_subdev_8254 *subpriv = s->private;
- int ret = 0;
- int chan = CR_CHAN(insn->chanspec);
- unsigned long flags;
- spin_lock_irqsave(&subpriv->spinlock, flags);
- switch (data[0]) {
- case INSN_CONFIG_SET_COUNTER_MODE:
- ret = i8254_set_mode(subpriv->iobase, 0, chan, data[1]);
- if (ret < 0)
- ret = -EINVAL;
- break;
- case INSN_CONFIG_8254_READ_STATUS:
- data[1] = i8254_status(subpriv->iobase, 0, chan);
- break;
- case INSN_CONFIG_SET_GATE_SRC:
- ret = dio200_set_gate_src(subpriv, chan, data[2]);
- if (ret < 0)
- ret = -EINVAL;
- break;
- case INSN_CONFIG_GET_GATE_SRC:
- ret = dio200_get_gate_src(subpriv, chan);
- if (ret < 0) {
- ret = -EINVAL;
- break;
- }
- data[2] = ret;
- break;
- case INSN_CONFIG_SET_CLOCK_SRC:
- ret = dio200_set_clock_src(subpriv, chan, data[1]);
- if (ret < 0)
- ret = -EINVAL;
- break;
- case INSN_CONFIG_GET_CLOCK_SRC:
- ret = dio200_get_clock_src(subpriv, chan, &data[2]);
- if (ret < 0) {
- ret = -EINVAL;
- break;
- }
- data[1] = ret;
- break;
- default:
- ret = -EINVAL;
- break;
- }
- spin_unlock_irqrestore(&subpriv->spinlock, flags);
- return ret < 0 ? ret : insn->n;
- }
- /*
- * This function initializes an '8254' counter subdevice.
- *
- * Note: iobase is the base address of the board, not the subdevice;
- * offset is the offset to the 8254 chip.
- */
- static int
- dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s,
- unsigned long iobase, unsigned offset,
- int has_clk_gat_sce)
- {
- struct dio200_subdev_8254 *subpriv;
- unsigned int chan;
- subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
- if (!subpriv) {
- printk(KERN_ERR "comedi%d: error! out of memory!\n",
- dev->minor);
- return -ENOMEM;
- }
- s->private = subpriv;
- s->type = COMEDI_SUBD_COUNTER;
- s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
- s->n_chan = 3;
- s->maxdata = 0xFFFF;
- s->insn_read = dio200_subdev_8254_read;
- s->insn_write = dio200_subdev_8254_write;
- s->insn_config = dio200_subdev_8254_config;
- spin_lock_init(&subpriv->spinlock);
- subpriv->iobase = offset + iobase;
- subpriv->has_clk_gat_sce = has_clk_gat_sce;
- if (has_clk_gat_sce) {
- /* Derive CLK_SCE and GAT_SCE register offsets from
- * 8254 offset. */
- subpriv->clk_sce_iobase =
- DIO200_XCLK_SCE + (offset >> 3) + iobase;
- subpriv->gat_sce_iobase =
- DIO200_XGAT_SCE + (offset >> 3) + iobase;
- subpriv->which = (offset >> 2) & 1;
- }
- /* Initialize channels. */
- for (chan = 0; chan < 3; chan++) {
- i8254_set_mode(subpriv->iobase, 0, chan,
- I8254_MODE0 | I8254_BINARY);
- if (subpriv->has_clk_gat_sce) {
- /* Gate source 0 is VCC (logic 1). */
- dio200_set_gate_src(subpriv, chan, 0);
- /* Clock source 0 is the dedicated clock input. */
- dio200_set_clock_src(subpriv, chan, 0);
- }
- }
- return 0;
- }
- /*
- * This function cleans up an '8254' counter subdevice.
- */
- static void
- dio200_subdev_8254_cleanup(struct comedi_device *dev,
- struct comedi_subdevice *s)
- {
- struct dio200_subdev_intr *subpriv = s->private;
- kfree(subpriv);
- }
- /*
- * 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 dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it)
- {
- struct comedi_subdevice *s;
- unsigned long iobase = 0;
- unsigned int irq = 0;
- #ifdef CONFIG_COMEDI_PCI
- struct pci_dev *pci_dev = NULL;
- int bus = 0, slot = 0;
- #endif
- const struct dio200_layout_struct *layout;
- int share_irq = 0;
- int sdx;
- unsigned n;
- int ret;
- printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
- DIO200_DRIVER_NAME);
- ret = alloc_private(dev, sizeof(struct dio200_private));
- if (ret < 0) {
- printk(KERN_ERR "comedi%d: error! out of memory!\n",
- dev->minor);
- return ret;
- }
- /* Process options. */
- switch (thisboard->bustype) {
- case isa_bustype:
- iobase = it->options[0];
- irq = it->options[1];
- share_irq = 0;
- break;
- #ifdef CONFIG_COMEDI_PCI
- case pci_bustype:
- bus = it->options[0];
- slot = it->options[1];
- share_irq = 1;
- ret = dio200_find_pci(dev, bus, slot, &pci_dev);
- if (ret < 0)
- return ret;
- devpriv->pci_dev = pci_dev;
- break;
- #endif
- default:
- printk(KERN_ERR
- "comedi%d: %s: BUG! cannot determine board type!\n",
- dev->minor, DIO200_DRIVER_NAME);
- return -EINVAL;
- break;
- }
- devpriv->intr_sd = -1;
- /* Enable device and reserve I/O spaces. */
- #ifdef CONFIG_COMEDI_PCI
- if (pci_dev) {
- ret = comedi_pci_enable(pci_dev, DIO200_DRIVER_NAME);
- if (ret < 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);
- irq = pci_dev->irq;
- } else
- #endif
- {
- ret = dio200_request_region(dev->minor, iobase, DIO200_IO_SIZE);
- if (ret < 0)
- return ret;
- }
- dev->iobase = iobase;
- layout = thislayout;
- ret = alloc_subdevices(dev, layout->n_subdevs);
- if (ret < 0) {
- printk(KERN_ERR "comedi%d: error! out of memory!\n",
- dev->minor);
- return ret;
- }
- for (n = 0; n < dev->n_subdevices; n++) {
- s = &dev->subdevices[n];
- switch (layout->sdtype[n]) {
- case sd_8254:
- /* counter subdevice (8254) */
- ret = dio200_subdev_8254_init(dev, s, iobase,
- layout->sdinfo[n],
- layout->has_clk_gat_sce);
- if (ret < 0)
- return ret;
- break;
- case sd_8255:
- /* digital i/o subdevice (8255) */
- ret = subdev_8255_init(dev, s, NULL,
- iobase + layout->sdinfo[n]);
- if (ret < 0)
- return ret;
- break;
- case sd_intr:
- /* 'INTERRUPT' subdevice */
- if (irq) {
- ret = dio200_subdev_intr_init(dev, s,
- iobase +
- DIO200_INT_SCE,
- layout->sdinfo[n],
- layout->
- has_int_sce);
- if (ret < 0)
- return ret;
- devpriv->intr_sd = n;
- } else {
- s->type = COMEDI_SUBD_UNUSED;
- }
- break;
- default:
- s->type = COMEDI_SUBD_UNUSED;
- break;
- }
- }
- sdx = devpriv->intr_sd;
- if (sdx >= 0 && sdx < dev->n_subdevices)
- dev->read_subdev = &dev->subdevices[sdx];
- dev->board_name = thisboard->name;
- if (irq) {
- unsigned long flags = share_irq ? IRQF_SHARED : 0;
- if (request_irq(irq, dio200_interrupt, flags,
- DIO200_DRIVER_NAME, dev) >= 0) {
- dev->irq = irq;
- } else {
- printk(KERN_WARNING
- "comedi%d: warning! irq %u unavailable!\n",
- dev->minor, irq);
- }
- }
- 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
- }
- if (irq)
- printk("(irq %u%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 dio200_detach(struct comedi_device *dev)
- {
- const struct dio200_layout_struct *layout;
- unsigned n;
- printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
- DIO200_DRIVER_NAME);
- if (dev->irq)
- free_irq(dev->irq, dev);
- if (dev->subdevices) {
- layout = thislayout;
- for (n = 0; n < dev->n_subdevices; n++) {
- struct comedi_subdevice *s = &dev->subdevices[n];
- switch (layout->sdtype[n]) {
- case sd_8254:
- dio200_subdev_8254_cleanup(dev, s);
- break;
- case sd_8255:
- subdev_8255_cleanup(dev, s);
- break;
- case sd_intr:
- dio200_subdev_intr_cleanup(dev, s);
- break;
- default:
- break;
- }
- }
- }
- if (devpriv) {
- #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, DIO200_IO_SIZE);
- }
- }
- if (dev->board_name)
- printk(KERN_INFO "comedi%d: %s removed\n",
- dev->minor, dev->board_name);
- return 0;
- }
- MODULE_AUTHOR("Comedi http://www.comedi.org");
- MODULE_DESCRIPTION("Comedi low-level driver");
- MODULE_LICENSE("GPL");