Commit 7ff7e4c2 authored by Ian Abbott's avatar Ian Abbott Committed by Greg Kroah-Hartman

staging: comedi: amplc_dio200: split into ISA, PCI and common

Split the "amplc_dio200" comedi driver module into separate driver
modules for ISA and PCI boards with a common module for the shared code.

Keep the old name "amplc_dio200" for the ISA board driver as the module
may be modprobed with this name by a script.  (If the script uses insmod
it will need modifying to load the "amplc_dio200_common" module first.)

Use the module name "amplc_dio200_pci" for the PCI board driver.  On
most systems this will be auto-loaded.

Use the module name "amplc_dio200_common" for the module containing the
shared code.  This is normally loaded as a dependency of the other two
modules.

"amplc_dio200_common" exports the following functions:

* `amplc_dio200_common_attach()`: this is basically the old
  `dio200_common_attach()` from the combined driver module.  It is
  called from the driver-specific attach or auto-attach routines.
* `amplc_dio200_common_detach()`: this is most of the old
  `dio200_detach()`.  It is called from the driver-specific detach
  routine.
* `amplc_dio200_set_enhance()`: this is a new function called during
  initialization of PCIe cards to enable "enhanced" mode.
Signed-off-by: default avatarIan Abbott <abbotti@mev.co.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8d688377
......@@ -12,6 +12,7 @@ obj-$(CONFIG_COMEDI_SKEL) += skel.o
# Comedi ISA drivers
obj-$(CONFIG_COMEDI_ACL7225B) += acl7225b.o
obj-$(CONFIG_COMEDI_AMPLC_DIO200_ISA) += amplc_dio200.o
obj-$(CONFIG_COMEDI_PCL711) += pcl711.o
obj-$(CONFIG_COMEDI_PCL724) += pcl724.o
obj-$(CONFIG_COMEDI_PCL725) += pcl725.o
......@@ -77,7 +78,7 @@ obj-$(CONFIG_COMEDI_ADV_PCI1710) += adv_pci1710.o
obj-$(CONFIG_COMEDI_ADV_PCI1723) += adv_pci1723.o
obj-$(CONFIG_COMEDI_ADV_PCI1724) += adv_pci1724.o
obj-$(CONFIG_COMEDI_ADV_PCI_DIO) += adv_pci_dio.o
obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200.o
obj-$(CONFIG_COMEDI_AMPLC_DIO200_PCI) += amplc_dio200_pci.o
obj-$(CONFIG_COMEDI_AMPLC_PC236) += amplc_pc236.o
obj-$(CONFIG_COMEDI_AMPLC_PC263) += amplc_pc263.o
obj-$(CONFIG_COMEDI_AMPLC_PCI224) += amplc_pci224.o
......@@ -134,5 +135,6 @@ obj-$(CONFIG_COMEDI_NI_TIOCMD) += ni_tiocmd.o
obj-$(CONFIG_COMEDI_NI_LABPC) += ni_labpc.o
obj-$(CONFIG_COMEDI_8255) += 8255.o
obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200_common.o
obj-$(CONFIG_COMEDI_DAS08) += das08.o
obj-$(CONFIG_COMEDI_FC) += comedi_fc.o
/*
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/>
Driver for Amplicon PC212E, PC214E, PC215E, PC218E, PC272E.
Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
COMEDI - Linux Control and Measurement Device Interface
Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
......@@ -26,26 +25,23 @@
*/
/*
* Driver: amplc_dio200
* Description: Amplicon 200 Series Digital I/O
* Description: Amplicon 200 Series ISA Digital I/O
* Author: Ian Abbott <abbotti@mev.co.uk>
* Devices: [Amplicon] PC212E (pc212e), PC214E (pc214e), PC215E (pc215e),
* PCI215 (pci215), PCIe215 (pcie215), PC218E (pc218e), PCIe236 (pcie236),
* PC272E (pc272e), PCI272 (pci272), PCIe296 (pcie296)
* Updated: Wed, 24 Oct 2012 16:22:34 +0100
* PC218E (pc218e), PC272E (pc272e)
* Updated: Mon, 18 Mar 2013 14:40:41 +0000
*
* Status: works
*
* Configuration options - PC212E, PC214E, PC215E, PC218E, PC272E:
* Configuration options:
* [0] - I/O port base address
* [1] - IRQ (optional, but commands won't work without it)
*
* Manual configuration of PCI(e) cards is not supported; they are configured
* automatically.
*
* Passing a zero for an option is the same as leaving it unspecified.
*
* SUBDEVICES
*
* PC212E PC214E PC215E/PCI215
* PC212E PC214E PC215E
* ------------- ------------- -------------
* Subdevices 6 4 5
* 0 PPI-X PPI-X PPI-X
......@@ -55,29 +51,16 @@
* 4 CTR-Z2 INTERRUPT
* 5 INTERRUPT
*
* PCIe215 PC218E PCIe236
* ------------- ------------- -------------
* Subdevices 8 7 8
* 0 PPI-X CTR-X1 PPI-X
* 1 UNUSED CTR-X2 UNUSED
* 2 PPI-Y CTR-Y1 UNUSED
* 3 UNUSED CTR-Y2 UNUSED
* 4 CTR-Z1 CTR-Z1 CTR-Z1
* 5 CTR-Z2 CTR-Z2 CTR-Z2
* 6 TIMER INTERRUPT TIMER
* 7 INTERRUPT INTERRUPT
*
* PC272E/PCI272 PCIe296
* PC218E PC272E
* ------------- -------------
* Subdevices 4 8
* 0 PPI-X PPI-X1
* 1 PPI-Y PPI-X2
* 2 PPI-Z PPI-Y1
* 3 INTERRUPT PPI-Y2
* 4 CTR-Z1
* 5 CTR-Z2
* 6 TIMER
* 7 INTERRUPT
* Subdevices 7 4
* 0 CTR-X1 PPI-X
* 1 CTR-X2 PPI-Y
* 2 CTR-Y1 PPI-Z
* 3 CTR-Y2 INTERRUPT
* 4 CTR-Z1
* 5 CTR-Z2
* 6 INTERRUPT
*
* Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels
* are configurable as inputs or outputs in four groups:
......@@ -120,14 +103,6 @@
* the SK1 connector. This pin is shared by all three counter
* channels on the chip.
*
* For the PCIe boards, clock sources in the range 0 to 31 are allowed
* and the following additional clock sources are defined:
*
* 8. HIGH logic level.
* 9. LOW logic level.
* 10. "Pattern present" signal.
* 11. Internal 20 MHz clock.
*
* 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.
......@@ -149,27 +124,6 @@
* 6. Reserved.
* 7. Reserved.
*
* For the PCIe boards, gate sources in the range 0 to 31 are allowed;
* the following additional clock sources and clock sources 6 and 7 are
* (re)defined:
*
* 6. /GAT n, negated version of the counter channel's dedicated
* GAT input (negated version of gate source 2).
* 7. OUT n-2, the non-inverted output of counter channel n-2
* (negated version of gate source 3).
* 8. "Pattern present" signal, HIGH while pattern present.
* 9. "Pattern occurred" latched signal, latches HIGH when pattern
* occurs.
* 10. "Pattern gone away" latched signal, latches LOW when pattern
* goes away after it occurred.
* 11. Negated "pattern present" signal, LOW while pattern present
* (negated version of gate source 8).
* 12. Negated "pattern occurred" latched signal, latches LOW when
* pattern occurs (negated version of gate source 9).
* 13. Negated "pattern gone away" latched signal, latches LOW when
* pattern goes away after it occurred (negated version of gate
* source 10).
*
* INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate
* source in data[2].
*
......@@ -186,8 +140,6 @@
* 3. The counter subdevices are connected in a ring, so the highest
* counter subdevice precedes the lowest.
*
* The 'TIMER' subdevice is a free-running 32-bit timer subdevice.
*
* 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
......@@ -196,7 +148,7 @@
*
* INTERRUPT SOURCES
*
* PC212E PC214E PC215E/PCI215
* PC212E PC214E PC215E
* ------------- ------------- -------------
* Sources 6 1 6
* 0 PPI-X-C0 JUMPER-J5 PPI-X-C0
......@@ -206,25 +158,15 @@
* 4 CTR-Z1-OUT1 CTR-Z1-OUT1
* 5 CTR-Z2-OUT1 CTR-Z2-OUT1
*
* PCIe215 PC218E PCIe236
* ------------- ------------- -------------
* Sources 6 6 6
* 0 PPI-X-C0 CTR-X1-OUT1 PPI-X-C0
* 1 PPI-X-C3 CTR-X2-OUT1 PPI-X-C3
* 2 PPI-Y-C0 CTR-Y1-OUT1 unused
* 3 PPI-Y-C3 CTR-Y2-OUT1 unused
* 4 CTR-Z1-OUT1 CTR-Z1-OUT1 CTR-Z1-OUT1
* 5 CTR-Z2-OUT1 CTR-Z2-OUT1 CTR-Z2-OUT1
*
* PC272E/PCI272 PCIe296
* PC218E PC272E
* ------------- -------------
* Sources 6 6
* 0 PPI-X-C0 PPI-X1-C0
* 1 PPI-X-C3 PPI-X1-C3
* 2 PPI-Y-C0 PPI-Y1-C0
* 3 PPI-Y-C3 PPI-Y1-C3
* 4 PPI-Z-C0 CTR-Z1-OUT1
* 5 PPI-Z-C3 CTR-Z2-OUT1
* 0 CTR-X1-OUT1 PPI-X-C0
* 1 CTR-X2-OUT1 PPI-X-C3
* 2 CTR-Y1-OUT1 PPI-Y-C0
* 3 CTR-Y2-OUT1 PPI-Y-C3
* 4 CTR-Z1-OUT1 PPI-Z-C0
* 5 CTR-Z2-OUT1 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
......@@ -232,14 +174,11 @@
*
* 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).
* generate an interrupt. 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
......@@ -258,159 +197,15 @@
* order they appear in the channel list.
*/
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include "../comedidev.h"
#include "comedi_fc.h"
#include "8253.h"
#define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_DIO200_ISA)
#define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_DIO200_PCI)
/* PCI IDs */
#define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a
#define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b
#define PCI_DEVICE_ID_AMPLICON_PCIE236 0x0011
#define PCI_DEVICE_ID_AMPLICON_PCIE215 0x0012
#define PCI_DEVICE_ID_AMPLICON_PCIE296 0x0014
/* 8255 control register bits */
#define CR_C_LO_IO 0x01
#define CR_B_IO 0x02
#define CR_B_MODE 0x04
#define CR_C_HI_IO 0x08
#define CR_A_IO 0x10
#define CR_A_MODE(a) ((a)<<5)
#define CR_CW 0x80
/* 200 series registers */
#define DIO200_IO_SIZE 0x20
#define DIO200_PCIE_IO_SIZE 0x4000
#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 */
/* Extra registers for new PCIe boards */
#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */
#define DIO200_VERSION 0x24 /* Hardware version register */
#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */
#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */
/*
* Functions 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, or 0 to 31 for "enhanced" boards.
*/
static unsigned char clk_gat_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return (which << 5) | (chan << 3) |
((source & 030) << 3) | (source & 007);
}
static unsigned char clk_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return clk_gat_sce(which, chan, source);
}
static unsigned char gat_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return clk_gat_sce(which, chan, source);
}
/*
* Periods of the internal clock sources in nanoseconds.
*/
static const unsigned int clock_period[32] = {
[1] = 100, /* 10 MHz */
[2] = 1000, /* 1 MHz */
[3] = 10000, /* 100 kHz */
[4] = 100000, /* 10 kHz */
[5] = 1000000, /* 1 kHz */
[11] = 50, /* 20 MHz (enhanced boards) */
/* clock sources 12 and later reserved for enhanced boards */
};
/*
* Timestamp timer configuration register (for new PCIe boards).
*/
#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */
#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */
#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */
/*
* Periods of the timestamp timer clock sources in nanoseconds.
*/
static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = {
1, /* 1 nanosecond (but with 20 ns granularity). */
1000, /* 1 microsecond. */
1000000, /* 1 millisecond. */
};
/*
* Register region.
*/
enum dio200_regtype { no_regtype = 0, io_regtype, mmio_regtype };
struct dio200_region {
union {
unsigned long iobase; /* I/O base address */
unsigned char __iomem *membase; /* mapped MMIO base address */
} u;
enum dio200_regtype regtype;
};
/*
* Subdevice types.
*/
enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254, sd_timer };
#define DIO200_MAX_SUBDEVS 8
#define DIO200_MAX_ISNS 6
struct dio200_layout {
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 */
bool has_int_sce:1; /* has interrupt enable/status reg */
bool has_clk_gat_sce:1; /* has clock/gate selection registers */
bool has_enhancements:1; /* has enhanced features */
};
#include "amplc_dio200.h"
/*
* Board descriptions.
*/
enum dio200_bustype { isa_bustype, pci_bustype };
enum dio200_pci_model {
pci215_model,
pci272_model,
pcie215_model,
pcie236_model,
pcie296_model
};
struct dio200_board {
const char *name;
struct dio200_layout layout;
enum dio200_bustype bustype;
unsigned char mainbar;
unsigned char mainshift;
unsigned int mainsize;
};
#if DO_ISA
static const struct dio200_board dio200_isa_boards[] = {
{
.name = "pc212e",
......@@ -472,207 +267,6 @@ static const struct dio200_board dio200_isa_boards[] = {
},
},
};
#endif
#if DO_PCI
static const struct dio200_board dio200_pci_boards[] = {
[pci215_model] {
.name = "pci215",
.bustype = pci_bustype,
.mainbar = 2,
.mainsize = DIO200_IO_SIZE,
.layout = {
.n_subdevs = 5,
.sdtype = {sd_8255, sd_8255, sd_8254, sd_8254, sd_intr},
.sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
},
},
[pci272_model] {
.name = "pci272",
.bustype = pci_bustype,
.mainbar = 2,
.mainsize = DIO200_IO_SIZE,
.layout = {
.n_subdevs = 4,
.sdtype = {sd_8255, sd_8255, sd_8255, sd_intr},
.sdinfo = {0x00, 0x08, 0x10, 0x3F},
.has_int_sce = true,
},
},
[pcie215_model] {
.name = "pcie215",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_none, sd_8255, sd_none,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x00, 0x08, 0x00,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
[pcie236_model] {
.name = "pcie236",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_none, sd_none, sd_none,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x00, 0x00, 0x00,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
[pcie296_model] {
.name = "pcie296",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_8255, sd_8255, sd_8255,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x04, 0x08, 0x0C,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
};
#endif
/* 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 {
struct dio200_region io; /* Register region */
int intr_sd;
};
struct dio200_subdev_8254 {
unsigned int ofs; /* Counter base offset */
unsigned int clk_sce_ofs; /* CLK_SCE base address */
unsigned int gat_sce_ofs; /* GAT_SCE base address */
int which; /* Bit 5 of CLK_SCE or GAT_SCE */
unsigned int clock_src[3]; /* Current clock sources */
unsigned int gate_src[3]; /* Current gate sources */
spinlock_t spinlock;
};
struct dio200_subdev_8255 {
unsigned int ofs; /* DIO base offset */
};
struct dio200_subdev_intr {
spinlock_t spinlock;
unsigned int ofs;
unsigned int valid_isns;
unsigned int enabled_isns;
unsigned int stopcount;
bool active:1;
bool continuous:1;
};
static inline const struct dio200_layout *
dio200_board_layout(const struct dio200_board *board)
{
return &board->layout;
}
static inline const struct dio200_layout *
dio200_dev_layout(struct comedi_device *dev)
{
return dio200_board_layout(comedi_board(dev));
}
static inline bool is_pci_board(const struct dio200_board *board)
{
return DO_PCI && board->bustype == pci_bustype;
}
static inline bool is_isa_board(const struct dio200_board *board)
{
return DO_ISA && board->bustype == isa_bustype;
}
/*
* Read 8-bit register.
*/
static unsigned char dio200_read8(struct comedi_device *dev,
unsigned int offset)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
return inb(devpriv->io.u.iobase + offset);
else
return readb(devpriv->io.u.membase + offset);
}
/*
* Write 8-bit register.
*/
static void dio200_write8(struct comedi_device *dev, unsigned int offset,
unsigned char val)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
outb(val, devpriv->io.u.iobase + offset);
else
writeb(val, devpriv->io.u.membase + offset);
}
/*
* Read 32-bit register.
*/
static unsigned int dio200_read32(struct comedi_device *dev,
unsigned int offset)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
return inl(devpriv->io.u.iobase + offset);
else
return readl(devpriv->io.u.membase + offset);
}
/*
* Write 32-bit register.
*/
static void dio200_write32(struct comedi_device *dev, unsigned int offset,
unsigned int val)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
outl(val, devpriv->io.u.iobase + offset);
else
writel(val, devpriv->io.u.membase + offset);
}
/*
* This function checks and requests an I/O region, reporting an error
......@@ -690,1296 +284,56 @@ dio200_request_region(struct comedi_device *dev,
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)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv = s->private;
if (layout->has_int_sce) {
/* Just read the interrupt status register. */
data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns;
} else {
/* No interrupt status register. */
data[0] = 0;
}
return insn->n;
}
/*
* Called to stop acquisition for an 'INTERRUPT' subdevice.
*/
static void dio200_stop_intr(struct comedi_device *dev,
struct comedi_subdevice *s)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv = s->private;
subpriv->active = false;
subpriv->enabled_isns = 0;
if (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, 0);
}
/*
* 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;
const struct dio200_layout *layout = dio200_dev_layout(dev);
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 = false;
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 (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, isn_bits);
}
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;
}
static void dio200_read_scan_intr(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int triggered)
{
struct dio200_subdev_intr *subpriv = s->private;
unsigned 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);
}
}
}
}
/*
* 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)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
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 (layout->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 = (dio200_read8(dev, subpriv->ofs) &
subpriv->valid_isns & ~triggered)) != 0) {
triggered |= intstat;
cur_enabled &= ~triggered;
dio200_write8(dev, subpriv->ofs, cur_enabled);
}
} 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 (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, cur_enabled);
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. */
dio200_read_scan_intr(dev, s, triggered);
}
}
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)
static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
struct dio200_subdev_intr *subpriv = s->private;
unsigned long flags;
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv;
unsigned long iobase;
unsigned int irq;
int ret;
spin_lock_irqsave(&subpriv->spinlock, flags);
if (subpriv->active)
dio200_stop_intr(dev, s);
dev->board_name = thisboard->name;
iobase = it->options[0];
irq = it->options[1];
dev_info(dev->class_dev, "%s: attach %s 0x%lX,%u\n",
dev->driver->driver_name, dev->board_name, iobase, irq);
spin_unlock_irqrestore(&subpriv->spinlock, flags);
devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
if (!devpriv)
return -ENOMEM;
dev->private = devpriv;
return 0;
ret = dio200_request_region(dev, iobase, thisboard->mainsize);
if (ret < 0)
return ret;
devpriv->io.u.iobase = iobase;
devpriv->io.regtype = io_regtype;
return amplc_dio200_common_attach(dev, irq, 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)
static void dio200_detach(struct comedi_device *dev)
{
int err = 0;
/* Step 1 : check if triggers are trivially valid */
err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
if (err)
return 1;
/* Step 2a : make sure trigger sources are unique */
err |= cfc_check_trigger_is_unique(cmd->start_src);
err |= cfc_check_trigger_is_unique(cmd->stop_src);
/* Step 2b : and mutually compatible */
if (err)
return 2;
/* Step 3: check if arguments are trivially valid */
err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
switch (cmd->stop_src) {
case TRIG_COUNT:
/* any count allowed */
break;
case TRIG_NONE:
err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
break;
default:
break;
}
if (err)
return 3;
/* step 4: fix up any arguments */
/* if (err) return 4; */
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
return 0;
if (!thisboard || !devpriv)
return;
amplc_dio200_common_detach(dev);
if (devpriv->io.regtype == io_regtype)
release_region(devpriv->io.u.iobase, thisboard->mainsize);
}
/*
* '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 = false;
subpriv->stopcount = cmd->stop_arg;
break;
default:
/* TRIG_NONE */
subpriv->continuous = true;
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 int offset, unsigned valid_isns)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
return -ENOMEM;
subpriv->ofs = offset;
subpriv->valid_isns = valid_isns;
spin_lock_init(&subpriv->spinlock);
if (layout->has_int_sce)
/* Disable interrupt sources. */
dio200_write8(dev, subpriv->ofs, 0);
s->private = subpriv;
s->type = COMEDI_SUBD_DI;
s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
if (layout->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;
struct dio200_private *devpriv = dev->private;
struct comedi_subdevice *s;
int handled;
if (!dev->attached)
return IRQ_NONE;
if (devpriv->intr_sd >= 0) {
s = &dev->subdevices[devpriv->intr_sd];
handled = dio200_handle_read_intr(dev, s);
} else {
handled = 0;
}
return IRQ_RETVAL(handled);
}
/*
* Read an '8254' counter subdevice channel.
*/
static unsigned int
dio200_subdev_8254_read_chan(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan)
{
struct dio200_subdev_8254 *subpriv = s->private;
unsigned int val;
/* latch counter */
val = chan << 6;
dio200_write8(dev, subpriv->ofs + i8254_control_reg, val);
/* read lsb, msb */
val = dio200_read8(dev, subpriv->ofs + chan);
val += dio200_read8(dev, subpriv->ofs + chan) << 8;
return val;
}
/*
* Write an '8254' subdevice channel.
*/
static void
dio200_subdev_8254_write_chan(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan,
unsigned int count)
{
struct dio200_subdev_8254 *subpriv = s->private;
/* write lsb, msb */
dio200_write8(dev, subpriv->ofs + chan, count & 0xff);
dio200_write8(dev, subpriv->ofs + chan, (count >> 8) & 0xff);
}
/*
* Set mode of an '8254' subdevice channel.
*/
static void
dio200_subdev_8254_set_mode(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan,
unsigned int mode)
{
struct dio200_subdev_8254 *subpriv = s->private;
unsigned int byte;
byte = chan << 6;
byte |= 0x30; /* access order: lsb, msb */
byte |= (mode & 0xf); /* counter mode and BCD|binary */
dio200_write8(dev, subpriv->ofs + i8254_control_reg, byte);
}
/*
* Read status byte of an '8254' counter subdevice channel.
*/
static unsigned int
dio200_subdev_8254_status(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan)
{
struct dio200_subdev_8254 *subpriv = s->private;
/* latch status */
dio200_write8(dev, subpriv->ofs + i8254_control_reg,
0xe0 | (2 << chan));
/* read status */
return dio200_read8(dev, subpriv->ofs + chan);
}
/*
* 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 int n;
unsigned long flags;
for (n = 0; n < insn->n; n++) {
spin_lock_irqsave(&subpriv->spinlock, flags);
data[n] = dio200_subdev_8254_read_chan(dev, s, chan);
spin_unlock_irqrestore(&subpriv->spinlock, flags);
}
return insn->n;
}
/*
* 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 int n;
unsigned long flags;
for (n = 0; n < insn->n; n++) {
spin_lock_irqsave(&subpriv->spinlock, flags);
dio200_subdev_8254_write_chan(dev, s, chan, data[n]);
spin_unlock_irqrestore(&subpriv->spinlock, flags);
}
return insn->n;
}
/*
* Set gate source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_set_gate_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int gate_src)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned char byte;
if (!layout->has_clk_gat_sce)
return -1;
if (counter_number > 2)
return -1;
if (gate_src > (layout->has_enhancements ? 31 : 7))
return -1;
subpriv->gate_src[counter_number] = gate_src;
byte = gat_sce(subpriv->which, counter_number, gate_src);
dio200_write8(dev, subpriv->gat_sce_ofs, byte);
return 0;
}
/*
* Get gate source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_get_gate_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
if (!layout->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_subdev_8254_set_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int clock_src)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned char byte;
if (!layout->has_clk_gat_sce)
return -1;
if (counter_number > 2)
return -1;
if (clock_src > (layout->has_enhancements ? 31 : 7))
return -1;
subpriv->clock_src[counter_number] = clock_src;
byte = clk_sce(subpriv->which, counter_number, clock_src);
dio200_write8(dev, subpriv->clk_sce_ofs, byte);
return 0;
}
/*
* Get clock source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_get_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int *period_ns)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned clock_src;
if (!layout->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:
if (data[1] > (I8254_MODE5 | I8254_BINARY))
ret = -EINVAL;
else
dio200_subdev_8254_set_mode(dev, s, chan, data[1]);
break;
case INSN_CONFIG_8254_READ_STATUS:
data[1] = dio200_subdev_8254_status(dev, s, chan);
break;
case INSN_CONFIG_SET_GATE_SRC:
ret = dio200_subdev_8254_set_gate_src(dev, s, chan, data[2]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_GATE_SRC:
ret = dio200_subdev_8254_get_gate_src(dev, s, chan);
if (ret < 0) {
ret = -EINVAL;
break;
}
data[2] = ret;
break;
case INSN_CONFIG_SET_CLOCK_SRC:
ret = dio200_subdev_8254_set_clock_src(dev, s, chan, data[1]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_CLOCK_SRC:
ret = dio200_subdev_8254_get_clock_src(dev, s, 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.
*/
static int
dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s,
unsigned int offset)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv;
unsigned int chan;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
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->ofs = offset;
if (layout->has_clk_gat_sce) {
/* Derive CLK_SCE and GAT_SCE register offsets from
* 8254 offset. */
subpriv->clk_sce_ofs = DIO200_XCLK_SCE + (offset >> 3);
subpriv->gat_sce_ofs = DIO200_XGAT_SCE + (offset >> 3);
subpriv->which = (offset >> 2) & 1;
}
/* Initialize channels. */
for (chan = 0; chan < 3; chan++) {
dio200_subdev_8254_set_mode(dev, s, chan,
I8254_MODE0 | I8254_BINARY);
if (layout->has_clk_gat_sce) {
/* Gate source 0 is VCC (logic 1). */
dio200_subdev_8254_set_gate_src(dev, s, chan, 0);
/* Clock source 0 is the dedicated clock input. */
dio200_subdev_8254_set_clock_src(dev, s, 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);
}
/*
* This function sets I/O directions for an '8255' DIO subdevice.
*/
static void dio200_subdev_8255_set_dir(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct dio200_subdev_8255 *subpriv = s->private;
int config;
config = CR_CW;
/* 1 in io_bits indicates output, 1 in config indicates input */
if (!(s->io_bits & 0x0000ff))
config |= CR_A_IO;
if (!(s->io_bits & 0x00ff00))
config |= CR_B_IO;
if (!(s->io_bits & 0x0f0000))
config |= CR_C_LO_IO;
if (!(s->io_bits & 0xf00000))
config |= CR_C_HI_IO;
dio200_write8(dev, subpriv->ofs + 3, config);
}
/*
* Handle 'insn_bits' for an '8255' DIO subdevice.
*/
static int dio200_subdev_8255_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
struct dio200_subdev_8255 *subpriv = s->private;
if (data[0]) {
s->state &= ~data[0];
s->state |= (data[0] & data[1]);
if (data[0] & 0xff)
dio200_write8(dev, subpriv->ofs, s->state & 0xff);
if (data[0] & 0xff00)
dio200_write8(dev, subpriv->ofs + 1,
(s->state >> 8) & 0xff);
if (data[0] & 0xff0000)
dio200_write8(dev, subpriv->ofs + 2,
(s->state >> 16) & 0xff);
}
data[1] = dio200_read8(dev, subpriv->ofs);
data[1] |= dio200_read8(dev, subpriv->ofs + 1) << 8;
data[1] |= dio200_read8(dev, subpriv->ofs + 2) << 16;
return 2;
}
/*
* Handle 'insn_config' for an '8255' DIO subdevice.
*/
static int dio200_subdev_8255_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned int mask;
unsigned int bits;
mask = 1 << CR_CHAN(insn->chanspec);
if (mask & 0x0000ff)
bits = 0x0000ff;
else if (mask & 0x00ff00)
bits = 0x00ff00;
else if (mask & 0x0f0000)
bits = 0x0f0000;
else
bits = 0xf00000;
switch (data[0]) {
case INSN_CONFIG_DIO_INPUT:
s->io_bits &= ~bits;
break;
case INSN_CONFIG_DIO_OUTPUT:
s->io_bits |= bits;
break;
case INSN_CONFIG_DIO_QUERY:
data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
return insn->n;
break;
default:
return -EINVAL;
}
dio200_subdev_8255_set_dir(dev, s);
return 1;
}
/*
* This function initializes an '8255' DIO subdevice.
*
* offset is the offset to the 8255 chip.
*/
static int dio200_subdev_8255_init(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int offset)
{
struct dio200_subdev_8255 *subpriv;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
return -ENOMEM;
subpriv->ofs = offset;
s->private = subpriv;
s->type = COMEDI_SUBD_DIO;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan = 24;
s->range_table = &range_digital;
s->maxdata = 1;
s->insn_bits = dio200_subdev_8255_bits;
s->insn_config = dio200_subdev_8255_config;
s->state = 0;
s->io_bits = 0;
dio200_subdev_8255_set_dir(dev, s);
return 0;
}
/*
* This function cleans up an '8255' DIO subdevice.
*/
static void dio200_subdev_8255_cleanup(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct dio200_subdev_8255 *subpriv = s->private;
kfree(subpriv);
}
/*
* Handle 'insn_read' for a timer subdevice.
*/
static int dio200_subdev_timer_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned int n;
for (n = 0; n < insn->n; n++)
data[n] = dio200_read32(dev, DIO200_TS_COUNT);
return n;
}
/*
* Reset timer subdevice.
*/
static void dio200_subdev_timer_reset(struct comedi_device *dev,
struct comedi_subdevice *s)
{
unsigned int clock;
clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET);
dio200_write32(dev, DIO200_TS_CONFIG, clock);
}
/*
* Get timer subdevice clock source and period.
*/
static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int *src,
unsigned int *period)
{
unsigned int clk;
clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
*src = clk;
*period = (clk < ARRAY_SIZE(ts_clock_period)) ?
ts_clock_period[clk] : 0;
}
/*
* Set timer subdevice clock source.
*/
static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int src)
{
if (src > TS_CONFIG_MAX_CLK_SRC)
return -EINVAL;
dio200_write32(dev, DIO200_TS_CONFIG, src);
return 0;
}
/*
* Handle 'insn_config' for a timer subdevice.
*/
static int dio200_subdev_timer_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
int ret = 0;
switch (data[0]) {
case INSN_CONFIG_RESET:
dio200_subdev_timer_reset(dev, s);
break;
case INSN_CONFIG_SET_CLOCK_SRC:
ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_CLOCK_SRC:
dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]);
break;
default:
ret = -EINVAL;
break;
}
return ret < 0 ? ret : insn->n;
}
/*
* This function initializes a timer subdevice.
*
* Uses the timestamp timer registers. There is only one timestamp timer.
*/
static int dio200_subdev_timer_init(struct comedi_device *dev,
struct comedi_subdevice *s)
{
s->type = COMEDI_SUBD_TIMER;
s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
s->n_chan = 1;
s->maxdata = 0xFFFFFFFF;
s->insn_read = dio200_subdev_timer_read;
s->insn_config = dio200_subdev_timer_config;
return 0;
}
/*
* This function cleans up a timer subdevice.
*/
static void dio200_subdev_timer_cleanup(struct comedi_device *dev,
struct comedi_subdevice *s)
{
/* Nothing to do. */
}
/*
* This function does some special set-up for the PCIe boards
* PCIe215, PCIe236, PCIe296.
*/
static int dio200_pcie_board_setup(struct comedi_device *dev)
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
void __iomem *brbase;
resource_size_t brlen;
/*
* The board uses Altera Cyclone IV with PCI-Express hard IP.
* The FPGA configuration has the PCI-Express Avalon-MM Bridge
* Control registers in PCI BAR 0, offset 0, and the length of
* these registers is 0x4000.
*
* We need to write 0x80 to the "Avalon-MM to PCI-Express Interrupt
* Enable" register at offset 0x50 to allow generation of PCIe
* interrupts when RXmlrq_i is asserted in the SOPC Builder system.
*/
brlen = pci_resource_len(pcidev, 0);
if (brlen < 0x4000 ||
!(pci_resource_flags(pcidev, 0) & IORESOURCE_MEM)) {
dev_err(dev->class_dev, "error! bad PCI region!\n");
return -EINVAL;
}
brbase = ioremap_nocache(pci_resource_start(pcidev, 0), brlen);
if (!brbase) {
dev_err(dev->class_dev, "error! failed to map registers!\n");
return -ENOMEM;
}
writel(0x80, brbase + 0x50);
iounmap(brbase);
/* Enable "enhanced" features of board. */
dio200_write8(dev, DIO200_ENHANCE, 1);
return 0;
}
static int dio200_common_attach(struct comedi_device *dev, unsigned int irq,
unsigned long req_irq_flags)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
const struct dio200_layout *layout = dio200_board_layout(thisboard);
struct comedi_subdevice *s;
int sdx;
unsigned int n;
int ret;
devpriv->intr_sd = -1;
ret = comedi_alloc_subdevices(dev, layout->n_subdevs);
if (ret)
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,
layout->sdinfo[n]);
if (ret < 0)
return ret;
break;
case sd_8255:
/* digital i/o subdevice (8255) */
ret = dio200_subdev_8255_init(dev, s,
layout->sdinfo[n]);
if (ret < 0)
return ret;
break;
case sd_intr:
/* 'INTERRUPT' subdevice */
if (irq) {
ret = dio200_subdev_intr_init(dev, s,
DIO200_INT_SCE,
layout->sdinfo[n]
);
if (ret < 0)
return ret;
devpriv->intr_sd = n;
} else {
s->type = COMEDI_SUBD_UNUSED;
}
break;
case sd_timer:
ret = dio200_subdev_timer_init(dev, s);
if (ret < 0)
return ret;
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];
if (irq) {
if (request_irq(irq, dio200_interrupt, req_irq_flags,
dev->board_name, dev) >= 0) {
dev->irq = irq;
} else {
dev_warn(dev->class_dev,
"warning! irq %u unavailable!\n", irq);
}
}
dev_info(dev->class_dev, "attached\n");
return 0;
}
/* Only called for ISA boards. */
static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv;
unsigned long iobase;
unsigned int irq;
int ret;
if (!DO_ISA)
return -EINVAL;
dev->board_name = thisboard->name;
iobase = it->options[0];
irq = it->options[1];
dev_info(dev->class_dev, "%s: attach %s 0x%lX,%u\n",
dev->driver->driver_name, dev->board_name, iobase, irq);
devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
if (!devpriv)
return -ENOMEM;
dev->private = devpriv;
ret = dio200_request_region(dev, iobase, thisboard->mainsize);
if (ret < 0)
return ret;
devpriv->io.u.iobase = iobase;
devpriv->io.regtype = io_regtype;
return dio200_common_attach(dev, irq, 0);
}
/*
* The auto_attach hook is called at PCI probe time via
* comedi_pci_auto_config(). dev->board_ptr is NULL on entry.
* The context should be an index into dio200_pci_boards[].
*/
static int dio200_auto_attach(struct comedi_device *dev,
unsigned long context_model)
{
struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
const struct dio200_board *thisboard = NULL;
struct dio200_private *devpriv;
resource_size_t base, len;
unsigned int bar;
int ret;
if (!DO_PCI)
return -EINVAL;
if (context_model < ARRAY_SIZE(dio200_pci_boards))
thisboard = &dio200_pci_boards[context_model];
if (!thisboard)
return -EINVAL;
dev->board_ptr = thisboard;
dev->board_name = thisboard->name;
dev_info(dev->class_dev, "%s: attach pci %s (%s)\n",
dev->driver->driver_name, pci_name(pci_dev), dev->board_name);
devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
if (!devpriv)
return -ENOMEM;
dev->private = devpriv;
ret = comedi_pci_enable(dev);
if (ret)
return ret;
bar = thisboard->mainbar;
base = pci_resource_start(pci_dev, bar);
len = pci_resource_len(pci_dev, bar);
if (len < thisboard->mainsize) {
dev_err(dev->class_dev, "error! PCI region size too small!\n");
return -EINVAL;
}
if ((pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) != 0) {
devpriv->io.u.membase = ioremap_nocache(base, len);
if (!devpriv->io.u.membase) {
dev_err(dev->class_dev,
"error! cannot remap registers\n");
return -ENOMEM;
}
devpriv->io.regtype = mmio_regtype;
} else {
devpriv->io.u.iobase = (unsigned long)base;
devpriv->io.regtype = io_regtype;
}
switch (context_model) {
case pcie215_model:
case pcie236_model:
case pcie296_model:
ret = dio200_pcie_board_setup(dev);
if (ret < 0)
return ret;
break;
default:
break;
}
return dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED);
}
static void dio200_detach(struct comedi_device *dev)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
const struct dio200_layout *layout;
unsigned n;
if (!thisboard || !devpriv)
return;
if (dev->irq)
free_irq(dev->irq, dev);
if (dev->subdevices) {
layout = dio200_board_layout(thisboard);
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:
dio200_subdev_8255_cleanup(dev, s);
break;
case sd_intr:
dio200_subdev_intr_cleanup(dev, s);
break;
case sd_timer:
dio200_subdev_timer_cleanup(dev, s);
break;
default:
break;
}
}
}
if (is_isa_board(thisboard)) {
if (devpriv->io.regtype == io_regtype)
release_region(devpriv->io.u.iobase,
thisboard->mainsize);
} else if (is_pci_board(thisboard)) {
if (devpriv->io.regtype == mmio_regtype)
iounmap(devpriv->io.u.membase);
comedi_pci_disable(dev);
}
}
/*
* 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 struct comedi_driver amplc_dio200_driver = {
.driver_name = "amplc_dio200",
.module = THIS_MODULE,
.attach = dio200_attach,
.auto_attach = dio200_auto_attach,
.detach = dio200_detach,
#if DO_ISA
.board_name = &dio200_isa_boards[0].name,
.offset = sizeof(struct dio200_board),
.num_names = ARRAY_SIZE(dio200_isa_boards),
#endif
};
#if DO_PCI
static DEFINE_PCI_DEVICE_TABLE(dio200_pci_table) = {
{
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215),
pci215_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272),
pci272_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE236),
pcie236_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE215),
pcie215_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE296),
pcie296_model
},
{0}
};
MODULE_DEVICE_TABLE(pci, dio200_pci_table);
static int amplc_dio200_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
return comedi_pci_auto_config(dev, &amplc_dio200_driver,
id->driver_data);
}
static struct pci_driver amplc_dio200_pci_driver = {
.name = "amplc_dio200",
.id_table = dio200_pci_table,
.probe = &amplc_dio200_pci_probe,
.remove = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(amplc_dio200_driver, amplc_dio200_pci_driver);
#else
module_comedi_driver(amplc_dio200_driver);
#endif
MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series ISA DIO boards");
MODULE_LICENSE("GPL");
/*
comedi/drivers/amplc_dio.h
Header for amplc_dio200.c, amplc_dio200_common.c and
amplc_dio200_pci.c.
Copyright (C) 2005-2013 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.
*/
#ifndef AMPLC_DIO200_H_INCLUDED
#define AMPLC_DIO200_H_INCLUDED
/* 200 series register area sizes */
#define DIO200_IO_SIZE 0x20
#define DIO200_PCIE_IO_SIZE 0x4000
/*
* Register region.
*/
enum dio200_regtype { no_regtype = 0, io_regtype, mmio_regtype };
struct dio200_region {
union {
unsigned long iobase; /* I/O base address */
unsigned char __iomem *membase; /* mapped MMIO base address */
} u;
enum dio200_regtype regtype;
};
/*
* Subdevice types.
*/
enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254, sd_timer };
#define DIO200_MAX_SUBDEVS 8
#define DIO200_MAX_ISNS 6
/*
* Board descriptions.
*/
struct dio200_layout {
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 */
bool has_int_sce:1; /* has interrupt enable/status reg */
bool has_clk_gat_sce:1; /* has clock/gate selection registers */
bool has_enhancements:1; /* has enhanced features */
};
enum dio200_bustype { isa_bustype, pci_bustype };
struct dio200_board {
const char *name;
struct dio200_layout layout;
enum dio200_bustype bustype;
unsigned char mainbar;
unsigned char mainshift;
unsigned int mainsize;
};
/*
* Comedi device private data.
*/
struct dio200_private {
struct dio200_region io; /* Register region */
int intr_sd;
};
int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
unsigned long req_irq_flags);
void amplc_dio200_common_detach(struct comedi_device *dev);
/* Used by initialization of PCIe boards. */
void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val);
#endif
/*
comedi/drivers/amplc_dio200_common.c
Common support code for "amplc_dio200" and "amplc_dio200_pci".
Copyright (C) 2005-2013 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.
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include "../comedidev.h"
#include "amplc_dio200.h"
#include "comedi_fc.h"
#include "8253.h"
/* 8255 control register bits */
#define CR_C_LO_IO 0x01
#define CR_B_IO 0x02
#define CR_B_MODE 0x04
#define CR_C_HI_IO 0x08
#define CR_A_IO 0x10
#define CR_A_MODE(a) ((a)<<5)
#define CR_CW 0x80
/* 200 series registers */
#define DIO200_IO_SIZE 0x20
#define DIO200_PCIE_IO_SIZE 0x4000
#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 */
/* Extra registers for new PCIe boards */
#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */
#define DIO200_VERSION 0x24 /* Hardware version register */
#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */
#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */
/*
* Functions 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, or 0 to 31 for "enhanced" boards.
*/
static unsigned char clk_gat_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return (which << 5) | (chan << 3) |
((source & 030) << 3) | (source & 007);
}
static unsigned char clk_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return clk_gat_sce(which, chan, source);
}
static unsigned char gat_sce(unsigned int which, unsigned int chan,
unsigned int source)
{
return clk_gat_sce(which, chan, source);
}
/*
* Periods of the internal clock sources in nanoseconds.
*/
static const unsigned int clock_period[32] = {
[1] = 100, /* 10 MHz */
[2] = 1000, /* 1 MHz */
[3] = 10000, /* 100 kHz */
[4] = 100000, /* 10 kHz */
[5] = 1000000, /* 1 kHz */
[11] = 50, /* 20 MHz (enhanced boards) */
/* clock sources 12 and later reserved for enhanced boards */
};
/*
* Timestamp timer configuration register (for new PCIe boards).
*/
#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */
#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */
#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */
/*
* Periods of the timestamp timer clock sources in nanoseconds.
*/
static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = {
1, /* 1 nanosecond (but with 20 ns granularity). */
1000, /* 1 microsecond. */
1000000, /* 1 millisecond. */
};
struct dio200_subdev_8254 {
unsigned int ofs; /* Counter base offset */
unsigned int clk_sce_ofs; /* CLK_SCE base address */
unsigned int gat_sce_ofs; /* GAT_SCE base address */
int which; /* Bit 5 of CLK_SCE or GAT_SCE */
unsigned int clock_src[3]; /* Current clock sources */
unsigned int gate_src[3]; /* Current gate sources */
spinlock_t spinlock;
};
struct dio200_subdev_8255 {
unsigned int ofs; /* DIO base offset */
};
struct dio200_subdev_intr {
spinlock_t spinlock;
unsigned int ofs;
unsigned int valid_isns;
unsigned int enabled_isns;
unsigned int stopcount;
bool active:1;
bool continuous:1;
};
static inline const struct dio200_layout *
dio200_board_layout(const struct dio200_board *board)
{
return &board->layout;
}
static inline const struct dio200_layout *
dio200_dev_layout(struct comedi_device *dev)
{
return dio200_board_layout(comedi_board(dev));
}
/*
* Read 8-bit register.
*/
static unsigned char dio200_read8(struct comedi_device *dev,
unsigned int offset)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
return inb(devpriv->io.u.iobase + offset);
else
return readb(devpriv->io.u.membase + offset);
}
/*
* Write 8-bit register.
*/
static void dio200_write8(struct comedi_device *dev, unsigned int offset,
unsigned char val)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
outb(val, devpriv->io.u.iobase + offset);
else
writeb(val, devpriv->io.u.membase + offset);
}
/*
* Read 32-bit register.
*/
static unsigned int dio200_read32(struct comedi_device *dev,
unsigned int offset)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
return inl(devpriv->io.u.iobase + offset);
else
return readl(devpriv->io.u.membase + offset);
}
/*
* Write 32-bit register.
*/
static void dio200_write32(struct comedi_device *dev, unsigned int offset,
unsigned int val)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
offset <<= thisboard->mainshift;
if (devpriv->io.regtype == io_regtype)
outl(val, devpriv->io.u.iobase + offset);
else
writel(val, devpriv->io.u.membase + offset);
}
/*
* '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)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv = s->private;
if (layout->has_int_sce) {
/* Just read the interrupt status register. */
data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns;
} else {
/* No interrupt status register. */
data[0] = 0;
}
return insn->n;
}
/*
* Called to stop acquisition for an 'INTERRUPT' subdevice.
*/
static void dio200_stop_intr(struct comedi_device *dev,
struct comedi_subdevice *s)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv = s->private;
subpriv->active = false;
subpriv->enabled_isns = 0;
if (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, 0);
}
/*
* 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;
const struct dio200_layout *layout = dio200_dev_layout(dev);
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 = false;
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 (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, isn_bits);
}
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;
}
static void dio200_read_scan_intr(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int triggered)
{
struct dio200_subdev_intr *subpriv = s->private;
unsigned 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);
}
}
}
}
/*
* 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)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
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 (layout->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 = (dio200_read8(dev, subpriv->ofs) &
subpriv->valid_isns & ~triggered)) != 0) {
triggered |= intstat;
cur_enabled &= ~triggered;
dio200_write8(dev, subpriv->ofs, cur_enabled);
}
} 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 (layout->has_int_sce)
dio200_write8(dev, subpriv->ofs, cur_enabled);
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. */
dio200_read_scan_intr(dev, s, triggered);
}
}
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;
/* Step 1 : check if triggers are trivially valid */
err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
if (err)
return 1;
/* Step 2a : make sure trigger sources are unique */
err |= cfc_check_trigger_is_unique(cmd->start_src);
err |= cfc_check_trigger_is_unique(cmd->stop_src);
/* Step 2b : and mutually compatible */
if (err)
return 2;
/* Step 3: check if arguments are trivially valid */
err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
switch (cmd->stop_src) {
case TRIG_COUNT:
/* any count allowed */
break;
case TRIG_NONE:
err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
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 = false;
subpriv->stopcount = cmd->stop_arg;
break;
default:
/* TRIG_NONE */
subpriv->continuous = true;
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 int offset, unsigned valid_isns)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_intr *subpriv;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
return -ENOMEM;
subpriv->ofs = offset;
subpriv->valid_isns = valid_isns;
spin_lock_init(&subpriv->spinlock);
if (layout->has_int_sce)
/* Disable interrupt sources. */
dio200_write8(dev, subpriv->ofs, 0);
s->private = subpriv;
s->type = COMEDI_SUBD_DI;
s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
if (layout->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;
struct dio200_private *devpriv = dev->private;
struct comedi_subdevice *s;
int handled;
if (!dev->attached)
return IRQ_NONE;
if (devpriv->intr_sd >= 0) {
s = &dev->subdevices[devpriv->intr_sd];
handled = dio200_handle_read_intr(dev, s);
} else {
handled = 0;
}
return IRQ_RETVAL(handled);
}
/*
* Read an '8254' counter subdevice channel.
*/
static unsigned int
dio200_subdev_8254_read_chan(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan)
{
struct dio200_subdev_8254 *subpriv = s->private;
unsigned int val;
/* latch counter */
val = chan << 6;
dio200_write8(dev, subpriv->ofs + i8254_control_reg, val);
/* read lsb, msb */
val = dio200_read8(dev, subpriv->ofs + chan);
val += dio200_read8(dev, subpriv->ofs + chan) << 8;
return val;
}
/*
* Write an '8254' subdevice channel.
*/
static void
dio200_subdev_8254_write_chan(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan,
unsigned int count)
{
struct dio200_subdev_8254 *subpriv = s->private;
/* write lsb, msb */
dio200_write8(dev, subpriv->ofs + chan, count & 0xff);
dio200_write8(dev, subpriv->ofs + chan, (count >> 8) & 0xff);
}
/*
* Set mode of an '8254' subdevice channel.
*/
static void
dio200_subdev_8254_set_mode(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan,
unsigned int mode)
{
struct dio200_subdev_8254 *subpriv = s->private;
unsigned int byte;
byte = chan << 6;
byte |= 0x30; /* access order: lsb, msb */
byte |= (mode & 0xf); /* counter mode and BCD|binary */
dio200_write8(dev, subpriv->ofs + i8254_control_reg, byte);
}
/*
* Read status byte of an '8254' counter subdevice channel.
*/
static unsigned int
dio200_subdev_8254_status(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned int chan)
{
struct dio200_subdev_8254 *subpriv = s->private;
/* latch status */
dio200_write8(dev, subpriv->ofs + i8254_control_reg,
0xe0 | (2 << chan));
/* read status */
return dio200_read8(dev, subpriv->ofs + chan);
}
/*
* 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 int n;
unsigned long flags;
for (n = 0; n < insn->n; n++) {
spin_lock_irqsave(&subpriv->spinlock, flags);
data[n] = dio200_subdev_8254_read_chan(dev, s, chan);
spin_unlock_irqrestore(&subpriv->spinlock, flags);
}
return insn->n;
}
/*
* 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 int n;
unsigned long flags;
for (n = 0; n < insn->n; n++) {
spin_lock_irqsave(&subpriv->spinlock, flags);
dio200_subdev_8254_write_chan(dev, s, chan, data[n]);
spin_unlock_irqrestore(&subpriv->spinlock, flags);
}
return insn->n;
}
/*
* Set gate source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_set_gate_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int gate_src)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned char byte;
if (!layout->has_clk_gat_sce)
return -1;
if (counter_number > 2)
return -1;
if (gate_src > (layout->has_enhancements ? 31 : 7))
return -1;
subpriv->gate_src[counter_number] = gate_src;
byte = gat_sce(subpriv->which, counter_number, gate_src);
dio200_write8(dev, subpriv->gat_sce_ofs, byte);
return 0;
}
/*
* Get gate source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_get_gate_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
if (!layout->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_subdev_8254_set_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int clock_src)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned char byte;
if (!layout->has_clk_gat_sce)
return -1;
if (counter_number > 2)
return -1;
if (clock_src > (layout->has_enhancements ? 31 : 7))
return -1;
subpriv->clock_src[counter_number] = clock_src;
byte = clk_sce(subpriv->which, counter_number, clock_src);
dio200_write8(dev, subpriv->clk_sce_ofs, byte);
return 0;
}
/*
* Get clock source for an '8254' counter subdevice channel.
*/
static int
dio200_subdev_8254_get_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int counter_number,
unsigned int *period_ns)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv = s->private;
unsigned clock_src;
if (!layout->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:
if (data[1] > (I8254_MODE5 | I8254_BINARY))
ret = -EINVAL;
else
dio200_subdev_8254_set_mode(dev, s, chan, data[1]);
break;
case INSN_CONFIG_8254_READ_STATUS:
data[1] = dio200_subdev_8254_status(dev, s, chan);
break;
case INSN_CONFIG_SET_GATE_SRC:
ret = dio200_subdev_8254_set_gate_src(dev, s, chan, data[2]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_GATE_SRC:
ret = dio200_subdev_8254_get_gate_src(dev, s, chan);
if (ret < 0) {
ret = -EINVAL;
break;
}
data[2] = ret;
break;
case INSN_CONFIG_SET_CLOCK_SRC:
ret = dio200_subdev_8254_set_clock_src(dev, s, chan, data[1]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_CLOCK_SRC:
ret = dio200_subdev_8254_get_clock_src(dev, s, 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.
*/
static int
dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s,
unsigned int offset)
{
const struct dio200_layout *layout = dio200_dev_layout(dev);
struct dio200_subdev_8254 *subpriv;
unsigned int chan;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
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->ofs = offset;
if (layout->has_clk_gat_sce) {
/* Derive CLK_SCE and GAT_SCE register offsets from
* 8254 offset. */
subpriv->clk_sce_ofs = DIO200_XCLK_SCE + (offset >> 3);
subpriv->gat_sce_ofs = DIO200_XGAT_SCE + (offset >> 3);
subpriv->which = (offset >> 2) & 1;
}
/* Initialize channels. */
for (chan = 0; chan < 3; chan++) {
dio200_subdev_8254_set_mode(dev, s, chan,
I8254_MODE0 | I8254_BINARY);
if (layout->has_clk_gat_sce) {
/* Gate source 0 is VCC (logic 1). */
dio200_subdev_8254_set_gate_src(dev, s, chan, 0);
/* Clock source 0 is the dedicated clock input. */
dio200_subdev_8254_set_clock_src(dev, s, 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);
}
/*
* This function sets I/O directions for an '8255' DIO subdevice.
*/
static void dio200_subdev_8255_set_dir(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct dio200_subdev_8255 *subpriv = s->private;
int config;
config = CR_CW;
/* 1 in io_bits indicates output, 1 in config indicates input */
if (!(s->io_bits & 0x0000ff))
config |= CR_A_IO;
if (!(s->io_bits & 0x00ff00))
config |= CR_B_IO;
if (!(s->io_bits & 0x0f0000))
config |= CR_C_LO_IO;
if (!(s->io_bits & 0xf00000))
config |= CR_C_HI_IO;
dio200_write8(dev, subpriv->ofs + 3, config);
}
/*
* Handle 'insn_bits' for an '8255' DIO subdevice.
*/
static int dio200_subdev_8255_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn, unsigned int *data)
{
struct dio200_subdev_8255 *subpriv = s->private;
if (data[0]) {
s->state &= ~data[0];
s->state |= (data[0] & data[1]);
if (data[0] & 0xff)
dio200_write8(dev, subpriv->ofs, s->state & 0xff);
if (data[0] & 0xff00)
dio200_write8(dev, subpriv->ofs + 1,
(s->state >> 8) & 0xff);
if (data[0] & 0xff0000)
dio200_write8(dev, subpriv->ofs + 2,
(s->state >> 16) & 0xff);
}
data[1] = dio200_read8(dev, subpriv->ofs);
data[1] |= dio200_read8(dev, subpriv->ofs + 1) << 8;
data[1] |= dio200_read8(dev, subpriv->ofs + 2) << 16;
return 2;
}
/*
* Handle 'insn_config' for an '8255' DIO subdevice.
*/
static int dio200_subdev_8255_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned int mask;
unsigned int bits;
mask = 1 << CR_CHAN(insn->chanspec);
if (mask & 0x0000ff)
bits = 0x0000ff;
else if (mask & 0x00ff00)
bits = 0x00ff00;
else if (mask & 0x0f0000)
bits = 0x0f0000;
else
bits = 0xf00000;
switch (data[0]) {
case INSN_CONFIG_DIO_INPUT:
s->io_bits &= ~bits;
break;
case INSN_CONFIG_DIO_OUTPUT:
s->io_bits |= bits;
break;
case INSN_CONFIG_DIO_QUERY:
data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
return insn->n;
break;
default:
return -EINVAL;
}
dio200_subdev_8255_set_dir(dev, s);
return 1;
}
/*
* This function initializes an '8255' DIO subdevice.
*
* offset is the offset to the 8255 chip.
*/
static int dio200_subdev_8255_init(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int offset)
{
struct dio200_subdev_8255 *subpriv;
subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
if (!subpriv)
return -ENOMEM;
subpriv->ofs = offset;
s->private = subpriv;
s->type = COMEDI_SUBD_DIO;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan = 24;
s->range_table = &range_digital;
s->maxdata = 1;
s->insn_bits = dio200_subdev_8255_bits;
s->insn_config = dio200_subdev_8255_config;
s->state = 0;
s->io_bits = 0;
dio200_subdev_8255_set_dir(dev, s);
return 0;
}
/*
* This function cleans up an '8255' DIO subdevice.
*/
static void dio200_subdev_8255_cleanup(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct dio200_subdev_8255 *subpriv = s->private;
kfree(subpriv);
}
/*
* Handle 'insn_read' for a timer subdevice.
*/
static int dio200_subdev_timer_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned int n;
for (n = 0; n < insn->n; n++)
data[n] = dio200_read32(dev, DIO200_TS_COUNT);
return n;
}
/*
* Reset timer subdevice.
*/
static void dio200_subdev_timer_reset(struct comedi_device *dev,
struct comedi_subdevice *s)
{
unsigned int clock;
clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET);
dio200_write32(dev, DIO200_TS_CONFIG, clock);
}
/*
* Get timer subdevice clock source and period.
*/
static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int *src,
unsigned int *period)
{
unsigned int clk;
clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
*src = clk;
*period = (clk < ARRAY_SIZE(ts_clock_period)) ?
ts_clock_period[clk] : 0;
}
/*
* Set timer subdevice clock source.
*/
static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int src)
{
if (src > TS_CONFIG_MAX_CLK_SRC)
return -EINVAL;
dio200_write32(dev, DIO200_TS_CONFIG, src);
return 0;
}
/*
* Handle 'insn_config' for a timer subdevice.
*/
static int dio200_subdev_timer_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
int ret = 0;
switch (data[0]) {
case INSN_CONFIG_RESET:
dio200_subdev_timer_reset(dev, s);
break;
case INSN_CONFIG_SET_CLOCK_SRC:
ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]);
if (ret < 0)
ret = -EINVAL;
break;
case INSN_CONFIG_GET_CLOCK_SRC:
dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]);
break;
default:
ret = -EINVAL;
break;
}
return ret < 0 ? ret : insn->n;
}
/*
* This function initializes a timer subdevice.
*
* Uses the timestamp timer registers. There is only one timestamp timer.
*/
static int dio200_subdev_timer_init(struct comedi_device *dev,
struct comedi_subdevice *s)
{
s->type = COMEDI_SUBD_TIMER;
s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
s->n_chan = 1;
s->maxdata = 0xFFFFFFFF;
s->insn_read = dio200_subdev_timer_read;
s->insn_config = dio200_subdev_timer_config;
return 0;
}
/*
* This function cleans up a timer subdevice.
*/
static void dio200_subdev_timer_cleanup(struct comedi_device *dev,
struct comedi_subdevice *s)
{
/* Nothing to do. */
}
void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val)
{
dio200_write8(dev, DIO200_ENHANCE, val);
}
EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance);
int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
unsigned long req_irq_flags)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
const struct dio200_layout *layout = dio200_board_layout(thisboard);
struct comedi_subdevice *s;
int sdx;
unsigned int n;
int ret;
devpriv->intr_sd = -1;
ret = comedi_alloc_subdevices(dev, layout->n_subdevs);
if (ret)
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,
layout->sdinfo[n]);
if (ret < 0)
return ret;
break;
case sd_8255:
/* digital i/o subdevice (8255) */
ret = dio200_subdev_8255_init(dev, s,
layout->sdinfo[n]);
if (ret < 0)
return ret;
break;
case sd_intr:
/* 'INTERRUPT' subdevice */
if (irq) {
ret = dio200_subdev_intr_init(dev, s,
DIO200_INT_SCE,
layout->sdinfo[n]
);
if (ret < 0)
return ret;
devpriv->intr_sd = n;
} else {
s->type = COMEDI_SUBD_UNUSED;
}
break;
case sd_timer:
ret = dio200_subdev_timer_init(dev, s);
if (ret < 0)
return ret;
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];
if (irq) {
if (request_irq(irq, dio200_interrupt, req_irq_flags,
dev->board_name, dev) >= 0) {
dev->irq = irq;
} else {
dev_warn(dev->class_dev,
"warning! irq %u unavailable!\n", irq);
}
}
dev_info(dev->class_dev, "attached\n");
return 0;
}
EXPORT_SYMBOL_GPL(amplc_dio200_common_attach);
void amplc_dio200_common_detach(struct comedi_device *dev)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
const struct dio200_layout *layout;
unsigned n;
if (!thisboard || !devpriv)
return;
if (dev->irq)
free_irq(dev->irq, dev);
if (dev->subdevices) {
layout = dio200_board_layout(thisboard);
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:
dio200_subdev_8255_cleanup(dev, s);
break;
case sd_intr:
dio200_subdev_intr_cleanup(dev, s);
break;
case sd_timer:
dio200_subdev_timer_cleanup(dev, s);
break;
default:
break;
}
}
}
}
EXPORT_SYMBOL_GPL(amplc_dio200_common_detach);
static int __init amplc_dio200_common_init(void)
{
return 0;
}
module_init(amplc_dio200_common_init);
static void __exit amplc_dio200_common_exit(void)
{
}
module_exit(amplc_dio200_common_exit);
MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci");
MODULE_LICENSE("GPL");
/* comedi/drivers/amplc_dio200_pci.c
Driver for Amplicon PCI215, PCI272, PCIe215, PCIe236, PCIe296.
Copyright (C) 2005-2013 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_pci
* Description: Amplicon 200 Series PCI Digital I/O
* Author: Ian Abbott <abbotti@mev.co.uk>
* Devices: [Amplicon] PCI215 (amplc_dio200_pci), PCIe215, PCIe236,
* PCI272, PCIe296
* Updated: Mon, 18 Mar 2013 15:03:50 +0000
* Status: works
*
* Configuration options:
* none
*
* Manual configuration of PCI(e) cards is not supported; they are configured
* automatically.
*
* SUBDEVICES
*
* PCI215 PCIe215 PCIe236
* ------------- ------------- -------------
* Subdevices 5 8 8
* 0 PPI-X PPI-X PPI-X
* 1 PPI-Y UNUSED UNUSED
* 2 CTR-Z1 PPI-Y UNUSED
* 3 CTR-Z2 UNUSED UNUSED
* 4 INTERRUPT CTR-Z1 CTR-Z1
* 5 CTR-Z2 CTR-Z2
* 6 TIMER TIMER
* 7 INTERRUPT INTERRUPT
*
*
* PCI272 PCIe296
* ------------- -------------
* Subdevices 4 8
* 0 PPI-X PPI-X1
* 1 PPI-Y PPI-X2
* 2 PPI-Z PPI-Y1
* 3 INTERRUPT PPI-Y2
* 4 CTR-Z1
* 5 CTR-Z2
* 6 TIMER
* 7 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.
*
* For the PCIe boards, clock sources in the range 0 to 31 are allowed
* and the following additional clock sources are defined:
*
* 8. HIGH logic level.
* 9. LOW logic level.
* 10. "Pattern present" signal.
* 11. Internal 20 MHz clock.
*
* 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.
*
* For the PCIe boards, gate sources in the range 0 to 31 are allowed;
* the following additional clock sources and clock sources 6 and 7 are
* (re)defined:
*
* 6. /GAT n, negated version of the counter channel's dedicated
* GAT input (negated version of gate source 2).
* 7. OUT n-2, the non-inverted output of counter channel n-2
* (negated version of gate source 3).
* 8. "Pattern present" signal, HIGH while pattern present.
* 9. "Pattern occurred" latched signal, latches HIGH when pattern
* occurs.
* 10. "Pattern gone away" latched signal, latches LOW when pattern
* goes away after it occurred.
* 11. Negated "pattern present" signal, LOW while pattern present
* (negated version of gate source 8).
* 12. Negated "pattern occurred" latched signal, latches LOW when
* pattern occurs (negated version of gate source 9).
* 13. Negated "pattern gone away" latched signal, latches LOW when
* pattern goes away after it occurred (negated version of gate
* source 10).
*
* 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 'TIMER' subdevice is a free-running 32-bit timer subdevice.
*
* 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
*
* PCI215 PCIe215 PCIe236
* ------------- ------------- -------------
* Sources 6 6 6
* 0 PPI-X-C0 PPI-X-C0 PPI-X-C0
* 1 PPI-X-C3 PPI-X-C3 PPI-X-C3
* 2 PPI-Y-C0 PPI-Y-C0 unused
* 3 PPI-Y-C3 PPI-Y-C3 unused
* 4 CTR-Z1-OUT1 CTR-Z1-OUT1 CTR-Z1-OUT1
* 5 CTR-Z2-OUT1 CTR-Z2-OUT1 CTR-Z2-OUT1
*
* PCI272 PCIe296
* ------------- -------------
* Sources 6 6
* 0 PPI-X-C0 PPI-X1-C0
* 1 PPI-X-C3 PPI-X1-C3
* 2 PPI-Y-C0 PPI-Y1-C0
* 3 PPI-Y-C3 PPI-Y1-C3
* 4 PPI-Z-C0 CTR-Z1-OUT1
* 5 PPI-Z-C3 CTR-Z2-OUT1
*
* 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. The interrupt will remain asserted 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).
*
* 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/pci.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include "../comedidev.h"
#include "amplc_dio200.h"
/* PCI IDs */
#define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a
#define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b
#define PCI_DEVICE_ID_AMPLICON_PCIE236 0x0011
#define PCI_DEVICE_ID_AMPLICON_PCIE215 0x0012
#define PCI_DEVICE_ID_AMPLICON_PCIE296 0x0014
/*
* Board descriptions.
*/
enum dio200_pci_model {
pci215_model,
pci272_model,
pcie215_model,
pcie236_model,
pcie296_model
};
static const struct dio200_board dio200_pci_boards[] = {
[pci215_model] {
.name = "pci215",
.bustype = pci_bustype,
.mainbar = 2,
.mainsize = DIO200_IO_SIZE,
.layout = {
.n_subdevs = 5,
.sdtype = {sd_8255, sd_8255, sd_8254, sd_8254, sd_intr},
.sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
},
},
[pci272_model] {
.name = "pci272",
.bustype = pci_bustype,
.mainbar = 2,
.mainsize = DIO200_IO_SIZE,
.layout = {
.n_subdevs = 4,
.sdtype = {sd_8255, sd_8255, sd_8255, sd_intr},
.sdinfo = {0x00, 0x08, 0x10, 0x3F},
.has_int_sce = true,
},
},
[pcie215_model] {
.name = "pcie215",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_none, sd_8255, sd_none,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x00, 0x08, 0x00,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
[pcie236_model] {
.name = "pcie236",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_none, sd_none, sd_none,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x00, 0x00, 0x00,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
[pcie296_model] {
.name = "pcie296",
.bustype = pci_bustype,
.mainbar = 1,
.mainshift = 3,
.mainsize = DIO200_PCIE_IO_SIZE,
.layout = {
.n_subdevs = 8,
.sdtype = {sd_8255, sd_8255, sd_8255, sd_8255,
sd_8254, sd_8254, sd_timer, sd_intr},
.sdinfo = {0x00, 0x04, 0x08, 0x0C,
0x10, 0x14, 0x00, 0x3F},
.has_int_sce = true,
.has_clk_gat_sce = true,
.has_enhancements = true,
},
},
};
/*
* This function does some special set-up for the PCIe boards
* PCIe215, PCIe236, PCIe296.
*/
static int dio200_pcie_board_setup(struct comedi_device *dev)
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
void __iomem *brbase;
resource_size_t brlen;
/*
* The board uses Altera Cyclone IV with PCI-Express hard IP.
* The FPGA configuration has the PCI-Express Avalon-MM Bridge
* Control registers in PCI BAR 0, offset 0, and the length of
* these registers is 0x4000.
*
* We need to write 0x80 to the "Avalon-MM to PCI-Express Interrupt
* Enable" register at offset 0x50 to allow generation of PCIe
* interrupts when RXmlrq_i is asserted in the SOPC Builder system.
*/
brlen = pci_resource_len(pcidev, 0);
if (brlen < 0x4000 ||
!(pci_resource_flags(pcidev, 0) & IORESOURCE_MEM)) {
dev_err(dev->class_dev, "error! bad PCI region!\n");
return -EINVAL;
}
brbase = ioremap_nocache(pci_resource_start(pcidev, 0), brlen);
if (!brbase) {
dev_err(dev->class_dev, "error! failed to map registers!\n");
return -ENOMEM;
}
writel(0x80, brbase + 0x50);
iounmap(brbase);
/* Enable "enhanced" features of board. */
amplc_dio200_set_enhance(dev, 1);
return 0;
}
static int dio200_pci_auto_attach(struct comedi_device *dev,
unsigned long context_model)
{
struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
const struct dio200_board *thisboard = NULL;
struct dio200_private *devpriv;
resource_size_t base, len;
unsigned int bar;
int ret;
if (context_model < ARRAY_SIZE(dio200_pci_boards))
thisboard = &dio200_pci_boards[context_model];
if (!thisboard)
return -EINVAL;
dev->board_ptr = thisboard;
dev->board_name = thisboard->name;
dev_info(dev->class_dev, "%s: attach pci %s (%s)\n",
dev->driver->driver_name, pci_name(pci_dev), dev->board_name);
devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
if (!devpriv)
return -ENOMEM;
dev->private = devpriv;
ret = comedi_pci_enable(dev);
if (ret)
return ret;
bar = thisboard->mainbar;
base = pci_resource_start(pci_dev, bar);
len = pci_resource_len(pci_dev, bar);
if (len < thisboard->mainsize) {
dev_err(dev->class_dev, "error! PCI region size too small!\n");
return -EINVAL;
}
if (pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) {
devpriv->io.u.membase = ioremap_nocache(base, len);
if (!devpriv->io.u.membase) {
dev_err(dev->class_dev,
"error! cannot remap registers\n");
return -ENOMEM;
}
devpriv->io.regtype = mmio_regtype;
} else {
devpriv->io.u.iobase = (unsigned long)base;
devpriv->io.regtype = io_regtype;
}
switch (context_model) {
case pcie215_model:
case pcie236_model:
case pcie296_model:
ret = dio200_pcie_board_setup(dev);
if (ret < 0)
return ret;
break;
default:
break;
}
return amplc_dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED);
}
static void dio200_pci_detach(struct comedi_device *dev)
{
const struct dio200_board *thisboard = comedi_board(dev);
struct dio200_private *devpriv = dev->private;
if (!thisboard || !devpriv)
return;
amplc_dio200_common_detach(dev);
if (devpriv->io.regtype == mmio_regtype)
iounmap(devpriv->io.u.membase);
comedi_pci_disable(dev);
}
static struct comedi_driver dio200_pci_comedi_driver = {
.driver_name = "amplc_dio200_pci",
.module = THIS_MODULE,
.auto_attach = dio200_pci_auto_attach,
.detach = dio200_pci_detach,
};
static DEFINE_PCI_DEVICE_TABLE(dio200_pci_table) = {
{
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215),
pci215_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272),
pci272_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE236),
pcie236_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE215),
pcie215_model
}, {
PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE296),
pcie296_model
},
{0}
};
MODULE_DEVICE_TABLE(pci, dio200_pci_table);
static int dio200_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
return comedi_pci_auto_config(dev, &dio200_pci_comedi_driver,
id->driver_data);
}
static struct pci_driver dio200_pci_pci_driver = {
.name = "amplc_dio200_pci",
.id_table = dio200_pci_table,
.probe = dio200_pci_probe,
.remove = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(dio200_pci_comedi_driver, dio200_pci_pci_driver);
MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series PCI(e) DIO boards");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment