]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
staging: gpib: Add TNT4882 chip based GPIB driver
authorDave Penkler <dpenkler@gmail.com>
Wed, 18 Sep 2024 12:19:08 +0000 (14:19 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 10 Oct 2024 13:28:50 +0000 (15:28 +0200)
Driver for National Instruments TNT4882 based boards

Signed-off-by: Dave Penkler <dpenkler@gmail.com>
Link: https://lore.kernel.org/r/20240918121908.19366-22-dpenkler@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/gpib/tnt4882/Makefile [new file with mode: 0644]
drivers/staging/gpib/tnt4882/mite.c [new file with mode: 0644]
drivers/staging/gpib/tnt4882/mite.h [new file with mode: 0644]
drivers/staging/gpib/tnt4882/tnt4882_gpib.c [new file with mode: 0644]

diff --git a/drivers/staging/gpib/tnt4882/Makefile b/drivers/staging/gpib/tnt4882/Makefile
new file mode 100644 (file)
index 0000000..f767c99
--- /dev/null
@@ -0,0 +1,7 @@
+ccflags-$(CONFIG_GPIB_PCMCIA) := -DGPIB_PCMCIA
+obj-m += tnt4882.o
+
+tnt4882-objs := tnt4882_gpib.o mite.o
+
+
+
diff --git a/drivers/staging/gpib/tnt4882/mite.c b/drivers/staging/gpib/tnt4882/mite.c
new file mode 100644 (file)
index 0000000..adb656a
--- /dev/null
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2
+
+/*
+ *     Hardware driver for NI Mite PCI interface chip,
+ *     adapted from COMEDI
+ *
+ *     Copyright (C) 1997-8 David A. Schleef
+ *     Copyright (C) 2002 Frank Mori Hess
+ *
+ *     The PCI-MIO E series driver was originally written by
+ *     Tomasz Motylewski <...>, and ported to comedi by ds.
+ *
+ *     References for specifications:
+ *
+ *        321747b.pdf  Register Level Programmer Manual (obsolete)
+ *        321747c.pdf  Register Level Programmer Manual (new)
+ *        DAQ-STC reference manual
+ *
+ *     Other possibly relevant info:
+ *
+ *        320517c.pdf  User manual (obsolete)
+ *        320517f.pdf  User manual (new)
+ *        320889a.pdf  delete
+ *        320906c.pdf  maximum signal ratings
+ *        321066a.pdf  about 16x
+ *        321791a.pdf  discontinuation of at-mio-16e-10 rev. c
+ *        321808a.pdf  about at-mio-16e-10 rev P
+ *        321837a.pdf  discontinuation of at-mio-16de-10 rev d
+ *        321838a.pdf  about at-mio-16de-10 rev N
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include "mite.h"
+
+#define PCI_MITE_SIZE          4096
+#define PCI_DAQ_SIZE           4096
+
+struct mite_struct *mite_devices;
+
+#define TOP_OF_PAGE(x) ((x) | (~(PAGE_MASK)))
+
+void mite_init(void)
+{
+       struct pci_dev *pcidev;
+       struct mite_struct *mite;
+
+       for (pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, NULL);
+               pcidev;
+               pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, pcidev)) {
+               mite = kmalloc(sizeof(*mite), GFP_KERNEL);
+               if (!mite)
+                       return;
+
+               memset(mite, 0, sizeof(*mite));
+
+               mite->pcidev = pcidev;
+               pci_dev_get(mite->pcidev);
+               mite->next = mite_devices;
+               mite_devices = mite;
+       }
+}
+
+int mite_setup(struct mite_struct *mite)
+{
+       u32 addr;
+
+       if (pci_enable_device(mite->pcidev)) {
+               pr_err("mite: error enabling mite.\n");
+               return -EIO;
+       }
+       pci_set_master(mite->pcidev);
+       if (pci_request_regions(mite->pcidev, "mite")) {
+               pr_err("mite: failed to request mite io regions.\n");
+               return -EIO;
+       };
+       addr = pci_resource_start(mite->pcidev, 0);
+       mite->mite_phys_addr = addr;
+       mite->mite_io_addr = ioremap(addr, pci_resource_len(mite->pcidev, 0));
+       if (!mite->mite_io_addr) {
+               pr_err("mite: failed to remap mite io memory address.\n");
+               return -ENOMEM;
+       }
+       pr_info("mite: 0x%08lx mapped to %p\n", mite->mite_phys_addr, mite->mite_io_addr);
+       addr = pci_resource_start(mite->pcidev, 1);
+       mite->daq_phys_addr = addr;
+       mite->daq_io_addr = ioremap(mite->daq_phys_addr, pci_resource_len(mite->pcidev, 1));
+       if (!mite->daq_io_addr) {
+               pr_err("mite: failed to remap daq io memory address.\n");
+               return -ENOMEM;
+       }
+       pr_info("mite: daq: 0x%08lx mapped to %p\n", mite->daq_phys_addr, mite->daq_io_addr);
+       writel(mite->daq_phys_addr | WENAB, mite->mite_io_addr + MITE_IODWBSR);
+       mite->used = 1;
+       return 0;
+}
+
+void mite_cleanup(void)
+{
+       struct mite_struct *mite, *next;
+
+       for (mite = mite_devices; mite; mite = next) {
+               next = mite->next;
+               if (mite->pcidev)
+                       pci_dev_put(mite->pcidev);
+               kfree(mite);
+       }
+}
+
+void mite_unsetup(struct mite_struct *mite)
+{
+       if (!mite)
+               return;
+       if (mite->mite_io_addr) {
+               iounmap(mite->mite_io_addr);
+               mite->mite_io_addr = NULL;
+       }
+       if (mite->daq_io_addr) {
+               iounmap(mite->daq_io_addr);
+               mite->daq_io_addr = NULL;
+       }
+       if (mite->mite_phys_addr) {
+               pci_release_regions(mite->pcidev);
+               pci_disable_device(mite->pcidev);
+               mite->mite_phys_addr = 0;
+       }
+       mite->used = 0;
+}
+
+void mite_list_devices(void)
+{
+       struct mite_struct *mite, *next;
+
+       pr_info("Available NI PCI device IDs:");
+       if (mite_devices)
+               for (mite = mite_devices; mite; mite = next) {
+                       next = mite->next;
+                       pr_info(" 0x%04x", mite_device_id(mite));
+                       if (mite->used)
+                               pr_info("(used)");
+       }
+       pr_info("\n");
+}
+
+int mite_bytes_transferred(struct mite_struct *mite, int chan)
+{
+       int dar, fcr;
+
+       dar = readl(mite->mite_io_addr + MITE_DAR + CHAN_OFFSET(chan));
+       fcr = readl(mite->mite_io_addr + MITE_FCR + CHAN_OFFSET(chan)) & 0x000000FF;
+       return dar - fcr;
+}
+
+int mite_dma_tcr(struct mite_struct *mite)
+{
+       int tcr;
+       int lkar;
+
+       lkar = readl(mite->mite_io_addr + CHAN_OFFSET(0) + MITE_LKAR);
+       tcr = readl(mite->mite_io_addr + CHAN_OFFSET(0) + MITE_TCR);
+       MDPRINTK("lkar=0x%08x tcr=%d\n", lkar, tcr);
+
+       return tcr;
+}
+
+void mite_dma_disarm(struct mite_struct *mite)
+{
+       int chor;
+
+       /* disarm */
+       chor = CHOR_ABORT;
+       writel(chor, mite->mite_io_addr + CHAN_OFFSET(0) + MITE_CHOR);
+}
+
+void mite_dump_regs(struct mite_struct *mite)
+{
+       void *addr = 0;
+       unsigned long temp = 0;
+
+       pr_info("mite address is  =0x%p\n", mite->mite_io_addr);
+
+       addr = mite->mite_io_addr + MITE_CHOR + CHAN_OFFSET(0);
+       pr_info("mite status[CHOR]at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_CHOR_strings,temp);
+       addr = mite->mite_io_addr + MITE_CHCR + CHAN_OFFSET(0);
+       pr_info("mite status[CHCR]at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_CHCR_strings,temp);
+       addr = mite->mite_io_addr + MITE_TCR + CHAN_OFFSET(0);
+       pr_info("mite status[TCR] at 0x%p =0x%08x\n", addr, readl(addr));
+       addr = mite->mite_io_addr + MITE_MCR + CHAN_OFFSET(0);
+       pr_info("mite status[MCR] at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_MCR_strings,temp);
+       addr = mite->mite_io_addr + MITE_MAR + CHAN_OFFSET(0);
+       pr_info("mite status[MAR] at 0x%p =0x%08x\n", addr, readl(addr));
+       addr = mite->mite_io_addr + MITE_DCR + CHAN_OFFSET(0);
+       pr_info("mite status[DCR] at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_CR_strings,temp);
+       addr = mite->mite_io_addr + MITE_DAR + CHAN_OFFSET(0);
+       pr_info("mite status[DAR] at 0x%p =0x%08x\n", addr, readl(addr));
+       addr = mite->mite_io_addr + MITE_LKCR + CHAN_OFFSET(0);
+       pr_info("mite status[LKCR]at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_CR_strings,temp);
+       addr = mite->mite_io_addr + MITE_LKAR + CHAN_OFFSET(0);
+       pr_info("mite status[LKAR]at 0x%p =0x%08x\n", addr, readl(addr));
+
+       addr = mite->mite_io_addr + MITE_CHSR + CHAN_OFFSET(0);
+       pr_info("mite status[CHSR]at 0x%p =0x%08lx\n", addr, temp = readl(addr));
+       //mite_decode(mite_CHSR_strings,temp);
+       addr = mite->mite_io_addr + MITE_FCR + CHAN_OFFSET(0);
+       pr_info("mite status[FCR] at 0x%p =0x%08x\n\n", addr, readl(addr));
+}
+
diff --git a/drivers/staging/gpib/tnt4882/mite.h b/drivers/staging/gpib/tnt4882/mite.h
new file mode 100644 (file)
index 0000000..6454d06
--- /dev/null
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: GPL-2 */
+
+/*
+ *   Hardware driver for NI Mite PCI interface chip
+ *
+ *   Copyright (C) 1999 David A. Schleef <ds@stm.lbl.gov>
+ */
+
+#ifndef _MITE_H_
+#define _MITE_H_
+
+#include <linux/pci.h>
+
+#define PCI_VENDOR_ID_NATINST          0x1093
+
+//#define DEBUG_MITE
+
+#ifdef DEBUG_MITE
+#define MDPRINTK(format, args...) pr_debug(format, ## args)
+#else
+#define MDPRINTK(args...)
+#endif
+
+#define MITE_RING_SIZE 3000
+struct mite_dma_chain {
+       u32 count;
+       u32 addr;
+       u32 next;
+};
+
+struct mite_struct {
+       struct mite_struct *next;
+       int used;
+
+       struct pci_dev *pcidev;
+       unsigned long mite_phys_addr;
+       void *mite_io_addr;
+       unsigned long daq_phys_addr;
+       void *daq_io_addr;
+
+       int DMA_CheckNearEnd;
+
+       struct mite_dma_chain ring[MITE_RING_SIZE];
+};
+
+extern struct mite_struct *mite_devices;
+
+extern inline unsigned int mite_irq(struct mite_struct *mite)
+{
+       return mite->pcidev->irq;
+};
+
+extern inline unsigned int mite_device_id(struct mite_struct *mite)
+{
+       return mite->pcidev->device;
+};
+
+void mite_init(void);
+void mite_cleanup(void);
+int mite_setup(struct mite_struct *mite);
+void mite_unsetup(struct mite_struct *mite);
+void mite_list_devices(void);
+
+int mite_dma_tcr(struct mite_struct *mite);
+
+void mite_dma_arm(struct mite_struct *mite);
+void mite_dma_disarm(struct mite_struct *mite);
+
+void mite_dump_regs(struct mite_struct *mite);
+void mite_setregs(struct mite_struct *mite, unsigned long ll_start, int chan, int dir);
+int mite_bytes_transferred(struct mite_struct *mite, int chan);
+
+#define CHAN_OFFSET(x)                 (0x100 * (x))
+
+/* DMA base for chan 0 is 0x500, chan 1 is 0x600 */
+
+#define MITE_CHOR              0x500
+#define CHOR_DMARESET                  BIT(31)
+#define CHOR_SET_SEND_TC               BIT(11)
+#define CHOR_CLR_SEND_TC               BIT(10)
+#define CHOR_SET_LPAUSE                        BIT(9)
+#define CHOR_CLR_LPAUSE                        BIT(8)
+#define CHOR_CLRDONE                   BIT(7)
+#define CHOR_CLRRB                     BIT(6)
+#define CHOR_CLRLC                     BIT(5)
+#define CHOR_FRESET                    BIT(4)
+#define CHOR_ABORT                     BIT(3)
+#define CHOR_STOP                      BIT(2)
+#define CHOR_CONT                      BIT(1)
+#define CHOR_START                     BIT(0)
+#define CHOR_PON                       (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE)
+
+#define MITE_CHCR              0x504
+#define CHCR_SET_DMA_IE                        BIT(31)
+#define CHCR_CLR_DMA_IE                        BIT(30)
+#define CHCR_SET_LINKP_IE              BIT(29)
+#define CHCR_CLR_LINKP_IE              BIT(28)
+#define CHCR_SET_SAR_IE                        BIT(27)
+#define CHCR_CLR_SAR_IE                        BIT(26)
+#define CHCR_SET_DONE_IE               BIT(25)
+#define CHCR_CLR_DONE_IE               BIT(24)
+#define CHCR_SET_MRDY_IE               BIT(23)
+#define CHCR_CLR_MRDY_IE               BIT(22)
+#define CHCR_SET_DRDY_IE               BIT(21)
+#define CHCR_CLR_DRDY_IE               BIT(20)
+#define CHCR_SET_LC_IE                 BIT(19)
+#define CHCR_CLR_LC_IE                 BIT(18)
+#define CHCR_SET_CONT_RB_IE            BIT(17)
+#define CHCR_CLR_CONT_RB_IE            BIT(16)
+#define CHCR_FIFODIS                   BIT(15)
+#define CHCR_FIFO_ON                   0
+#define CHCR_BURSTEN                   BIT(14)
+#define CHCR_NO_BURSTEN                        0
+#define CHCR_NFTP(x)                   ((x) << 11)
+#define CHCR_NFTP0                     CHCR_NFTP(0)
+#define CHCR_NFTP1                     CHCR_NFTP(1)
+#define CHCR_NFTP2                     CHCR_NFTP(2)
+#define CHCR_NFTP4                     CHCR_NFTP(3)
+#define CHCR_NFTP8                     CHCR_NFTP(4)
+#define CHCR_NFTP16                    CHCR_NFTP(5)
+#define CHCR_NETP(x)                   ((x) << 11)
+#define CHCR_NETP0                     CHCR_NETP(0)
+#define CHCR_NETP1                     CHCR_NETP(1)
+#define CHCR_NETP2                     CHCR_NETP(2)
+#define CHCR_NETP4                     CHCR_NETP(3)
+#define CHCR_NETP8                     CHCR_NETP(4)
+#define CHCR_CHEND1                    BIT(5)
+#define CHCR_CHEND0                    BIT(4)
+#define CHCR_DIR                       BIT(3)
+#define CHCR_DEV_TO_MEM                        CHCR_DIR
+#define CHCR_MEM_TO_DEV                        0
+#define CHCR_NORMAL                    ((0) << 0)
+#define CHCR_CONTINUE                  ((1) << 0)
+#define CHCR_RINGBUFF                  ((2) << 0)
+#define CHCR_LINKSHORT                 ((4) << 0)
+#define CHCR_LINKLONG                  ((5) << 0)
+#define CHCRPON                                (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | \
+                                        CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | \
+                                        CHCR_CLR_LC_IE | CHCR_CLR_CONT_IE)
+
+#define MITE_TCR               0x508
+
+/* CR bits */
+#define CR_RL(x)                       ((x) << 21)
+#define CR_RL0                         CR_RL(0)
+#define CR_RL1                         CR_RL(1)
+#define CR_RL2                         CR_RL(2)
+#define CR_RL4                         CR_RL(3)
+#define CR_RL8                         CR_RL(4)
+#define CR_RL16                                CR_RL(5)
+#define CR_RL32                                CR_RL(6)
+#define CR_RL64                                CR_RL(7)
+#define CR_RD(x)                       ((x) << 19)
+#define CR_RD0                         CR_RD(0)
+#define CR_RD32                                CR_RD(1)
+#define CR_RD512                       CR_RD(2)
+#define CR_RD8192                      CR_RD(3)
+#define CR_REQS(x)                     ((x) << 16)
+#define CR_REQSDRQ0                    CR_REQS(4)
+#define CR_REQSDRQ1                    CR_REQS(5)
+#define CR_REQSDRQ2                    CR_REQS(6)
+#define CR_REQSDRQ3                    CR_REQS(7)
+#define CR_ASEQX(x)                    ((x) << 10)
+#define CR_ASEQX0                      CR_ASEQX(0)
+#define        CR_ASEQDONT                     CR_ASEQX0
+#define CR_ASEQXP1                     CR_ASEQX(1)
+#define CR_ASEQUP                      CR_ASEQXP1
+#define CR_ASEQXP2                     CR_ASEQX(2)
+#define CR_ASEQDOWN                    CR_ASEQXP2
+#define CR_ASEQXP4                     CR_ASEQX(3)
+#define CR_ASEQXP8                     CR_ASEQX(4)
+#define CR_ASEQXP16                    CR_ASEQX(5)
+#define CR_ASEQXP32                    CR_ASEQX(6)
+#define CR_ASEQXP64                    CR_ASEQX(7)
+#define CR_ASEQXM1                     CR_ASEQX(9)
+#define CR_ASEQXM2                     CR_ASEQX(10)
+#define CR_ASEQXM4                     CR_ASEQX(11)
+#define CR_ASEQXM8                     CR_ASEQX(12)
+#define CR_ASEQXM16                    CR_ASEQX(13)
+#define CR_ASEQXM32                    CR_ASEQX(14)
+#define CR_ASEQXM64                    CR_ASEQX(15)
+#define CR_PSIZEBYTE                   BIT(8)
+#define CR_PSIZEHALF                   (2 << 8)
+#define CR_PSIZEWORD                   (3 << 8)
+#define CR_PORTCPU                     (0 << 6)
+#define CR_PORTIO                      BIT(6)
+#define CR_PORTVXI                     (2 << 6)
+#define CR_PORTMXI                     (3 << 6)
+#define CR_AMDEVICE                    BIT(0)
+
+#define CHSR_INT                       0x80000000
+#define CHSR_DONE                      0x02000000
+#define CHSR_LINKC                     0x00080000
+
+#define MITE_MCR               0x50c
+#define        MCRPON                          0
+
+#define MITE_MAR               0x510
+
+#define MITE_DCR               0x514
+#define DCR_NORMAL                     BIT(29)
+#define DCRPON                         0
+
+#define MITE_DAR               0x518
+
+#define MITE_LKCR              0x51c
+
+#define MITE_LKAR              0x520
+#define MITE_LLKAR             0x524
+#define MITE_BAR               0x528
+#define MITE_BCR               0x52c
+#define MITE_SAR               0x530
+#define MITE_WSCR              0x534
+#define MITE_WSER              0x538
+#define MITE_CHSR              0x53c
+#define MITE_FCR               0x540
+
+#define MITE_FIFO              0x80
+#define MITE_FIFOEND           0xff
+
+#define MITE_AMRAM                     0x00
+#define MITE_AMDEVICE                  0x01
+#define MITE_AMHOST_A32_SINGLE         0x09
+#define MITE_AMHOST_A24_SINGLE         0x39
+#define MITE_AMHOST_A16_SINGLE         0x29
+#define MITE_AMHOST_A32_BLOCK          0x0b
+#define MITE_AMHOST_A32D64_BLOCK       0x08
+#define MITE_AMHOST_A24_BLOCK          0x3b
+
+enum mite_registers {
+       MITE_IODWBSR = 0xc0, //IO Device Window Base Size Register
+       MITE_CSIGR = 0x460,     //chip signature
+       MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards)
+       MITE_IODWCR_1 = 0xf4
+};
+
+enum MITE_IODWBSR_bits {
+       WENAB = 0x80,   // window enable
+       WENAB_6602 = 0x8c // window enable for 6602 boards
+};
+
+#endif
+
diff --git a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c
new file mode 100644 (file)
index 0000000..ef4b9ce
--- /dev/null
@@ -0,0 +1,1874 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/***************************************************************************
+ * National Instruments boards using tnt4882 or compatible chips (at-gpib, etc).
+ *    copyright            : (C) 2001, 2002 by Frank Mori Hess
+ ***************************************************************************/
+
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/isapnp.h>
+
+#include "nec7210.h"
+#include "gpibP.h"
+#include "mite.h"
+#include "tnt4882_registers.h"
+
+static const int ISAPNP_VENDOR_ID_NI = ISAPNP_VENDOR('N', 'I', 'C');
+static const int ISAPNP_ID_NI_ATGPIB_TNT = 0xc601;
+enum {
+       PCI_DEVICE_ID_NI_GPIB = 0xc801,
+       PCI_DEVICE_ID_NI_GPIB_PLUS = 0xc811,
+       PCI_DEVICE_ID_NI_GPIB_PLUS2 = 0x71ad,
+       PCI_DEVICE_ID_NI_PXIGPIB = 0xc821,
+       PCI_DEVICE_ID_NI_PMCGPIB = 0xc831,
+       PCI_DEVICE_ID_NI_PCIEGPIB = 0x70cf,
+       PCI_DEVICE_ID_NI_PCIE2GPIB = 0x710e,
+// Measurement Computing PCI-488 same design as PCI-GPIB with TNT5004
+       PCI_DEVICE_ID_MC_PCI488 = 0x7259,
+       PCI_DEVICE_ID_CEC_NI_GPIB = 0x7258
+};
+
+// struct which defines private_data for tnt4882 devices
+struct tnt4882_priv {
+       struct nec7210_priv nec7210_priv;
+       struct mite_struct *mite;
+       struct pnp_dev *pnp_dev;
+       unsigned int irq;
+       unsigned short imr0_bits;
+       unsigned short imr3_bits;
+       unsigned short auxg_bits;       // bits written to auxiliary register G
+       void (*io_writeb)(unsigned int value, void *address);
+       void (*io_writew)(unsigned int value, void *address);
+       unsigned int (*io_readb)(void *address);
+       unsigned int (*io_readw)(void *address);
+};
+
+// interface functions
+static int tnt4882_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                       int *end, size_t *bytes_read);
+static int tnt4882_accel_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                             int *end, size_t *bytes_read);
+static int tnt4882_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                        int send_eoi, size_t *bytes_written);
+static int tnt4882_accel_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                              int send_eoi, size_t *bytes_written);
+static int tnt4882_command(gpib_board_t *board, uint8_t *buffer, size_t length,
+                          size_t *bytes_written);
+static int tnt4882_command_unaccel(gpib_board_t *board, uint8_t *buffer,
+                                  size_t length, size_t *bytes_written);
+static int tnt4882_take_control(gpib_board_t *board, int synchronous);
+static int tnt4882_go_to_standby(gpib_board_t *board);
+static void tnt4882_request_system_control(gpib_board_t *board, int request_control);
+static void tnt4882_interface_clear(gpib_board_t *board, int assert);
+static void tnt4882_remote_enable(gpib_board_t *board, int enable);
+static int tnt4882_enable_eos(gpib_board_t *board, uint8_t eos_byte, int
+                             compare_8_bits);
+static void tnt4882_disable_eos(gpib_board_t *board);
+static unsigned int tnt4882_update_status(gpib_board_t *board, unsigned int clear_mask);
+static int tnt4882_primary_address(gpib_board_t *board, unsigned int address);
+static int tnt4882_secondary_address(gpib_board_t *board, unsigned int address,
+                                    int enable);
+static int tnt4882_parallel_poll(gpib_board_t *board, uint8_t *result);
+static void tnt4882_parallel_poll_configure(gpib_board_t *board, uint8_t config);
+static void tnt4882_parallel_poll_response(gpib_board_t *board, int ist);
+static void tnt4882_serial_poll_response(gpib_board_t *board, uint8_t status);
+static uint8_t tnt4882_serial_poll_status(gpib_board_t *board);
+static int tnt4882_line_status(const gpib_board_t *board);
+static unsigned int tnt4882_t1_delay(gpib_board_t *board, unsigned int nano_sec);
+static void tnt4882_return_to_local(gpib_board_t *board);
+
+// interrupt service routines
+static irqreturn_t tnt4882_internal_interrupt(gpib_board_t *board);
+static irqreturn_t tnt4882_interrupt(int irq, void *arg);
+
+// utility functions
+static int tnt4882_allocate_private(gpib_board_t *board);
+static void tnt4882_free_private(gpib_board_t *board);
+static void tnt4882_init(struct tnt4882_priv *tnt_priv, const gpib_board_t *board);
+static void tnt4882_board_reset(struct tnt4882_priv *tnt_priv, gpib_board_t *board);
+
+// register offset for nec7210 compatible registers
+static const int atgpib_reg_offset = 2;
+
+// number of ioports used
+static const int atgpib_iosize = 32;
+static const int pcmcia_gpib_iosize = 32;
+
+/* paged io */
+static inline unsigned int tnt_paged_readb(struct tnt4882_priv *priv, unsigned long offset)
+{
+       priv->io_writeb(AUX_PAGEIN, priv->nec7210_priv.iobase + AUXMR * priv->nec7210_priv.offset);
+       udelay(1);
+       return priv->io_readb(priv->nec7210_priv.iobase + offset);
+}
+
+static inline void tnt_paged_writeb(struct tnt4882_priv *priv, unsigned int value,
+                                   unsigned long offset)
+{
+       priv->io_writeb(AUX_PAGEIN, priv->nec7210_priv.iobase + AUXMR * priv->nec7210_priv.offset);
+       udelay(1);
+       priv->io_writeb(value, priv->nec7210_priv.iobase + offset);
+}
+
+/* readb/writeb wrappers */
+static inline unsigned short tnt_readb(struct tnt4882_priv *priv, unsigned long offset)
+{
+       void *address = priv->nec7210_priv.iobase + offset;
+       unsigned long flags;
+       unsigned short retval;
+       spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock;
+
+       spin_lock_irqsave(register_lock, flags);
+       switch (offset) {
+       case CSR:
+       case SASR:
+       case ISR0:
+       case BSR:
+               switch (priv->nec7210_priv.type) {
+               case TNT4882:
+               case TNT5004:
+                       retval = priv->io_readb(address);
+                       break;
+               case NAT4882:
+                       retval = tnt_paged_readb(priv, offset - tnt_pagein_offset);
+                       break;
+               case NEC7210:
+                       retval = 0;
+                       break;
+               default:
+                       pr_err("tnt4882: bug! unsupported ni_chipset\n");
+                       retval = 0;
+                       break;
+               }
+               break;
+       default:
+               retval = priv->io_readb(address);
+               break;
+       }
+       spin_unlock_irqrestore(register_lock, flags);
+       return retval;
+}
+
+static inline void tnt_writeb(struct tnt4882_priv *priv, unsigned short value, unsigned long offset)
+{
+       void *address = priv->nec7210_priv.iobase + offset;
+       unsigned long flags;
+       spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock;
+
+       spin_lock_irqsave(register_lock, flags);
+       switch (offset) {
+       case KEYREG:
+       case IMR0:
+       case BCR:
+               switch (priv->nec7210_priv.type) {
+               case TNT4882:
+               case TNT5004:
+                       priv->io_writeb(value, address);
+                       break;
+               case NAT4882:
+                       tnt_paged_writeb(priv, value, offset - tnt_pagein_offset);
+                       break;
+               case NEC7210:
+                       break;
+               default:
+                       pr_err("tnt4882: bug! unsupported ni_chipset\n");
+                       break;
+               }
+               break;
+       default:
+               priv->io_writeb(value, address);
+               break;
+       }
+       spin_unlock_irqrestore(register_lock, flags);
+}
+
+MODULE_LICENSE("GPL");
+
+int tnt4882_line_status(const gpib_board_t *board)
+{
+       int status = ValidALL;
+       int bcsr_bits;
+       struct tnt4882_priv *tnt_priv;
+
+       tnt_priv = board->private_data;
+
+       bcsr_bits = tnt_readb(tnt_priv, BSR);
+
+       if (bcsr_bits & BCSR_REN_BIT)
+               status |= BusREN;
+       if (bcsr_bits & BCSR_IFC_BIT)
+               status |= BusIFC;
+       if (bcsr_bits & BCSR_SRQ_BIT)
+               status |= BusSRQ;
+       if (bcsr_bits & BCSR_EOI_BIT)
+               status |= BusEOI;
+       if (bcsr_bits & BCSR_NRFD_BIT)
+               status |= BusNRFD;
+       if (bcsr_bits & BCSR_NDAC_BIT)
+               status |= BusNDAC;
+       if (bcsr_bits & BCSR_DAV_BIT)
+               status |= BusDAV;
+       if (bcsr_bits & BCSR_ATN_BIT)
+               status |= BusATN;
+
+       return status;
+}
+
+unsigned int tnt4882_t1_delay(gpib_board_t *board, unsigned int nano_sec)
+{
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+       unsigned int retval;
+
+       retval = nec7210_t1_delay(board, nec_priv, nano_sec);
+       if (nec_priv->type == NEC7210)
+               return retval;
+
+       if (nano_sec <= 350) {
+               tnt_writeb(tnt_priv, MSTD, KEYREG);
+               retval = 350;
+       } else {
+               tnt_writeb(tnt_priv, 0, KEYREG);
+       }
+       if (nano_sec > 500 && nano_sec <= 1100) {
+               write_byte(nec_priv, AUXRI | USTD, AUXMR);
+               retval = 1100;
+       } else {
+               write_byte(nec_priv, AUXRI, AUXMR);
+       }
+       return retval;
+}
+
+static int fifo_word_available(struct tnt4882_priv *tnt_priv)
+{
+       int status2;
+       int retval;
+
+       status2 = tnt_readb(tnt_priv, STS2);
+       retval = (status2 & AEFN) && (status2 & BEFN);
+
+       return retval;
+}
+
+static int fifo_byte_available(struct tnt4882_priv *tnt_priv)
+{
+       int status2;
+       int retval;
+
+       status2 = tnt_readb(tnt_priv, STS2);
+       retval = (status2 & AEFN) || (status2 & BEFN);
+
+       return retval;
+}
+
+static int fifo_xfer_done(struct tnt4882_priv *tnt_priv)
+{
+       int status1;
+       int retval;
+
+       status1 = tnt_readb(tnt_priv, STS1);
+       retval = status1 & (S_DONE | S_HALT);
+
+       return retval;
+}
+
+static int drain_fifo_words(struct tnt4882_priv *tnt_priv, uint8_t *buffer, int num_bytes)
+{
+       int count = 0;
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+
+       while (fifo_word_available(tnt_priv) && count + 2 <= num_bytes) {
+               short word;
+
+               word = tnt_priv->io_readw(nec_priv->iobase + FIFOB);
+               buffer[count++] = word & 0xff;
+               buffer[count++] = (word >> 8) & 0xff;
+       }
+       return count;
+}
+
+static void tnt4882_release_holdoff(gpib_board_t *board, struct tnt4882_priv *tnt_priv)
+{
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+       unsigned short sasr_bits;
+
+       sasr_bits = tnt_readb(tnt_priv, SASR);
+
+       /*tnt4882 not in one-chip mode won't always release holdoff unless we
+        * are in the right mode when release handshake command is given
+        */
+       if (sasr_bits & AEHS_BIT) /* holding off due to holdoff on end mode*/   {
+               nec7210_set_handshake_mode(board, nec_priv, HR_HLDE);
+               write_byte(nec_priv, AUX_FH, AUXMR);
+       } else if (sasr_bits & ANHS1_BIT) { /* held off due to holdoff on all data mode*/
+               nec7210_set_handshake_mode(board, nec_priv, HR_HLDA);
+               write_byte(nec_priv, AUX_FH, AUXMR);
+               nec7210_set_handshake_mode(board, nec_priv, HR_HLDE);
+       } else { /* held off due to holdoff immediately command */
+               nec7210_set_handshake_mode(board, nec_priv, HR_HLDE);
+               write_byte(nec_priv, AUX_FH, AUXMR);
+       }
+}
+
+int tnt4882_accel_read(gpib_board_t *board, uint8_t *buffer, size_t length, int *end,
+                      size_t *bytes_read)
+{
+       size_t count = 0;
+       ssize_t retval = 0;
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+       unsigned int bits;
+       s32 hw_count;
+       unsigned long flags;
+
+       *bytes_read = 0;
+       // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state);
+       clear_bit(ADR_CHANGE_BN, &nec_priv->state);
+
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, HR_ENDIE);
+       if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004)
+               nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI);
+       else
+               nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0);
+       tnt_writeb(tnt_priv, nec_priv->auxa_bits | HR_HLDA, CCR);
+       bits = TNT_B_16BIT | TNT_IN | TNT_CCEN;
+       tnt_writeb(tnt_priv, bits, CFG);
+       tnt_writeb(tnt_priv, RESET_FIFO, CMDR);
+       udelay(1);
+       // load 2's complement of count into hardware counters
+       hw_count = -length;
+       tnt_writeb(tnt_priv, hw_count & 0xff, CNT0);
+       tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1);
+       tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2);
+       tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3);
+
+       tnt4882_release_holdoff(board, tnt_priv);
+
+       tnt_writeb(tnt_priv, GO, CMDR);
+       udelay(1);
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       tnt_priv->imr3_bits |= HR_DONE | HR_NEF;
+       tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+
+       while (count + 2 <= length &&
+              test_bit(RECEIVED_END_BN, &nec_priv->state) == 0 &&
+              fifo_xfer_done(tnt_priv) == 0) {
+               // wait until a word is ready
+               if (wait_event_interruptible(board->wait,
+                                            fifo_word_available(tnt_priv) ||
+                                            fifo_xfer_done(tnt_priv) ||
+                                            test_bit(RECEIVED_END_BN, &nec_priv->state) ||
+                                            test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                            test_bit(ADR_CHANGE_BN, &nec_priv->state) ||
+                                            test_bit(TIMO_NUM, &board->status))) {
+                       pr_err("tnt4882: read interrupted\n");
+                       retval = -ERESTARTSYS;
+                       break;
+               }
+               if (test_bit(TIMO_NUM, &board->status)) {
+                       //pr_info("tnt4882: minor %i read timed out\n", board->minor);
+                       retval = -ETIMEDOUT;
+                       break;
+               }
+               if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) {
+                       pr_err("tnt4882: device clear interrupted read\n");
+                       retval = -EINTR;
+                       break;
+               }
+               if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) {
+                       pr_err("tnt4882: address change interrupted read\n");
+                       retval = -EINTR;
+                       break;
+               }
+
+               spin_lock_irqsave(&board->spinlock, flags);
+               count += drain_fifo_words(tnt_priv, &buffer[count], length - count);
+               tnt_priv->imr3_bits |= HR_NEF;
+               tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+               spin_unlock_irqrestore(&board->spinlock, flags);
+
+               if (need_resched())
+                       schedule();
+       }
+       // wait for last byte
+       if (count < length) {
+               spin_lock_irqsave(&board->spinlock, flags);
+               tnt_priv->imr3_bits |= HR_DONE | HR_NEF;
+               tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+               spin_unlock_irqrestore(&board->spinlock, flags);
+
+               if (wait_event_interruptible(board->wait,
+                                            fifo_xfer_done(tnt_priv) ||
+                                            test_bit(RECEIVED_END_BN, &nec_priv->state) ||
+                                            test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                            test_bit(ADR_CHANGE_BN, &nec_priv->state) ||
+                                            test_bit(TIMO_NUM, &board->status))) {
+                       pr_err("tnt4882: read interrupted\n");
+                       retval = -ERESTARTSYS;
+               }
+               if (test_bit(TIMO_NUM, &board->status))
+                       //pr_info("tnt4882: read timed out\n");
+                       retval = -ETIMEDOUT;
+               if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) {
+                       pr_err("tnt4882: device clear interrupted read\n");
+                       retval = -EINTR;
+               }
+               if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) {
+                       pr_err("tnt4882: address change interrupted read\n");
+                       retval = -EINTR;
+               }
+               count += drain_fifo_words(tnt_priv, &buffer[count], length - count);
+               if (fifo_byte_available(tnt_priv) && count < length)
+                       buffer[count++] = tnt_readb(tnt_priv, FIFOB);
+       }
+       if (count < length)
+               tnt_writeb(tnt_priv, STOP, CMDR);
+       udelay(1);
+
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0);
+       /* force handling of any pending interrupts (seems to be needed
+        * to keep interrupts from getting hosed, plus for syncing
+        * with RECEIVED_END below)
+        */
+       tnt4882_internal_interrupt(board);
+       /* RECEIVED_END should be in sync now */
+       if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state))
+               *end = 1;
+       if (retval < 0) {
+               // force immediate holdoff
+               write_byte(nec_priv, AUX_HLDI, AUXMR);
+
+               set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+       }
+       *bytes_read = count;
+
+       return retval;
+}
+
+static int fifo_space_available(struct tnt4882_priv *tnt_priv)
+{
+       int status2;
+       int retval;
+
+       status2 = tnt_readb(tnt_priv, STS2);
+       retval = (status2 & AFFN) && (status2 & BFFN);
+
+       return retval;
+}
+
+static unsigned int tnt_transfer_count(struct tnt4882_priv *tnt_priv)
+{
+       unsigned int count = 0;
+
+       count |= tnt_readb(tnt_priv, CNT0) & 0xff;
+       count |= (tnt_readb(tnt_priv, CNT1) << 8) & 0xff00;
+       count |= (tnt_readb(tnt_priv, CNT2) << 16) & 0xff0000;
+       count |= (tnt_readb(tnt_priv, CNT3) << 24) & 0xff000000;
+       // return two's complement
+       return -count;
+};
+
+static int write_wait(gpib_board_t *board, struct tnt4882_priv *tnt_priv,
+                     int wait_for_done, int send_commands)
+{
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+
+       if (wait_event_interruptible(board->wait,
+                                    (!wait_for_done && fifo_space_available(tnt_priv)) ||
+                                    fifo_xfer_done(tnt_priv) ||
+                                    test_bit(BUS_ERROR_BN, &nec_priv->state) ||
+                                    test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                    test_bit(TIMO_NUM, &board->status))) {
+               GPIB_DPRINTK("gpib write interrupted\n");
+               return -ERESTARTSYS;
+       }
+       if (test_bit(TIMO_NUM, &board->status)) {
+               pr_info("tnt4882: write timed out\n");
+               return -ETIMEDOUT;
+       }
+       if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) {
+               pr_err("tnt4882: write bus error\n");
+               return (send_commands) ? -ENOTCONN : -ECOMM;
+       }
+       if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) {
+               pr_err("tnt4882: device clear interrupted write\n");
+               return -EINTR;
+       }
+       return 0;
+}
+
+static int generic_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                        int send_eoi, int send_commands, size_t *bytes_written)
+{
+       size_t count = 0;
+       ssize_t retval = 0;
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+       unsigned int bits;
+       s32 hw_count;
+       unsigned long flags;
+
+       *bytes_written = 0;
+       // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state);
+
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_ERRIE, HR_ERRIE);
+
+       if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004)
+               nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO);
+       else
+               nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0);
+
+       tnt_writeb(tnt_priv, RESET_FIFO, CMDR);
+       udelay(1);
+
+       bits = TNT_B_16BIT;
+       if (send_eoi) {
+               bits |= TNT_CCEN;
+               if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004)
+                       tnt_writeb(tnt_priv, AUX_SEOI, CCR);
+       }
+       if (send_commands)
+               bits |= TNT_COMMAND;
+       tnt_writeb(tnt_priv, bits, CFG);
+
+       // load 2's complement of count into hardware counters
+       hw_count = -length;
+       tnt_writeb(tnt_priv, hw_count & 0xff, CNT0);
+       tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1);
+       tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2);
+       tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3);
+
+       tnt_writeb(tnt_priv, GO, CMDR);
+       udelay(1);
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       tnt_priv->imr3_bits |= HR_DONE;
+       tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+
+       while (count < length)  {
+               // wait until byte is ready to be sent
+               retval = write_wait(board, tnt_priv, 0, send_commands);
+               if (retval < 0)
+                       break;
+               if (fifo_xfer_done(tnt_priv))
+                       break;
+               spin_lock_irqsave(&board->spinlock, flags);
+               while (fifo_space_available(tnt_priv) && count < length) {
+                       u16 word;
+
+                       word = buffer[count++] & 0xff;
+                       if (count < length)
+                               word |= (buffer[count++] << 8) & 0xff00;
+                       tnt_priv->io_writew(word, nec_priv->iobase + FIFOB);
+               }
+//  avoid unnecessary HR_NFF interrupts
+//             tnt_priv->imr3_bits |= HR_NFF;
+//             tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+               spin_unlock_irqrestore(&board->spinlock, flags);
+
+               if (need_resched())
+                       schedule();
+       }
+       // wait last byte has been sent
+       if (retval == 0)
+               retval = write_wait(board, tnt_priv, 1, send_commands);
+
+       tnt_writeb(tnt_priv, STOP, CMDR);
+       udelay(1);
+
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_ERR, 0x0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0x0);
+       /* force handling of any interrupts that happened
+        * while they were masked (this appears to be needed)
+        */
+       tnt4882_internal_interrupt(board);
+       *bytes_written = length - tnt_transfer_count(tnt_priv);
+       return retval;
+}
+
+int tnt4882_accel_write(gpib_board_t *board, uint8_t *buffer, size_t length, int send_eoi,
+                       size_t *bytes_written)
+{
+       return generic_write(board, buffer, length, send_eoi, 0, bytes_written);
+}
+
+int tnt4882_command(gpib_board_t *board, uint8_t *buffer, size_t length, size_t *bytes_written)
+{
+       return generic_write(board, buffer, length, 0, 1, bytes_written);
+}
+
+irqreturn_t tnt4882_internal_interrupt(gpib_board_t *board)
+{
+       struct tnt4882_priv *priv = board->private_data;
+       int isr0_bits, isr3_bits, imr3_bits;
+       unsigned long flags;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+
+       nec7210_interrupt(board, &priv->nec7210_priv);
+
+       isr0_bits = tnt_readb(priv, ISR0);
+       isr3_bits = tnt_readb(priv, ISR3);
+       imr3_bits = priv->imr3_bits;
+
+       if (isr0_bits & TNT_IFCI_BIT)
+               push_gpib_event(board, EventIFC);
+       //XXX don't need this wakeup, one below should do?
+//             wake_up_interruptible(&board->wait);
+
+       if (isr3_bits & HR_NFF)
+               priv->imr3_bits &= ~HR_NFF;
+       if (isr3_bits & HR_NEF)
+               priv->imr3_bits &= ~HR_NEF;
+       if (isr3_bits & HR_DONE)
+               priv->imr3_bits &= ~HR_DONE;
+       if (isr3_bits & (HR_INTR | HR_TLCI)) {
+               GPIB_DPRINTK("tnt4882: minor %i isr0 0x%x imr0 0x%x isr3 0x%x imr3 0x%x\n",
+                            board->minor,
+                            isr0_bits, priv->imr0_bits, isr3_bits, imr3_bits);
+               tnt_writeb(priv, priv->imr3_bits, IMR3);
+               wake_up_interruptible(&board->wait);
+       }
+       spin_unlock_irqrestore(&board->spinlock, flags);
+       return IRQ_HANDLED;
+}
+
+irqreturn_t tnt4882_interrupt(int irq, void *arg)
+{
+       return tnt4882_internal_interrupt(arg);
+}
+
+static int ni_tnt_isa_attach(gpib_board_t *board, const gpib_board_config_t *config);
+static int ni_nat4882_isa_attach(gpib_board_t *board, const gpib_board_config_t *config);
+static int ni_nec_isa_attach(gpib_board_t *board, const gpib_board_config_t *config);
+static int ni_pci_attach(gpib_board_t *board, const gpib_board_config_t *config);
+
+static void ni_isa_detach(gpib_board_t *board);
+static void ni_pci_detach(gpib_board_t *board);
+
+#ifdef GPIB_PCMCIA
+static int ni_pcmcia_attach(gpib_board_t *board, const gpib_board_config_t *config);
+static void ni_pcmcia_detach(gpib_board_t *board);
+static int init_ni_gpib_cs(void);
+static void __exit exit_ni_gpib_cs(void);
+#endif
+
+// wrappers for interface functions
+int tnt4882_read(gpib_board_t *board, uint8_t *buffer, size_t length, int *end, size_t *bytes_read)
+{
+       struct tnt4882_priv *priv = board->private_data;
+       struct nec7210_priv *nec_priv = &priv->nec7210_priv;
+       int retval;
+       int dummy;
+
+       retval = nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read);
+
+       if (retval < 0) {       // force immediate holdoff
+               write_byte(nec_priv, AUX_HLDI, AUXMR);
+
+               set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+
+               nec7210_read_data_in(board, nec_priv, &dummy);
+       }
+       return retval;
+}
+
+int tnt4882_write(gpib_board_t *board, uint8_t *buffer, size_t length, int send_eoi,
+                 size_t *bytes_written)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written);
+}
+
+int tnt4882_command_unaccel(gpib_board_t *board, uint8_t *buffer,
+                           size_t length, size_t *bytes_written)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written);
+}
+
+int tnt4882_take_control(gpib_board_t *board, int synchronous)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_take_control(board, &priv->nec7210_priv, synchronous);
+}
+
+int tnt4882_go_to_standby(gpib_board_t *board)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_go_to_standby(board, &priv->nec7210_priv);
+}
+
+void tnt4882_request_system_control(gpib_board_t *board, int request_control)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       if (request_control) {
+               tnt_writeb(priv, SETSC, CMDR);
+               udelay(1);
+       }
+       nec7210_request_system_control(board, &priv->nec7210_priv, request_control);
+       if (!request_control) {
+               tnt_writeb(priv, CLRSC, CMDR);
+               udelay(1);
+       }
+}
+
+void tnt4882_interface_clear(gpib_board_t *board, int assert)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_interface_clear(board, &priv->nec7210_priv, assert);
+}
+
+void tnt4882_remote_enable(gpib_board_t *board, int enable)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_remote_enable(board, &priv->nec7210_priv, enable);
+}
+
+int tnt4882_enable_eos(gpib_board_t *board, uint8_t eos_byte, int compare_8_bits)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits);
+}
+
+void tnt4882_disable_eos(gpib_board_t *board)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_disable_eos(board, &priv->nec7210_priv);
+}
+
+unsigned int tnt4882_update_status(gpib_board_t *board, unsigned int clear_mask)
+{
+       unsigned long flags;
+       u8 line_status;
+       unsigned int retval;
+       struct tnt4882_priv *priv = board->private_data;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       board->status &= ~clear_mask;
+       retval = nec7210_update_status_nolock(board, &priv->nec7210_priv);
+       /* set / clear SRQ state since it is not cleared by interrupt */
+       line_status = tnt_readb(priv, BSR);
+       if (line_status & BCSR_SRQ_BIT)
+               set_bit(SRQI_NUM, &board->status);
+       else
+               clear_bit(SRQI_NUM, &board->status);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+       return board->status;
+}
+
+int tnt4882_primary_address(gpib_board_t *board, unsigned int address)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_primary_address(board, &priv->nec7210_priv, address);
+}
+
+int tnt4882_secondary_address(gpib_board_t *board, unsigned int address, int enable)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable);
+}
+
+int tnt4882_parallel_poll(gpib_board_t *board, uint8_t *result)
+
+{
+       struct tnt4882_priv *tnt_priv = board->private_data;
+
+       if (tnt_priv->nec7210_priv.type != NEC7210) {
+               tnt_priv->auxg_bits |= RPP2_BIT;
+               write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR);
+               udelay(2);      //FIXME use parallel poll timeout
+               *result = read_byte(&tnt_priv->nec7210_priv, CPTR);
+               tnt_priv->auxg_bits &= ~RPP2_BIT;
+               write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR);
+               return 0;
+       } else {
+               return nec7210_parallel_poll(board, &tnt_priv->nec7210_priv, result);
+       }
+}
+
+void tnt4882_parallel_poll_configure(gpib_board_t *board, uint8_t config)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       if (priv->nec7210_priv.type == TNT5004) {
+               /* configure locally */
+               write_byte(&priv->nec7210_priv, AUXRI | 0x4, AUXMR);
+               if (config)
+                       /* set response + clear sense */
+                       write_byte(&priv->nec7210_priv, PPR | config, AUXMR);
+               else
+                       /* disable ppoll */
+                       write_byte(&priv->nec7210_priv, PPR | 0x10, AUXMR);
+       } else {
+               nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config);
+       }
+}
+
+void tnt4882_parallel_poll_response(gpib_board_t *board, int ist)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist);
+}
+
+/* this is just used by the old nec7210 isa interfaces, the newer
+ * boards use tnt4882_serial_poll_response2
+ */
+void tnt4882_serial_poll_response(gpib_board_t *board, uint8_t status)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_serial_poll_response(board, &priv->nec7210_priv, status);
+}
+
+static void tnt4882_serial_poll_response2(gpib_board_t *board, uint8_t status,
+                                         int new_reason_for_service)
+{
+       struct tnt4882_priv *priv = board->private_data;
+       unsigned long flags;
+       const int MSS = status & request_service_bit;
+       const int reqt = MSS && new_reason_for_service;
+       const int reqf = MSS == 0;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       if (reqt) {
+               priv->nec7210_priv.srq_pending = 1;
+               clear_bit(SPOLL_NUM, &board->status);
+       } else {
+               if (reqf)
+                       priv->nec7210_priv.srq_pending = 0;
+       }
+       if (reqt)
+               /* It may seem like a race to issue reqt before updating
+                * the status byte, but it is not.  The chip does not
+                * issue the reqt until the SPMR is written to at
+                * a later time.
+                */
+               write_byte(&priv->nec7210_priv, AUX_REQT, AUXMR);
+       else if (reqf)
+               write_byte(&priv->nec7210_priv, AUX_REQF, AUXMR);
+       /* We need to always zero bit 6 of the status byte before writing it to
+        * the SPMR to insure we are using
+        * serial poll mode SP1, and not accidentally triggering mode SP3.
+        */
+       write_byte(&priv->nec7210_priv, status & ~request_service_bit, SPMR);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+}
+
+uint8_t tnt4882_serial_poll_status(gpib_board_t *board)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       return nec7210_serial_poll_status(board, &priv->nec7210_priv);
+}
+
+void tnt4882_return_to_local(gpib_board_t *board)
+{
+       struct tnt4882_priv *priv = board->private_data;
+
+       nec7210_return_to_local(board, &priv->nec7210_priv);
+}
+
+gpib_interface_t ni_pci_interface = {
+name: "ni_pci",
+attach :  ni_pci_attach,
+detach :  ni_pci_detach,
+read :  tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_pci_accel_interface = {
+name: "ni_pci_accel",
+attach : ni_pci_attach,
+detach : ni_pci_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_isa_interface = {
+name: "ni_isa",
+attach : ni_tnt_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_nat4882_isa_interface = {
+name: "ni_nat4882_isa",
+attach : ni_nat4882_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_read,
+write : tnt4882_write,
+command : tnt4882_command_unaccel,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_nec_isa_interface = {
+name: "ni_nec_isa",
+attach : ni_nec_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_read,
+write : tnt4882_write,
+command : tnt4882_command_unaccel,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : NULL,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response : tnt4882_serial_poll_response,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_isa_accel_interface = {
+name: "ni_isa_accel",
+attach : ni_tnt_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_nat4882_isa_accel_interface = {
+name: "ni_nat4882_isa_accel",
+attach : ni_nat4882_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command_unaccel,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response2 : tnt4882_serial_poll_response2,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_nec_isa_accel_interface = {
+name: "ni_nec_isa_accel",
+attach : ni_nec_isa_attach,
+detach : ni_isa_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command_unaccel,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : NULL,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response : tnt4882_serial_poll_response,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+#ifdef GPIB_PCMCIA
+gpib_interface_t ni_pcmcia_interface = {
+name: "ni_pcmcia",
+attach : ni_pcmcia_attach,
+detach : ni_pcmcia_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response : tnt4882_serial_poll_response,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+
+gpib_interface_t ni_pcmcia_accel_interface = {
+name: "ni_pcmcia_accel",
+attach : ni_pcmcia_attach,
+detach : ni_pcmcia_detach,
+read : tnt4882_accel_read,
+write : tnt4882_accel_write,
+command : tnt4882_command,
+take_control : tnt4882_take_control,
+go_to_standby : tnt4882_go_to_standby,
+request_system_control : tnt4882_request_system_control,
+interface_clear : tnt4882_interface_clear,
+remote_enable : tnt4882_remote_enable,
+enable_eos : tnt4882_enable_eos,
+disable_eos : tnt4882_disable_eos,
+parallel_poll : tnt4882_parallel_poll,
+parallel_poll_configure : tnt4882_parallel_poll_configure,
+parallel_poll_response : tnt4882_parallel_poll_response,
+local_parallel_poll_mode : NULL, // XXX
+line_status : tnt4882_line_status,
+update_status : tnt4882_update_status,
+primary_address : tnt4882_primary_address,
+secondary_address : tnt4882_secondary_address,
+serial_poll_response : tnt4882_serial_poll_response,
+serial_poll_status : tnt4882_serial_poll_status,
+t1_delay : tnt4882_t1_delay,
+return_to_local : tnt4882_return_to_local,
+};
+#endif
+
+void tnt4882_board_reset(struct tnt4882_priv *tnt_priv, gpib_board_t *board)
+{
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+
+       tnt_priv->imr0_bits = 0;
+       tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0);
+       tnt_priv->imr3_bits = 0;
+       tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+       tnt_readb(tnt_priv, IMR0);
+       tnt_readb(tnt_priv, IMR3);
+       nec7210_board_reset(nec_priv, board);
+}
+
+int tnt4882_allocate_private(gpib_board_t *board)
+{
+       struct tnt4882_priv *tnt_priv;
+
+       board->private_data = kmalloc(sizeof(struct tnt4882_priv), GFP_KERNEL);
+       if (!board->private_data)
+               return -1;
+       tnt_priv = board->private_data;
+       memset(tnt_priv, 0, sizeof(struct tnt4882_priv));
+       init_nec7210_private(&tnt_priv->nec7210_priv);
+       return 0;
+}
+
+void tnt4882_free_private(gpib_board_t *board)
+{
+       kfree(board->private_data);
+       board->private_data = NULL;
+}
+
+void tnt4882_init(struct tnt4882_priv *tnt_priv, const gpib_board_t *board)
+{
+       struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv;
+
+       /* Turbo488 software reset */
+       tnt_writeb(tnt_priv, SOFT_RESET, CMDR);
+       udelay(1);
+
+       // turn off one-chip mode
+       tnt_writeb(tnt_priv, NODMA, HSSEL);
+       tnt_writeb(tnt_priv, 0, ACCWR);
+       // make sure we are in 7210 mode
+       tnt_writeb(tnt_priv, AUX_7210, AUXCR);
+       udelay(1);
+       // registers might be swapped, so write it to the swapped address too
+       tnt_writeb(tnt_priv, AUX_7210, SWAPPED_AUXCR);
+       udelay(1);
+       // turn on one-chip mode
+       if (nec_priv->type == TNT4882 || nec_priv->type == TNT5004)
+               tnt_writeb(tnt_priv, NODMA | TNT_ONE_CHIP_BIT, HSSEL);
+       else
+               tnt_writeb(tnt_priv, NODMA, HSSEL);
+
+       nec7210_board_reset(nec_priv, board);
+       // read-clear isr0
+       tnt_readb(tnt_priv, ISR0);
+
+       // enable passing of nat4882 interrupts
+       tnt_priv->imr3_bits = HR_TLCI;
+       tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3);
+
+       // enable interrupt
+       tnt_writeb(tnt_priv, 0x1, INTRT);
+
+       // force immediate holdoff
+       write_byte(&tnt_priv->nec7210_priv, AUX_HLDI, AUXMR);
+
+       set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+
+       tnt_priv->auxg_bits = AUXRG | NTNL_BIT;
+       write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR);
+
+       nec7210_board_online(nec_priv, board);
+       // enable interface clear interrupt for event queue
+       tnt_priv->imr0_bits = TNT_IMR0_ALWAYS_BITS | TNT_ATNI_BIT | TNT_IFCIE_BIT;
+       tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0);
+}
+
+int ni_pci_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       struct tnt4882_priv *tnt_priv;
+       struct nec7210_priv *nec_priv;
+       int isr_flags = IRQF_SHARED;
+       int retval;
+       struct mite_struct *mite;
+
+       board->status = 0;
+
+       if (tnt4882_allocate_private(board))
+               return -ENOMEM;
+       tnt_priv = board->private_data;
+       tnt_priv->io_writeb = writeb_wrapper;
+       tnt_priv->io_readb = readb_wrapper;
+       tnt_priv->io_writew = writew_wrapper;
+       tnt_priv->io_readw = readw_wrapper;
+       nec_priv = &tnt_priv->nec7210_priv;
+       nec_priv->type = TNT4882;
+       nec_priv->read_byte = nec7210_locking_iomem_read_byte;
+       nec_priv->write_byte = nec7210_locking_iomem_write_byte;
+       nec_priv->offset = atgpib_reg_offset;
+
+       if (!mite_devices) {
+               pr_err("no National Instruments PCI boards found\n");
+               return -1;
+       }
+
+       for (mite = mite_devices; mite; mite = mite->next) {
+               short found_board;
+
+               if (mite->used)
+                       continue;
+               if (config->pci_bus >= 0 && config->pci_bus != mite->pcidev->bus->number)
+                       continue;
+               if (config->pci_slot >= 0 && config->pci_slot != PCI_SLOT(mite->pcidev->devfn))
+                       continue;
+               switch (mite_device_id(mite)) {
+               case PCI_DEVICE_ID_NI_GPIB:
+               case PCI_DEVICE_ID_NI_GPIB_PLUS:
+               case PCI_DEVICE_ID_NI_GPIB_PLUS2:
+               case PCI_DEVICE_ID_NI_PXIGPIB:
+               case PCI_DEVICE_ID_NI_PMCGPIB:
+               case PCI_DEVICE_ID_NI_PCIEGPIB:
+               case PCI_DEVICE_ID_NI_PCIE2GPIB:
+// support for Measurement Computing PCI-488
+               case PCI_DEVICE_ID_MC_PCI488:
+               case PCI_DEVICE_ID_CEC_NI_GPIB:
+                       found_board = 1;
+                       break;
+               default:
+                       found_board = 0;
+                       break;
+               }
+               if (found_board)
+                       break;
+       }
+       if (!mite) {
+               pr_err("no NI PCI-GPIB boards found\n");
+               return -1;
+       }
+       tnt_priv->mite = mite;
+       retval = mite_setup(tnt_priv->mite);
+       if (retval < 0) {
+               pr_err("tnt4882: error setting up mite.\n");
+               return retval;
+       }
+
+       nec_priv->iobase = tnt_priv->mite->daq_io_addr;
+
+       // get irq
+       if (request_irq(mite_irq(tnt_priv->mite), tnt4882_interrupt, isr_flags,
+                       "ni-pci-gpib", board)) {
+               pr_err("gpib: can't request IRQ %d\n", mite_irq(tnt_priv->mite));
+               return -1;
+       }
+       tnt_priv->irq = mite_irq(tnt_priv->mite);
+       pr_info("tnt4882: irq %i\n", tnt_priv->irq);
+
+       // TNT5004 detection
+       switch (tnt_readb(tnt_priv, CSR) & 0xf0) {
+       case 0x30:
+               nec_priv->type = TNT4882;
+               pr_info("tnt4882: TNT4882 chipset detected\n");
+               break;
+       case 0x40:
+               nec_priv->type = TNT5004;
+               pr_info("tnt4882: TNT5004 chipset detected\n");
+               break;
+       }
+       tnt4882_init(tnt_priv, board);
+
+       return 0;
+}
+
+void ni_pci_detach(gpib_board_t *board)
+{
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv;
+
+       if (tnt_priv) {
+               nec_priv = &tnt_priv->nec7210_priv;
+
+               if (nec_priv->iobase)
+                       tnt4882_board_reset(tnt_priv, board);
+               if (tnt_priv->irq)
+                       free_irq(tnt_priv->irq, board);
+               if (tnt_priv->mite)
+                       mite_unsetup(tnt_priv->mite);
+       }
+       tnt4882_free_private(board);
+}
+
+static int ni_isapnp_find(struct pnp_dev **dev)
+{
+       *dev = pnp_find_dev(NULL, ISAPNP_VENDOR_ID_NI,
+                           ISAPNP_FUNCTION(ISAPNP_ID_NI_ATGPIB_TNT), NULL);
+       if (!*dev || !(*dev)->card) {
+               pr_err("tnt4882: failed to find isapnp board\n");
+               return -ENODEV;
+       }
+       if (pnp_device_attach(*dev) < 0) {
+               pr_err("tnt4882: atgpib/tnt board already active, skipping\n");
+               return -EBUSY;
+       }
+       if (pnp_activate_dev(*dev) < 0) {
+               pnp_device_detach(*dev);
+               pr_err("tnt4882: failed to activate() atgpib/tnt, aborting\n");
+               return -EAGAIN;
+       }
+       if (!pnp_port_valid(*dev, 0) || !pnp_irq_valid(*dev, 0)) {
+               pnp_device_detach(*dev);
+               pr_err("tnt4882: invalid port or irq for atgpib/tnt, aborting\n");
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+static int ni_isa_attach_common(gpib_board_t *board, const gpib_board_config_t *config,
+                               enum nec7210_chipset chipset)
+{
+       struct tnt4882_priv *tnt_priv;
+       struct nec7210_priv *nec_priv;
+       int isr_flags = 0;
+       void *iobase;
+       int irq;
+
+       board->status = 0;
+
+       if (tnt4882_allocate_private(board))
+               return -ENOMEM;
+       tnt_priv = board->private_data;
+       tnt_priv->io_writeb = outb_wrapper;
+       tnt_priv->io_readb = inb_wrapper;
+       tnt_priv->io_writew = outw_wrapper;
+       tnt_priv->io_readw = inw_wrapper;
+       nec_priv = &tnt_priv->nec7210_priv;
+       nec_priv->type = chipset;
+       nec_priv->read_byte = nec7210_locking_ioport_read_byte;
+       nec_priv->write_byte = nec7210_locking_ioport_write_byte;
+       nec_priv->offset = atgpib_reg_offset;
+
+       // look for plug-n-play board
+       if (config->ibbase == 0) {
+               struct pnp_dev *dev;
+               int retval;
+
+               retval = ni_isapnp_find(&dev);
+               if (retval < 0)
+                       return retval;
+               tnt_priv->pnp_dev = dev;
+               iobase = (void *)(pnp_port_start(dev, 0));
+               irq = pnp_irq(dev, 0);
+       } else {
+               iobase = config->ibbase;
+               irq = config->ibirq;
+       }
+       // allocate ioports
+       if (!request_region((unsigned long)(iobase), atgpib_iosize, "atgpib")) {
+               pr_err("tnt4882: failed to allocate ioports\n");
+               return -1;
+       }
+       nec_priv->iobase = iobase;
+
+       // get irq
+       if (request_irq(irq, tnt4882_interrupt, isr_flags, "atgpib", board)) {
+               pr_err("gpib: can't request IRQ %d\n", irq);
+               return -1;
+       }
+       tnt_priv->irq = irq;
+
+       tnt4882_init(tnt_priv, board);
+
+       return 0;
+}
+
+int ni_tnt_isa_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return ni_isa_attach_common(board, config, TNT4882);
+}
+
+int ni_nat4882_isa_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return ni_isa_attach_common(board, config, NAT4882);
+}
+
+int ni_nec_isa_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return ni_isa_attach_common(board, config, NEC7210);
+}
+
+void ni_isa_detach(gpib_board_t *board)
+{
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv;
+
+       if (tnt_priv) {
+               nec_priv = &tnt_priv->nec7210_priv;
+               if (nec_priv->iobase)
+                       tnt4882_board_reset(tnt_priv, board);
+               if (tnt_priv->irq)
+                       free_irq(tnt_priv->irq, board);
+               if (nec_priv->iobase)
+                       release_region((unsigned long)(nec_priv->iobase), atgpib_iosize);
+               if (tnt_priv->pnp_dev)
+                       pnp_device_detach(tnt_priv->pnp_dev);
+       }
+       tnt4882_free_private(board);
+}
+
+static int tnt4882_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+       return 0;
+}
+
+static const struct pci_device_id tnt4882_pci_table[] = {
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS2)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PXIGPIB)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PMCGPIB)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIEGPIB)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIE2GPIB)},
+       // support for Measurement Computing PCI-488
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_MC_PCI488)},
+       {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_CEC_NI_GPIB)},
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, tnt4882_pci_table);
+
+static struct pci_driver tnt4882_pci_driver = {
+       .name = "tnt4882",
+       .id_table = tnt4882_pci_table,
+       .probe = &tnt4882_pci_probe
+};
+
+static const struct pnp_device_id tnt4882_pnp_table[] = {
+       {.id = "NICC601"},
+       {.id = ""}
+};
+MODULE_DEVICE_TABLE(pnp, tnt4882_pnp_table);
+
+static int __init tnt4882_init_module(void)
+{
+       int result;
+
+       result = pci_register_driver(&tnt4882_pci_driver);
+       if (result) {
+               pr_err("tnt4882: pci_driver_register failed!\n");
+               return result;
+       }
+
+       gpib_register_driver(&ni_isa_interface, THIS_MODULE);
+       gpib_register_driver(&ni_isa_accel_interface, THIS_MODULE);
+       gpib_register_driver(&ni_nat4882_isa_interface, THIS_MODULE);
+       gpib_register_driver(&ni_nat4882_isa_accel_interface, THIS_MODULE);
+       gpib_register_driver(&ni_nec_isa_interface, THIS_MODULE);
+       gpib_register_driver(&ni_nec_isa_accel_interface, THIS_MODULE);
+       gpib_register_driver(&ni_pci_interface, THIS_MODULE);
+       gpib_register_driver(&ni_pci_accel_interface, THIS_MODULE);
+#ifdef GPIB_PCMCIA
+       gpib_register_driver(&ni_pcmcia_interface, THIS_MODULE);
+       gpib_register_driver(&ni_pcmcia_accel_interface, THIS_MODULE);
+       if (init_ni_gpib_cs() < 0)
+               return -1;
+#endif
+
+       mite_init();
+       mite_list_devices();
+
+       return 0;
+}
+
+static void __exit tnt4882_exit_module(void)
+{
+       gpib_unregister_driver(&ni_isa_interface);
+       gpib_unregister_driver(&ni_isa_accel_interface);
+       gpib_unregister_driver(&ni_nat4882_isa_interface);
+       gpib_unregister_driver(&ni_nat4882_isa_accel_interface);
+       gpib_unregister_driver(&ni_nec_isa_interface);
+       gpib_unregister_driver(&ni_nec_isa_accel_interface);
+       gpib_unregister_driver(&ni_pci_interface);
+       gpib_unregister_driver(&ni_pci_accel_interface);
+#ifdef GPIB_PCMCIA
+       gpib_unregister_driver(&ni_pcmcia_interface);
+       gpib_unregister_driver(&ni_pcmcia_accel_interface);
+       exit_ni_gpib_cs();
+#endif
+
+       mite_cleanup();
+
+       pci_unregister_driver(&tnt4882_pci_driver);
+}
+
+#ifdef GPIB_PCMCIA
+
+#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/ptrace.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+/*
+ * All the PCMCIA modules use PCMCIA_DEBUG to control debugging.  If
+ * you do not define PCMCIA_DEBUG at all, all the debug code will be
+ * left out.  If you compile with PCMCIA_DEBUG=0, the debug code will
+ * be present but disabled -- but it can then be enabled for specific
+ * modules at load time with a 'pc_debug=#' option to insmod.
+ */
+#define PCMCIA_DEBUG 1
+#ifdef PCMCIA_DEBUG
+static int pc_debug = PCMCIA_DEBUG;
+module_param(pc_debug, int, 0);
+#define DEBUG(n, args...)                      \
+       do {if (pc_debug > (n))                 \
+                       pr_debug(args); }       \
+       while (0)
+#else
+#define DEBUG(args...)
+#endif
+
+static int ni_gpib_config(struct pcmcia_device  *link);
+static void ni_gpib_release(struct pcmcia_device *link);
+static int ni_pcmcia_attach(gpib_board_t *board, const gpib_board_config_t *config);
+static void ni_pcmcia_detach(gpib_board_t *board);
+
+/*
+ * A linked list of "instances" of the dummy device.  Each actual
+ * PCMCIA card corresponds to one device instance, and is described
+ * by one dev_link_t structure (defined in ds.h).
+ *
+ * You may not want to use a linked list for this -- for example, the
+ * memory card driver uses an array of dev_link_t pointers, where minor
+ * device numbers are used to derive the corresponding array index.
+ *
+ * I think this dev_list is obsolete but the pointer is needed to keep
+ * the module instance for the ni_pcmcia_attach function.
+ */
+
+static struct pcmcia_device   *curr_dev;
+
+struct local_info_t {
+       struct pcmcia_device    *p_dev;
+       gpib_board_t            *dev;
+       int                     stop;
+       struct bus_operations   *bus;
+};
+
+/*
+ * ni_gpib_probe() creates an "instance" of the driver, allocating
+ * local data structures for one device.  The device is registered
+ * with Card Services.
+ */
+
+static int ni_gpib_probe(struct pcmcia_device *link)
+{
+       struct local_info_t *info;
+       //struct gpib_board_t *dev;
+
+       DEBUG(0, "%s(0x%p)\n", __func__, link);
+
+       /* Allocate space for private device-specific data */
+       info = kmalloc(sizeof(*info), GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+       memset(info, 0, sizeof(*info));
+
+       info->p_dev = link;
+       link->priv = info;
+
+       /*
+        * General socket configuration defaults can go here.  In this
+        * client, we assume very little, and rely on the CIS for almost
+        * everything.  In most clients, many details (i.e., number, sizes,
+        * and attributes of IO windows) are fixed by the nature of the
+        * device, and can be hard-wired here.
+        */
+       link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
+       /* Register with Card Services */
+       curr_dev = link;
+       return ni_gpib_config(link);
+}
+
+/*
+ *     This deletes a driver "instance".  The device is de-registered
+ *     with Card Services.  If it has been released, all local data
+ *     structures are freed.  Otherwise, the structures will be freed
+ *     when the device is released.
+ */
+static void ni_gpib_remove(struct pcmcia_device *link)
+{
+       struct local_info_t *info = link->priv;
+       //struct gpib_board_t *dev = info->dev;
+
+       DEBUG(0, "%s(%p)\n", __func__, link);
+
+       if (info->dev)
+               ni_pcmcia_detach(info->dev);
+       ni_gpib_release(link);
+
+       //free_netdev(dev);
+       kfree(info);
+}
+
+static int ni_gpib_config_iteration(struct pcmcia_device *link,        void *priv_data)
+{
+       int retval;
+
+       retval = pcmcia_request_io(link);
+       if (retval != 0)
+               return retval;
+
+       return 0;
+}
+
+/*
+ *     ni_gpib_config() is scheduled to run after a CARD_INSERTION event
+ *     is received, to configure the PCMCIA socket, and to make the
+ *     device available to the system.
+ */
+static int ni_gpib_config(struct pcmcia_device *link)
+{
+       //struct local_info_t *info = link->priv;
+       //gpib_board_t *dev = info->dev;
+       int last_ret;
+
+       DEBUG(0, "%s(0x%p)\n", __func__, link);
+
+       last_ret = pcmcia_loop_config(link, &ni_gpib_config_iteration, NULL);
+       if (last_ret) {
+               dev_warn(&link->dev, "no configuration found\n");
+               ni_gpib_release(link);
+               return last_ret;
+       }
+
+       last_ret = pcmcia_enable_device(link);
+       if (last_ret) {
+               ni_gpib_release(link);
+               return last_ret;
+       }
+       return 0;
+} /* ni_gpib_config */
+
+/*
+ * After a card is removed, ni_gpib_release() will unregister the
+ * device, and release the PCMCIA configuration.  If the device is
+ * still open, this will be postponed until it is closed.
+ */
+static void ni_gpib_release(struct pcmcia_device *link)
+{
+       DEBUG(0, "%s(0x%p)\n", __func__, link);
+       pcmcia_disable_device(link);
+} /* ni_gpib_release */
+
+static int ni_gpib_suspend(struct pcmcia_device *link)
+{
+       //struct local_info_t *info = link->priv;
+       //struct gpib_board_t *dev = info->dev;
+       DEBUG(0, "%s(0x%p)\n", __func__, link);
+
+       if (link->open)
+               pr_err("Device still open ???\n");
+       //netif_device_detach(dev);
+
+       return 0;
+}
+
+static int ni_gpib_resume(struct pcmcia_device *link)
+{
+       //struct local_info_t *info = link->priv;
+       //struct gpib_board_t *dev = info->dev;
+       DEBUG(0, "%s(0x%p)\n", __func__, link);
+
+       /*if (link->open) {
+        *      ni_gpib_probe(dev);     / really?
+        *      printk("Gpib resumed ???\n");
+        *      //netif_device_attach(dev);
+        *}
+        */
+       return ni_gpib_config(link);
+}
+
+static struct pcmcia_device_id ni_pcmcia_ids[] = {
+       PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4882),
+       PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0c71), // NI PCMCIA-GPIB+
+       PCMCIA_DEVICE_NULL
+};
+
+MODULE_DEVICE_TABLE(pcmcia, ni_pcmcia_ids);
+
+static struct pcmcia_driver ni_gpib_cs_driver = {
+       .name           = "ni_gpib_cs",
+       .owner          = THIS_MODULE,
+       .drv = { .name = "ni_gpib_cs", },
+       .id_table       = ni_pcmcia_ids,
+       .probe          = ni_gpib_probe,
+       .remove         = ni_gpib_remove,
+       .suspend        = ni_gpib_suspend,
+       .resume         = ni_gpib_resume,
+};
+
+int __init init_ni_gpib_cs(void)
+{
+       return pcmcia_register_driver(&ni_gpib_cs_driver);
+}
+
+void __exit exit_ni_gpib_cs(void)
+{
+       DEBUG(0, "ni_gpib_cs: unloading\n");
+       pcmcia_unregister_driver(&ni_gpib_cs_driver);
+}
+
+int ni_pcmcia_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       struct local_info_t *info;
+       struct tnt4882_priv *tnt_priv;
+       struct nec7210_priv *nec_priv;
+       int isr_flags = IRQF_SHARED;
+
+       DEBUG(0, "%s(0x%p)\n", __func__, board);
+
+       if (!curr_dev) {
+               pr_err("gpib: no NI PCMCIA board found\n");
+               return -1;
+       }
+
+       info = curr_dev->priv;
+       info->dev = board;
+
+       board->status = 0;
+
+       if (tnt4882_allocate_private(board))
+               return -ENOMEM;
+       tnt_priv = board->private_data;
+       tnt_priv->io_writeb = outb_wrapper;
+       tnt_priv->io_readb = inb_wrapper;
+       tnt_priv->io_writew = outw_wrapper;
+       tnt_priv->io_readw = inw_wrapper;
+       nec_priv = &tnt_priv->nec7210_priv;
+       nec_priv->type = TNT4882;
+       nec_priv->read_byte = nec7210_locking_ioport_read_byte;
+       nec_priv->write_byte = nec7210_locking_ioport_write_byte;
+       nec_priv->offset = atgpib_reg_offset;
+
+       DEBUG(0, "ioport1 window attributes: 0x%lx\n", curr_dev->resource[0]->flags);
+       if (request_region(curr_dev->resource[0]->start, resource_size(curr_dev->resource[0]),
+                          "tnt4882") == 0) {
+               pr_err("gpib: ioports starting at 0x%lx are already in use\n",
+                      (unsigned long)curr_dev->resource[0]->start);
+               return -EIO;
+       }
+
+       nec_priv->iobase = (void *)(unsigned long)curr_dev->resource[0]->start;
+
+       // get irq
+       if (request_irq(curr_dev->irq, tnt4882_interrupt, isr_flags, "tnt4882", board)) {
+               pr_err("gpib: can't request IRQ %d\n", curr_dev->irq);
+               return -1;
+       }
+       tnt_priv->irq = curr_dev->irq;
+
+       tnt4882_init(tnt_priv, board);
+
+       return 0;
+}
+
+void ni_pcmcia_detach(gpib_board_t *board)
+{
+       struct tnt4882_priv *tnt_priv = board->private_data;
+       struct nec7210_priv *nec_priv;
+
+       DEBUG(0, "%s(0x%p)\n", __func__, board);
+
+       if (tnt_priv) {
+               nec_priv = &tnt_priv->nec7210_priv;
+               if (tnt_priv->irq)
+                       free_irq(tnt_priv->irq, board);
+               if (nec_priv->iobase) {
+                       tnt4882_board_reset(tnt_priv, board);
+                       release_region((unsigned long)nec_priv->iobase, pcmcia_gpib_iosize);
+               }
+       }
+       tnt4882_free_private(board);
+}
+
+#endif // GPIB_PCMCIA
+
+module_init(tnt4882_init_module);
+module_exit(tnt4882_exit_module);