From: Ian Abbott Date: Mon, 18 Mar 2013 17:19:09 +0000 (+0000) Subject: staging: comedi: amplc_dio200: split into ISA, PCI and common X-Git-Tag: v3.10-rc1~192^2~488 X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=commitdiff_plain;h=7ff7e4c2c460f55a96d09b33ce421f91a9b6a0fb;p=linux.git 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: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile index 6cdef4514fbf..4857d46d684a 100644 --- a/drivers/staging/comedi/drivers/Makefile +++ b/drivers/staging/comedi/drivers/Makefile @@ -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 diff --git a/drivers/staging/comedi/drivers/amplc_dio200.c b/drivers/staging/comedi/drivers/amplc_dio200.c index e00dda1170cf..1a562e8e5842 100644 --- a/drivers/staging/comedi/drivers/amplc_dio200.c +++ b/drivers/staging/comedi/drivers/amplc_dio200.c @@ -1,10 +1,9 @@ /* 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. + Driver for Amplicon PC212E, PC214E, PC215E, PC218E, PC272E. + + Copyright (C) 2005-2013 MEV Ltd. COMEDI - Linux Control and Measurement Device Interface Copyright (C) 1998,2000 David A. Schleef @@ -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 * 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 -#include #include #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, &lc_dio200_driver, - id->driver_data); -} - -static struct pci_driver amplc_dio200_pci_driver = { - .name = "amplc_dio200", - .id_table = dio200_pci_table, - .probe = &lc_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"); diff --git a/drivers/staging/comedi/drivers/amplc_dio200.h b/drivers/staging/comedi/drivers/amplc_dio200.h new file mode 100644 index 000000000000..cf2e7261740e --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200.h @@ -0,0 +1,95 @@ +/* + 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. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#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 diff --git a/drivers/staging/comedi/drivers/amplc_dio200_common.c b/drivers/staging/comedi/drivers/amplc_dio200_common.c new file mode 100644 index 000000000000..04a798e9d015 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200_common.c @@ -0,0 +1,1320 @@ +/* + comedi/drivers/amplc_dio200_common.c + + Common support code for "amplc_dio200" and "amplc_dio200_pci". + + Copyright (C) 2005-2013 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include + +#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"); diff --git a/drivers/staging/comedi/drivers/amplc_dio200_pci.c b/drivers/staging/comedi/drivers/amplc_dio200_pci.c new file mode 100644 index 000000000000..55b6efd94ae2 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200_pci.c @@ -0,0 +1,492 @@ +/* comedi/drivers/amplc_dio200_pci.c + + Driver for Amplicon PCI215, PCI272, PCIe215, PCIe236, PCIe296. + + Copyright (C) 2005-2013 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* + * Driver: amplc_dio200_pci + * Description: Amplicon 200 Series PCI Digital I/O + * Author: Ian Abbott + * 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 +#include +#include + +#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");