staging: gpib: Add Frank Mori Hess FPGA PCI GPIB driver
authorDave Penkler <dpenkler@gmail.com>
Wed, 18 Sep 2024 12:19:00 +0000 (14:19 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 10 Oct 2024 13:28:39 +0000 (15:28 +0200)
Driver for Frank Mori Hess' FPGA based PCI board

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

diff --git a/drivers/staging/gpib/fmh_gpib/Makefile b/drivers/staging/gpib/fmh_gpib/Makefile
new file mode 100644 (file)
index 0000000..cc4d9e7
--- /dev/null
@@ -0,0 +1,2 @@
+
+obj-$(CONFIG_GPIB_FMH) += fmh_gpib.o
diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c
new file mode 100644 (file)
index 0000000..07c75ba
--- /dev/null
@@ -0,0 +1,1726 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/***************************************************************************
+ * GPIB Driver for fmh_gpib_core, see
+ * https://github.com/fmhess/fmh_gpib_core
+ *
+ * More specifically, it is a driver for the hardware arrangement described by
+ *  src/examples/fmh_gpib_top.vhd in the fmh_gpib_core repository.
+ *
+ * Author: Frank Mori Hess <fmh6jj@gmail.com>
+ * Copyright: (C) 2006, 2010, 2015 Fluke Corporation
+ *     (C) 2017 Frank Mori Hess
+ ***************************************************************************/
+
+#include "fmh_gpib.h"
+
+#include "gpibP.h"
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+MODULE_LICENSE("GPL");
+
+static irqreturn_t fmh_gpib_interrupt(int irq, void *arg);
+static int fmh_gpib_attach_holdoff_all(gpib_board_t *board, const gpib_board_config_t *config);
+static int fmh_gpib_attach_holdoff_end(gpib_board_t *board, const gpib_board_config_t *config);
+static void fmh_gpib_detach(gpib_board_t *board);
+static int fmh_gpib_pci_attach_holdoff_all(gpib_board_t *board, const gpib_board_config_t *config);
+static int fmh_gpib_pci_attach_holdoff_end(gpib_board_t *board, const gpib_board_config_t *config);
+static void fmh_gpib_pci_detach(gpib_board_t *board);
+static int fmh_gpib_config_dma(gpib_board_t *board, int output);
+static irqreturn_t fmh_gpib_internal_interrupt(gpib_board_t *board);
+static struct platform_driver fmh_gpib_platform_driver;
+static struct pci_driver fmh_gpib_pci_driver;
+
+// wrappers for interface functions
+static int fmh_gpib_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                        int *end, size_t *bytes_read)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read);
+}
+
+static int fmh_gpib_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                         int send_eoi, size_t *bytes_written)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written);
+}
+
+static int fmh_gpib_command(gpib_board_t *board, uint8_t *buffer, size_t length,
+                           size_t *bytes_written)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written);
+}
+
+static int fmh_gpib_take_control(gpib_board_t *board, int synchronous)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_take_control(board, &priv->nec7210_priv, synchronous);
+}
+
+static int fmh_gpib_go_to_standby(gpib_board_t *board)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_go_to_standby(board, &priv->nec7210_priv);
+}
+
+static void fmh_gpib_request_system_control(gpib_board_t *board, int request_control)
+{
+       struct fmh_priv *priv = board->private_data;
+       struct nec7210_priv *nec_priv = &priv->nec7210_priv;
+
+       nec7210_request_system_control(board, nec_priv, request_control);
+}
+
+static void fmh_gpib_interface_clear(gpib_board_t *board, int assert)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       nec7210_interface_clear(board, &priv->nec7210_priv, assert);
+}
+
+static void fmh_gpib_remote_enable(gpib_board_t *board, int enable)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       nec7210_remote_enable(board, &priv->nec7210_priv, enable);
+}
+
+static int fmh_gpib_enable_eos(gpib_board_t *board, uint8_t eos_byte, int compare_8_bits)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits);
+}
+
+static void fmh_gpib_disable_eos(gpib_board_t *board)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       nec7210_disable_eos(board, &priv->nec7210_priv);
+}
+
+static unsigned int fmh_gpib_update_status(gpib_board_t *board, unsigned int clear_mask)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_update_status(board, &priv->nec7210_priv, clear_mask);
+}
+
+static int fmh_gpib_primary_address(gpib_board_t *board, unsigned int address)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_primary_address(board, &priv->nec7210_priv, address);
+}
+
+static int fmh_gpib_secondary_address(gpib_board_t *board, unsigned int address, int enable)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable);
+}
+
+static int fmh_gpib_parallel_poll(gpib_board_t *board, uint8_t *result)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_parallel_poll(board, &priv->nec7210_priv, result);
+}
+
+static void fmh_gpib_parallel_poll_configure(gpib_board_t *board, uint8_t configuration)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration);
+}
+
+static void fmh_gpib_parallel_poll_response(gpib_board_t *board, int ist)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist);
+}
+
+static void fmh_gpib_local_parallel_poll_mode(gpib_board_t *board, int local)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       if (local) {
+               write_byte(&priv->nec7210_priv, AUX_I_REG | LOCAL_PPOLL_MODE_BIT, AUXMR);
+       } else  {
+               /* For fmh_gpib_core, remote parallel poll config mode is unaffected by the
+                * state of the disable bit of the parallel poll register (unlike the tnt4882).
+                * So, we don't need to worry about that.
+                */
+               write_byte(&priv->nec7210_priv, AUX_I_REG | 0x0, AUXMR);
+       }
+}
+
+static void fmh_gpib_serial_poll_response2(gpib_board_t *board, uint8_t status,
+                                          int new_reason_for_service)
+{
+       struct fmh_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);
+}
+
+static uint8_t fmh_gpib_serial_poll_status(gpib_board_t *board)
+{
+       struct fmh_priv *priv = board->private_data;
+
+       return nec7210_serial_poll_status(board, &priv->nec7210_priv);
+}
+
+static void fmh_gpib_return_to_local(gpib_board_t *board)
+{
+       struct fmh_priv *priv = board->private_data;
+       struct nec7210_priv *nec_priv = &priv->nec7210_priv;
+
+       write_byte(nec_priv, AUX_RTL2, AUXMR);
+       udelay(1);
+       write_byte(nec_priv, AUX_RTL, AUXMR);
+}
+
+static int fmh_gpib_line_status(const gpib_board_t *board)
+{
+       int status = ValidALL;
+       int bsr_bits;
+       struct fmh_priv *e_priv;
+       struct nec7210_priv *nec_priv;
+
+       e_priv = board->private_data;
+       nec_priv = &e_priv->nec7210_priv;
+
+       bsr_bits = read_byte(nec_priv, BUS_STATUS_REG);
+
+       if ((bsr_bits & BSR_REN_BIT) == 0)
+               status |= BusREN;
+       if ((bsr_bits & BSR_IFC_BIT) == 0)
+               status |= BusIFC;
+       if ((bsr_bits & BSR_SRQ_BIT) == 0)
+               status |= BusSRQ;
+       if ((bsr_bits & BSR_EOI_BIT) == 0)
+               status |= BusEOI;
+       if ((bsr_bits & BSR_NRFD_BIT) == 0)
+               status |= BusNRFD;
+       if ((bsr_bits & BSR_NDAC_BIT) == 0)
+               status |= BusNDAC;
+       if ((bsr_bits & BSR_DAV_BIT) == 0)
+               status |= BusDAV;
+       if ((bsr_bits & BSR_ATN_BIT) == 0)
+               status |= BusATN;
+
+       return status;
+}
+
+static unsigned int fmh_gpib_t1_delay(gpib_board_t *board, unsigned int nano_sec)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       unsigned int retval;
+
+       retval = nec7210_t1_delay(board, nec_priv, nano_sec);
+
+       if (nano_sec <= 350) {
+               write_byte(nec_priv, AUX_HI_SPEED, AUXMR);
+               retval = 350;
+       } else {
+               write_byte(nec_priv, AUX_LO_SPEED, AUXMR);
+       }
+       return retval;
+}
+
+static int lacs_or_read_ready(gpib_board_t *board)
+{
+       const struct fmh_priv *e_priv = board->private_data;
+       const struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       retval = test_bit(LACS_NUM, &board->status) ||
+               test_bit(READ_READY_BN, &nec_priv->state);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+
+       return retval;
+}
+
+static int wait_for_read(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+
+       if (wait_event_interruptible(board->wait,
+                                    lacs_or_read_ready(board) ||
+                                    test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                    test_bit(TIMO_NUM, &board->status)))
+               retval = -ERESTARTSYS;
+
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+       return retval;
+}
+
+static int wait_for_rx_fifo_half_full_or_end(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+
+       if (wait_event_interruptible(board->wait,
+                                    (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) &
+                                     RX_FIFO_HALF_FULL) ||
+                                    test_bit(RECEIVED_END_BN, &nec_priv->state) ||
+                                    test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                    test_bit(TIMO_NUM, &board->status)))
+               retval = -ERESTARTSYS;
+
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+       return retval;
+}
+
+/* Wait until the gpib chip is ready to accept a data out byte.
+ */
+static int wait_for_data_out_ready(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+//     printk("%s: enter\n", __FUNCTION__);
+
+       if (wait_event_interruptible(board->wait,
+                                    (test_bit(TACS_NUM, &board->status) &&
+                                     (read_byte(nec_priv, EXT_STATUS_1_REG) &
+                                      DATA_OUT_STATUS_BIT)) ||
+                                    test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                    test_bit(TIMO_NUM, &board->status)))
+               retval = -ERESTARTSYS;
+
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+//     printk("%s: exit, retval=%i\n", __FUNCTION__, retval);
+       return retval;
+}
+
+static void fmh_gpib_dma_callback(void *arg)
+{
+       gpib_board_t *board = arg;
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+//     printk("%s: enter\n", __FUNCTION__);
+
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, HR_DOIE | HR_DIIE);
+       wake_up_interruptible(&board->wait);
+
+       fmh_gpib_internal_interrupt(board);
+
+       clear_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state);
+       clear_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state);
+
+       //      printk("%s: exit\n", __FUNCTION__);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+}
+
+/* returns true when all the bytes of a write have been transferred to
+ * the chip and successfully transferred out over the gpib bus.
+ */
+static int fmh_gpib_all_bytes_are_sent(struct fmh_priv *e_priv)
+{
+       if (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & fifo_xfer_counter_mask)
+               return 0;
+
+       if ((read_byte(&e_priv->nec7210_priv, EXT_STATUS_1_REG) & DATA_OUT_STATUS_BIT) == 0)
+               return 0;
+
+       return 1;
+}
+
+static int fmh_gpib_dma_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                             size_t *bytes_written)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       unsigned long flags;
+       int retval = 0;
+       dma_addr_t address;
+       struct dma_async_tx_descriptor *tx_desc;
+
+       *bytes_written = 0;
+//     printk("%s: enter\n", __FUNCTION__);
+       if (WARN_ON_ONCE(length > e_priv->dma_buffer_size))
+               return -EFAULT;
+       dmaengine_terminate_all(e_priv->dma_channel);
+       memcpy(e_priv->dma_buffer, buffer, length);
+       address = dma_map_single(board->dev, e_priv->dma_buffer, length, DMA_TO_DEVICE);
+       if (dma_mapping_error(board->dev,  address))
+               pr_err("dma mapping error in dma write!\n");
+       /* program dma controller */
+       retval = fmh_gpib_config_dma(board, 1);
+       if (retval)
+               goto cleanup;
+
+       tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, address, length, DMA_MEM_TO_DEV,
+                                             DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+       if (!tx_desc) {
+               pr_err("fmh_gpib_gpib: failed to allocate dma transmit descriptor\n");
+               retval = -ENOMEM;
+               goto cleanup;
+       }
+       tx_desc->callback = fmh_gpib_dma_callback;
+       tx_desc->callback_param = board;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG);
+       fifos_write(e_priv, TX_FIFO_DMA_REQUEST_ENABLE | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG);
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO);
+
+       dmaengine_submit(tx_desc);
+       dma_async_issue_pending(e_priv->dma_channel);
+       clear_bit(WRITE_READY_BN, &nec_priv->state);
+       set_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state);
+//     printk("%s: in spin lock\n", __FUNCTION__);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+
+//     printk("%s: waiting for write.\n", __FUNCTION__);
+       // suspend until message is sent
+       if (wait_event_interruptible(board->wait,
+                                    fmh_gpib_all_bytes_are_sent(e_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");
+               retval = -ERESTARTSYS;
+       }
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+       if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state))
+               retval = -EIO;
+       // disable board's dma
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0);
+       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+
+       dmaengine_terminate_all(e_priv->dma_channel);
+       // make sure fmh_gpib_dma_callback got called
+       if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state))
+               fmh_gpib_dma_callback(board);
+
+       *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) &
+                                  fifo_xfer_counter_mask);
+       if (WARN_ON_ONCE(*bytes_written > length))
+               return -EFAULT;
+       /*      printk("length=%i, *bytes_written=%i, residue=%i, retval=%i\n",
+        *      length, *bytes_written, get_dma_residue(e_priv->dma_channel), retval);
+        */
+cleanup:
+       dma_unmap_single(board->dev, address, length, DMA_TO_DEVICE);
+//     printk("%s: exit, retval=%d\n", __FUNCTION__, retval);
+       return retval;
+}
+
+static int fmh_gpib_accel_write(gpib_board_t *board, uint8_t *buffer,
+                               size_t length, int send_eoi, size_t *bytes_written)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       size_t remainder = length;
+       size_t transfer_size;
+       ssize_t retval = 0;
+       size_t dma_remainder = remainder;
+
+       if (!e_priv->dma_channel) {
+               pr_err("fmh_gpib_gpib: No dma channel available, cannot do accel write.");
+               return -ENXIO;
+       }
+
+       *bytes_written = 0;
+       if (length < 1)
+               return 0;
+
+       smp_mb__before_atomic();
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME
+       smp_mb__after_atomic();
+
+       if (send_eoi)
+               --dma_remainder;
+//     printk("%s: entering while loop\n", __FUNCTION__);
+
+       while (dma_remainder > 0) {
+               size_t num_bytes;
+
+               retval = wait_for_data_out_ready(board);
+               if (retval < 0)
+                       break;
+
+               transfer_size = (e_priv->dma_buffer_size < dma_remainder) ?
+                       e_priv->dma_buffer_size : dma_remainder;
+               retval = fmh_gpib_dma_write(board, buffer, transfer_size, &num_bytes);
+               *bytes_written += num_bytes;
+               if (retval < 0)
+                       break;
+               dma_remainder -= num_bytes;
+               remainder -= num_bytes;
+               buffer += num_bytes;
+               if (need_resched())
+                       schedule();
+       }
+       if (retval < 0)
+               return retval;
+       //handle sending of last byte with eoi
+       if (send_eoi) {
+               size_t num_bytes;
+               //              printk("%s: handling last byte\n", __FUNCTION__);
+               if (WARN_ON_ONCE(remainder != 1))
+                       return -EFAULT;
+
+               /* wait until we are sure we will be able to write the data byte
+                * into the chip before we send AUX_SEOI.  This prevents a timeout
+                * scenerio where we send AUX_SEOI but then timeout without getting
+                * any bytes into the gpib chip.  This will result in the first byte
+                * of the next write having a spurious EOI set on the first byte.
+                */
+               retval = wait_for_data_out_ready(board);
+               if (retval < 0)
+                       return retval;
+
+               write_byte(nec_priv, AUX_SEOI, AUXMR);
+               retval = fmh_gpib_dma_write(board, buffer, remainder, &num_bytes);
+               *bytes_written += num_bytes;
+               if (retval < 0)
+                       return retval;
+               remainder -= num_bytes;
+       }
+//     printk("%s: bytes send=%i\n", __FUNCTION__, (int)(length - remainder));
+       return 0;
+}
+
+static unsigned int fmh_gpib_get_dma_residue(struct dma_chan *chan, dma_cookie_t cookie)
+{
+       struct dma_tx_state state;
+       int result;
+
+       result = dmaengine_pause(chan);
+       if (result < 0) {
+               pr_err("fmh_gpib_gpib: dma pause failed?\n");
+               return -1;
+       }
+       dmaengine_tx_status(chan, cookie, &state);
+       // dma330 hardware doesn't support resume, so dont call this
+       // method unless the dma transfer is done.
+       return state.residue;
+}
+
+static int wait_for_tx_fifo_half_empty(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+//     printk("%s: enter\n", __FUNCTION__);
+
+       if (wait_event_interruptible(board->wait,
+                                    (test_bit(TACS_NUM, &board->status) &&
+                                     (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) &
+                                      TX_FIFO_HALF_EMPTY)) ||
+                                    test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                    test_bit(TIMO_NUM, &board->status)))
+               retval = -ERESTARTSYS;
+
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+//     printk("%s: exit, retval=%i\n", __FUNCTION__, retval);
+       return retval;
+}
+
+/* supports writing a chunk of data whose length must fit into the hardware'd xfer counter,
+ * called in a loop by fmh_gpib_fifo_write()
+ */
+static int fmh_gpib_fifo_write_countable(gpib_board_t *board, uint8_t *buffer,
+                                        size_t length, int send_eoi, size_t *bytes_written)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+       unsigned int remainder;
+
+       *bytes_written = 0;
+//     printk("%s: enter\n", __FUNCTION__);
+       if (WARN_ON_ONCE(length > fifo_xfer_counter_mask))
+               return -EFAULT;
+
+       fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG);
+       fifos_write(e_priv, TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG);
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO);
+
+       remainder = length;
+       while (remainder > 0) {
+               int i;
+
+               fifos_write(e_priv, TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG);
+               retval = wait_for_tx_fifo_half_empty(board);
+               if (retval < 0)
+                       goto cleanup;
+
+               for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && remainder > 0; ++i) {
+                       unsigned int data_value = *buffer;
+
+                       if (send_eoi && remainder == 1)
+                               data_value |= FIFO_DATA_EOI_FLAG;
+                       fifos_write(e_priv, data_value, FIFO_DATA_REG);
+                       ++buffer;
+                       --remainder;
+               }
+       }
+
+       // suspend until last byte is sent
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, HR_DOIE);
+       if (wait_event_interruptible(board->wait,
+                                    fmh_gpib_all_bytes_are_sent(e_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");
+               retval = -ERESTARTSYS;
+       }
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+       if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state))
+               retval = -EIO;
+
+cleanup:
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0);
+       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+
+       *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) &
+                                  fifo_xfer_counter_mask);
+       if (WARN_ON_ONCE(*bytes_written > length))
+               return -EFAULT;
+       /*      printk("length=%i, *bytes_written=%i, residue=%i, retval=%i\n",
+        *      length, *bytes_written, get_dma_residue(e_priv->dma_channel), retval);
+        */
+
+//     printk("%s: exit, retval=%d\n", __FUNCTION__, retval);
+       return retval;
+}
+
+static int fmh_gpib_fifo_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                              int send_eoi, size_t *bytes_written)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       size_t remainder = length;
+       size_t transfer_size;
+       ssize_t retval = 0;
+
+       *bytes_written = 0;
+       if (length < 1)
+               return 0;
+
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME
+
+//     printk("%s: entering while loop\n", __FUNCTION__);
+
+       while (remainder > 0) {
+               size_t num_bytes;
+               int last_pass;
+
+               retval = wait_for_data_out_ready(board);
+               if (retval < 0)
+                       break;
+
+               if (fifo_xfer_counter_mask < remainder) {
+                       // round transfer size to a multiple of half fifo size
+                       transfer_size = (fifo_xfer_counter_mask /
+                                        fmh_gpib_half_fifo_size(e_priv)) *
+                               fmh_gpib_half_fifo_size(e_priv);
+                       last_pass = 0;
+               } else {
+                       transfer_size = remainder;
+                       last_pass = 1;
+               }
+               retval = fmh_gpib_fifo_write_countable(board, buffer, transfer_size,
+                                                      last_pass && send_eoi, &num_bytes);
+               *bytes_written += num_bytes;
+               if (retval < 0)
+                       break;
+               remainder -= num_bytes;
+               buffer += num_bytes;
+               if (need_resched())
+                       schedule();
+       }
+//     printk("%s: bytes send=%i\n", __FUNCTION__, (int)(length - remainder));
+       return retval;
+}
+
+static int fmh_gpib_dma_read(gpib_board_t *board, uint8_t *buffer,
+                            size_t length, int *end, size_t *bytes_read)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+       unsigned long flags;
+       unsigned int residue;
+       int wait_retval;
+       dma_addr_t bus_address;
+       struct dma_async_tx_descriptor *tx_desc;
+       dma_cookie_t dma_cookie;
+
+       //      printk("%s: enter, bus_address=0x%x, length=%i\n", __FUNCTION__,
+       //(unsigned)bus_address,
+//                (int)length);
+
+       *bytes_read = 0;
+       *end = 0;
+       if (length == 0)
+               return 0;
+
+       bus_address = dma_map_single(board->dev, e_priv->dma_buffer,
+                                    length, DMA_FROM_DEVICE);
+       if (dma_mapping_error(board->dev, bus_address))
+               pr_err("dma mapping error in dma read!");
+
+       /* program dma controller */
+       retval = fmh_gpib_config_dma(board, 0);
+       if (retval) {
+               dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE);
+               return retval;
+       }
+       tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, bus_address,
+                                             length, DMA_DEV_TO_MEM,
+                                             DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+       if (!tx_desc)  {
+               pr_err("fmh_gpib_gpib: failed to allocate dma transmit descriptor\n");
+               dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE);
+               return -EIO;
+       }
+       tx_desc->callback = fmh_gpib_dma_callback;
+       tx_desc->callback_param = board;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       // enable nec7210 dma
+       fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG);
+       fifos_write(e_priv, RX_FIFO_DMA_REQUEST_ENABLE | RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG);
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI);
+
+       dma_cookie = dmaengine_submit(tx_desc);
+       dma_async_issue_pending(e_priv->dma_channel);
+
+       set_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state);
+
+       spin_unlock_irqrestore(&board->spinlock, flags);
+//     printk("waiting for data transfer.\n");
+       // wait for data to transfer
+       wait_retval = wait_event_interruptible(board->wait,
+                                              test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state)
+                                              == 0 ||
+                                              test_bit(RECEIVED_END_BN, &nec_priv->state) ||
+                                              test_bit(DEV_CLEAR_BN, &nec_priv->state) ||
+                                              test_bit(TIMO_NUM, &board->status));
+       if (wait_retval) {
+               pr_warn("fmh_gpib: dma read wait interrupted\n");
+               retval = -ERESTARTSYS;
+       }
+       if (test_bit(TIMO_NUM, &board->status))
+               retval = -ETIMEDOUT;
+       if (test_bit(DEV_CLEAR_BN, &nec_priv->state))
+               retval = -EINTR;
+       // stop the dma transfer
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0);
+       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+       // give time for pl330 to transfer any in-flight data, since
+       // pl330 will throw it away when dmaengine_pause is called.
+       usleep_range(10, 15);
+       residue = fmh_gpib_get_dma_residue(e_priv->dma_channel, dma_cookie);
+       if (WARN_ON_ONCE(residue > length || residue < 0))
+               return -EFAULT;
+       *bytes_read += length - residue;
+       dmaengine_terminate_all(e_priv->dma_channel);
+       // make sure fmh_gpib_dma_callback got called
+       if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state))
+               fmh_gpib_dma_callback(board);
+
+       dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE);
+       memcpy(buffer, e_priv->dma_buffer, *bytes_read);
+
+       /* Manually read any dregs out of fifo. */
+       while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) {
+               if ((*bytes_read) >= length) {
+                       dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding!  bytes_read=%d length=%d residue=%d\n",
+                               (int)(*bytes_read), (int)length, (int)residue);
+                       break;
+               }
+               buffer[(*bytes_read)++] = fifos_read(e_priv, FIFO_DATA_REG) & fifo_data_mask;
+       }
+
+       /* If we got an end interrupt, figure out if it was
+        * associated with the last byte we dma'd or with a
+        * byte still sitting on the cb7210.
+        */
+       spin_lock_irqsave(&board->spinlock, flags);
+       if (*bytes_read > 0 && test_bit(READ_READY_BN, &nec_priv->state) == 0) {
+               // If there is no byte sitting on the cb7210 and we
+               // saw an end, we need to deal with it now
+               if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state))
+                       *end = 1;
+       }
+       spin_unlock_irqrestore(&board->spinlock, flags);
+//     printk("\tbytes_read=%i, residue=%i, end=%i, retval=%i, wait_retval=%i\n",
+//                *bytes_read, residue, *end, retval, wait_retval);
+
+       return retval;
+}
+
+static void fmh_gpib_release_rfd_holdoff(gpib_board_t *board, struct fmh_priv *e_priv)
+{
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       unsigned int ext_status_1;
+       unsigned long flags;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+
+       ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG);
+
+       /* if there is an end byte sitting on the chip, don't release
+        * holdoff.  We want it left set after we read out the end
+        * byte.
+        */
+       if ((ext_status_1 & (DATA_IN_STATUS_BIT | END_STATUS_BIT)) !=
+           (DATA_IN_STATUS_BIT | END_STATUS_BIT))      {
+               if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT)
+                       write_byte(nec_priv, AUX_FH, AUXMR);
+
+               /* Check if an end byte raced in before we executed the AUX_FH command.
+                * If it did, we want to make sure the rfd holdoff is in effect.  The end
+                * byte can arrive since
+                * AUX_RFD_HOLDOFF_ASAP doesn't immediately force the acceptor handshake
+                * to leave ACRS.
+                */
+               if ((read_byte(nec_priv, EXT_STATUS_1_REG) &
+                    (RFD_HOLDOFF_STATUS_BIT | DATA_IN_STATUS_BIT | END_STATUS_BIT)) ==
+                   (DATA_IN_STATUS_BIT | END_STATUS_BIT)) {
+                       write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR);
+                       set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+               } else {
+                       clear_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+               }
+       }
+       spin_unlock_irqrestore(&board->spinlock, flags);
+}
+
+static int fmh_gpib_accel_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                              int *end, size_t *bytes_read)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       size_t remain = length;
+       size_t transfer_size;
+       int retval = 0;
+       size_t dma_nbytes;
+       unsigned long flags;
+
+       smp_mb__before_atomic();
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME
+       smp_mb__after_atomic();
+       *end = 0;
+       *bytes_read = 0;
+
+       retval = wait_for_read(board);
+       if (retval < 0)
+               return retval;
+
+       fmh_gpib_release_rfd_holdoff(board, e_priv);
+       while (remain > 0) {
+               transfer_size = (e_priv->dma_buffer_size < remain) ?
+                       e_priv->dma_buffer_size : remain;
+               retval = fmh_gpib_dma_read(board, buffer, transfer_size, end, &dma_nbytes);
+               remain -= dma_nbytes;
+               buffer += dma_nbytes;
+               *bytes_read += dma_nbytes;
+               if (*end)
+                       break;
+               if (retval < 0)
+                       break;
+               if (need_resched())
+                       schedule();
+       }
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       if (test_bit(RFD_HOLDOFF_BN, &nec_priv->state) == 0) {
+               write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR);
+               set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+       }
+       spin_unlock_irqrestore(&board->spinlock, flags);
+
+       return retval;
+}
+
+/* Read a chunk of data whose length is within the limits of the hardware's
+ * xfer counter.  Called in a loop from fmh_gpib_fifo_read().
+ */
+static int fmh_gpib_fifo_read_countable(gpib_board_t *board, uint8_t *buffer,
+                                       size_t length, int *end, size_t *bytes_read)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       int retval = 0;
+       unsigned int residue;
+
+       //      printk("%s: enter, bus_address=0x%x, length=%i\n", __FUNCTION__,
+       // (unsigned)bus_address,
+//                (int)length);
+
+       *bytes_read = 0;
+       *end = 0;
+       if (length == 0)
+               return 0;
+
+       fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG);
+       fifos_write(e_priv, RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG);
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0);
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI);
+
+       while (*bytes_read < length && *end == 0) {
+               int i;
+
+               fifos_write(e_priv, RX_FIFO_HALF_FULL_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG);
+               retval = wait_for_rx_fifo_half_full_or_end(board);
+               if (retval < 0)
+                       goto cleanup;
+
+               for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && *end == 0; ++i) {
+                       unsigned int data_value;
+
+                       data_value = fifos_read(e_priv, FIFO_DATA_REG);
+                       buffer[(*bytes_read)++] = data_value & fifo_data_mask;
+                       if (data_value & FIFO_DATA_EOI_FLAG)
+                               *end = 1;
+               }
+       }
+
+cleanup:
+       // stop the transfer
+       nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0);
+       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+
+       /* Manually read any dregs out of fifo. */
+       while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) {
+               unsigned int data_value;
+
+               if ((*bytes_read) >= length) {
+                       dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding!  bytes_read=%d length=%d residue=%d\n",
+                               (int)(*bytes_read), (int)length, (int)residue);
+                       break;
+               }
+               data_value = fifos_read(e_priv, FIFO_DATA_REG);
+               buffer[(*bytes_read)++] = data_value & fifo_data_mask;
+               if (data_value & FIFO_DATA_EOI_FLAG)
+                       *end = 1;
+       }
+
+//     printk("\tbytes_read=%i, residue=%i, end=%i, retval=%i, wait_retval=%i\n",
+//                *bytes_read, residue, *end, retval, wait_retval);
+
+       return retval;
+}
+
+static int fmh_gpib_fifo_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                             int *end, size_t *bytes_read)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       size_t remain = length;
+       size_t transfer_size;
+       int retval = 0;
+       size_t nbytes;
+       unsigned long flags;
+
+       clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME
+       *end = 0;
+       *bytes_read = 0;
+
+       /* Do a little prep with data in interrupt so that following wait_for_read()
+        * will wake up if a data byte is received.
+        */
+       nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, HR_DIIE);
+       fmh_gpib_interrupt(0, board);
+
+       retval = wait_for_read(board);
+       if (retval < 0)
+               return retval;
+
+       fmh_gpib_release_rfd_holdoff(board, e_priv);
+       while (remain > 0) {
+               if (fifo_xfer_counter_mask < remain) {
+                       // round transfer size to a multiple of half fifo size
+                       transfer_size = (fifo_xfer_counter_mask /
+                                        fmh_gpib_half_fifo_size(e_priv)) *
+                               fmh_gpib_half_fifo_size(e_priv);
+               } else {
+                       transfer_size = remain;
+               }
+               retval = fmh_gpib_fifo_read_countable(board, buffer, transfer_size, end, &nbytes);
+               remain -= nbytes;
+               buffer += nbytes;
+               *bytes_read += nbytes;
+               if (*end)
+                       break;
+               if (retval < 0)
+                       break;
+               if (need_resched())
+                       schedule();
+       }
+
+       if (*end == 0)  {
+               spin_lock_irqsave(&board->spinlock, flags);
+               write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR);
+               set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+               spin_unlock_irqrestore(&board->spinlock, flags);
+       }
+
+       return retval;
+}
+
+gpib_interface_t fmh_gpib_unaccel_interface = {
+name: "fmh_gpib_unaccel",
+attach : fmh_gpib_attach_holdoff_all,
+detach : fmh_gpib_detach,
+read : fmh_gpib_read,
+write : fmh_gpib_write,
+command : fmh_gpib_command,
+take_control : fmh_gpib_take_control,
+go_to_standby : fmh_gpib_go_to_standby,
+request_system_control : fmh_gpib_request_system_control,
+interface_clear : fmh_gpib_interface_clear,
+remote_enable : fmh_gpib_remote_enable,
+enable_eos : fmh_gpib_enable_eos,
+disable_eos : fmh_gpib_disable_eos,
+parallel_poll : fmh_gpib_parallel_poll,
+parallel_poll_configure : fmh_gpib_parallel_poll_configure,
+parallel_poll_response : fmh_gpib_parallel_poll_response,
+local_parallel_poll_mode : fmh_gpib_local_parallel_poll_mode,
+line_status : fmh_gpib_line_status,
+update_status : fmh_gpib_update_status,
+primary_address : fmh_gpib_primary_address,
+secondary_address : fmh_gpib_secondary_address,
+serial_poll_response2 : fmh_gpib_serial_poll_response2,
+serial_poll_status : fmh_gpib_serial_poll_status,
+t1_delay : fmh_gpib_t1_delay,
+return_to_local : fmh_gpib_return_to_local,
+};
+
+gpib_interface_t fmh_gpib_interface = {
+name: "fmh_gpib",
+attach : fmh_gpib_attach_holdoff_end,
+detach : fmh_gpib_detach,
+read : fmh_gpib_accel_read,
+write : fmh_gpib_accel_write,
+command : fmh_gpib_command,
+take_control : fmh_gpib_take_control,
+go_to_standby : fmh_gpib_go_to_standby,
+request_system_control : fmh_gpib_request_system_control,
+interface_clear : fmh_gpib_interface_clear,
+remote_enable : fmh_gpib_remote_enable,
+enable_eos : fmh_gpib_enable_eos,
+disable_eos : fmh_gpib_disable_eos,
+parallel_poll : fmh_gpib_parallel_poll,
+parallel_poll_configure : fmh_gpib_parallel_poll_configure,
+parallel_poll_response : fmh_gpib_parallel_poll_response,
+local_parallel_poll_mode : fmh_gpib_local_parallel_poll_mode,
+line_status : fmh_gpib_line_status,
+update_status : fmh_gpib_update_status,
+primary_address : fmh_gpib_primary_address,
+secondary_address : fmh_gpib_secondary_address,
+serial_poll_response2 : fmh_gpib_serial_poll_response2,
+serial_poll_status : fmh_gpib_serial_poll_status,
+t1_delay : fmh_gpib_t1_delay,
+return_to_local : fmh_gpib_return_to_local,
+};
+
+gpib_interface_t fmh_gpib_pci_interface = {
+name: "fmh_gpib_pci",
+attach : fmh_gpib_pci_attach_holdoff_end,
+detach : fmh_gpib_pci_detach,
+read : fmh_gpib_fifo_read,
+write : fmh_gpib_fifo_write,
+command : fmh_gpib_command,
+take_control : fmh_gpib_take_control,
+go_to_standby : fmh_gpib_go_to_standby,
+request_system_control : fmh_gpib_request_system_control,
+interface_clear : fmh_gpib_interface_clear,
+remote_enable : fmh_gpib_remote_enable,
+enable_eos : fmh_gpib_enable_eos,
+disable_eos : fmh_gpib_disable_eos,
+parallel_poll : fmh_gpib_parallel_poll,
+parallel_poll_configure : fmh_gpib_parallel_poll_configure,
+parallel_poll_response : fmh_gpib_parallel_poll_response,
+local_parallel_poll_mode : fmh_gpib_local_parallel_poll_mode,
+line_status : fmh_gpib_line_status,
+update_status : fmh_gpib_update_status,
+primary_address : fmh_gpib_primary_address,
+secondary_address : fmh_gpib_secondary_address,
+serial_poll_response2 : fmh_gpib_serial_poll_response2,
+serial_poll_status : fmh_gpib_serial_poll_status,
+t1_delay : fmh_gpib_t1_delay,
+return_to_local : fmh_gpib_return_to_local,
+};
+
+gpib_interface_t fmh_gpib_pci_unaccel_interface = {
+name: "fmh_gpib_pci_unaccel",
+attach : fmh_gpib_pci_attach_holdoff_all,
+detach : fmh_gpib_pci_detach,
+read : fmh_gpib_read,
+write : fmh_gpib_write,
+command : fmh_gpib_command,
+take_control : fmh_gpib_take_control,
+go_to_standby : fmh_gpib_go_to_standby,
+request_system_control : fmh_gpib_request_system_control,
+interface_clear : fmh_gpib_interface_clear,
+remote_enable : fmh_gpib_remote_enable,
+enable_eos : fmh_gpib_enable_eos,
+disable_eos : fmh_gpib_disable_eos,
+parallel_poll : fmh_gpib_parallel_poll,
+parallel_poll_configure : fmh_gpib_parallel_poll_configure,
+parallel_poll_response : fmh_gpib_parallel_poll_response,
+local_parallel_poll_mode : fmh_gpib_local_parallel_poll_mode,
+line_status : fmh_gpib_line_status,
+update_status : fmh_gpib_update_status,
+primary_address : fmh_gpib_primary_address,
+secondary_address : fmh_gpib_secondary_address,
+serial_poll_response2 : fmh_gpib_serial_poll_response2,
+serial_poll_status : fmh_gpib_serial_poll_status,
+t1_delay : fmh_gpib_t1_delay,
+return_to_local : fmh_gpib_return_to_local,
+};
+
+irqreturn_t fmh_gpib_internal_interrupt(gpib_board_t *board)
+{
+       unsigned int status0, status1, status2, ext_status_1, fifo_status;
+       struct fmh_priv *priv = board->private_data;
+       struct nec7210_priv *nec_priv = &priv->nec7210_priv;
+       int retval = IRQ_NONE;
+
+       status0 = read_byte(nec_priv, ISR0_IMR0_REG);
+       status1 = read_byte(nec_priv, ISR1);
+       status2 = read_byte(nec_priv, ISR2);
+       fifo_status = fifos_read(priv, FIFO_CONTROL_STATUS_REG);
+
+       if (status0 & IFC_INTERRUPT_BIT) {
+               push_gpib_event(board, EventIFC);
+               retval = IRQ_HANDLED;
+       }
+
+       if (nec7210_interrupt_have_status(board, nec_priv, status1, status2) == IRQ_HANDLED)
+               retval = IRQ_HANDLED;
+
+       ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG);
+
+       if (ext_status_1 & DATA_IN_STATUS_BIT)
+               set_bit(READ_READY_BN, &nec_priv->state);
+       else
+               clear_bit(READ_READY_BN, &nec_priv->state);
+
+       if (ext_status_1 & DATA_OUT_STATUS_BIT)
+               set_bit(WRITE_READY_BN, &nec_priv->state);
+       else
+               clear_bit(WRITE_READY_BN, &nec_priv->state);
+
+       if (ext_status_1 & COMMAND_OUT_STATUS_BIT)
+               set_bit(COMMAND_READY_BN, &nec_priv->state);
+       else
+               clear_bit(COMMAND_READY_BN, &nec_priv->state);
+
+       if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT)
+               set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+       else
+               clear_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+
+       if (ext_status_1 & END_STATUS_BIT) {
+               /* only set RECEIVED_END while there is still a data
+                * byte sitting in the chip, to avoid spuriously
+                * setting it multiple times after it has been cleared
+                * during a read.
+                */
+               if (ext_status_1 & DATA_IN_STATUS_BIT)
+                       set_bit(RECEIVED_END_BN, &nec_priv->state);
+       } else {
+               clear_bit(RECEIVED_END_BN, &nec_priv->state);
+       }
+
+       if ((fifo_status & TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED) &&
+           (fifo_status & TX_FIFO_HALF_EMPTY)) {
+               /* We really only want to clear the
+                * TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE bit in the
+                * FIFO_CONTROL_STATUS_REG.  Since we are not being
+                * careful, this also has a side effect of disabling
+                * DMA requests and the RX fifo interrupt.  That is
+                * fine though, since they should never be in use at
+                * the same time as the TX fifo interrupt.
+                */
+               fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG);
+               retval = IRQ_HANDLED;
+       }
+
+       if ((fifo_status & RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED) &&
+           (fifo_status & RX_FIFO_HALF_FULL)) {
+               /* We really only want to clear the
+                * RX_FIFO_HALF_FULL_INTERRUPT_ENABLE bit in the
+                * FIFO_CONTROL_STATUS_REG.  Since we are not being
+                * careful, this also has a side effect of disabling
+                * DMA requests and the TX fifo interrupt.  That is
+                * fine though, since they should never be in use at
+                * the same time as the RX fifo interrupt.
+                */
+               fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG);
+               retval = IRQ_HANDLED;
+       }
+
+       if (retval == IRQ_HANDLED)
+               wake_up_interruptible(&board->wait);
+
+       return retval;
+}
+
+irqreturn_t fmh_gpib_interrupt(int irq, void *arg)
+{
+       gpib_board_t *board = arg;
+       unsigned long flags;
+       irqreturn_t retval;
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       retval = fmh_gpib_internal_interrupt(board);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+       return retval;
+}
+
+static int fmh_gpib_allocate_private(gpib_board_t *board)
+{
+       struct fmh_priv *priv;
+
+       board->private_data = kmalloc(sizeof(struct fmh_priv), GFP_KERNEL);
+       if (!board->private_data)
+               return -ENOMEM;
+       priv = board->private_data;
+       memset(priv, 0, sizeof(struct fmh_priv));
+       init_nec7210_private(&priv->nec7210_priv);
+       priv->dma_buffer_size = 0x800;
+       priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL);
+       if (!priv->dma_buffer)
+               return -ENOMEM;
+       return 0;
+}
+
+static void fmh_gpib_generic_detach(gpib_board_t *board)
+{
+       if (board->private_data) {
+               struct fmh_priv *e_priv = board->private_data;
+
+               kfree(e_priv->dma_buffer);
+               kfree(board->private_data);
+               board->private_data = NULL;
+       }
+       if (board->dev)
+               dev_set_drvdata(board->dev, NULL);
+}
+
+// generic part of attach functions
+static int fmh_gpib_generic_attach(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv;
+       struct nec7210_priv *nec_priv;
+       int retval;
+
+       board->status = 0;
+
+       retval = fmh_gpib_allocate_private(board);
+       if (retval < 0)
+               return retval;
+       e_priv = board->private_data;
+       nec_priv = &e_priv->nec7210_priv;
+       nec_priv->read_byte = gpib_cs_read_byte;
+       nec_priv->write_byte = gpib_cs_write_byte;
+       nec_priv->offset = 1;
+       nec_priv->type = CB7210;
+       return 0;
+}
+
+static int fmh_gpib_config_dma(gpib_board_t *board, int output)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct dma_slave_config config;
+
+       config.device_fc = true;
+
+       if (e_priv->dma_burst_length < 1) {
+               config.src_maxburst = 1;
+               config.dst_maxburst = 1;
+       } else {
+               config.src_maxburst = e_priv->dma_burst_length;
+               config.dst_maxburst = e_priv->dma_burst_length;
+       }
+
+       config.src_addr_width = 1;
+       config.dst_addr_width = 1;
+
+       if (output) {
+               config.direction = DMA_MEM_TO_DEV;
+               config.src_addr = 0;
+               config.dst_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset;
+       } else {
+               config.direction = DMA_DEV_TO_MEM;
+               config.src_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset;
+               config.dst_addr = 0;
+       }
+       return dmaengine_slave_config(e_priv->dma_channel, &config);
+}
+
+static int fmh_gpib_init(struct fmh_priv *e_priv, gpib_board_t *board, int handshake_mode)
+{
+       struct nec7210_priv *nec_priv = &e_priv->nec7210_priv;
+       unsigned long flags;
+       unsigned int fifo_status_bits;
+
+       fifos_write(e_priv, RX_FIFO_CLEAR | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG);
+
+       nec7210_board_reset(nec_priv, board);
+       write_byte(nec_priv, AUX_LO_SPEED, AUXMR);
+       nec7210_set_handshake_mode(board, nec_priv, handshake_mode);
+
+       /* Hueristically check if hardware supports fifo half full/empty interrupts */
+       fifo_status_bits = fifos_read(e_priv, FIFO_CONTROL_STATUS_REG);
+       e_priv->supports_fifo_interrupts = (fifo_status_bits & TX_FIFO_EMPTY) &&
+               (fifo_status_bits & TX_FIFO_HALF_EMPTY);
+
+       nec7210_board_online(nec_priv, board);
+
+       write_byte(nec_priv, IFC_INTERRUPT_ENABLE_BIT | ATN_INTERRUPT_ENABLE_BIT, ISR0_IMR0_REG);
+
+       spin_lock_irqsave(&board->spinlock, flags);
+       write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR);
+       set_bit(RFD_HOLDOFF_BN, &nec_priv->state);
+       spin_unlock_irqrestore(&board->spinlock, flags);
+       return 0;
+}
+
+/* Match callback for driver_find_device */
+static int fmh_gpib_device_match(struct device *dev, const void *data)
+{
+       const gpib_board_config_t *config = data;
+
+       if (dev_get_drvdata(dev))
+               return 0;
+
+       if (gpib_match_device_path(dev, config->device_path) == 0)
+               return 0;
+
+       // driver doesn't support selection by serial number
+       if (config->serial_number)
+               return 0;
+
+       dev_notice(dev, "matched: %s\n", of_node_full_name(dev_of_node((dev))));
+       return 1;
+}
+
+static int fmh_gpib_attach_impl(gpib_board_t *board, const gpib_board_config_t *config,
+                               unsigned int handshake_mode, int acquire_dma)
+{
+       struct fmh_priv *e_priv;
+       struct nec7210_priv *nec_priv;
+       int retval;
+       int irq;
+       struct resource *res;
+       struct platform_device *pdev;
+
+       board->dev = driver_find_device(&fmh_gpib_platform_driver.driver,
+                                       NULL, (const void *)config, &fmh_gpib_device_match);
+       if (!board->dev)        {
+               pr_err("No matching fmh_gpib_core device was found, attach failed.");
+               return -ENODEV;
+       }
+       // currently only used to mark the device as already attached
+       dev_set_drvdata(board->dev, board);
+       pdev = to_platform_device(board->dev);
+
+       retval = fmh_gpib_generic_attach(board);
+       if (retval)
+               return retval;
+
+       e_priv = board->private_data;
+       nec_priv = &e_priv->nec7210_priv;
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpib_control_status");
+       if (!res) {
+               dev_err(board->dev, "Unable to locate mmio resource for cb7210 gpib\n");
+               return -ENODEV;
+       }
+
+       if (request_mem_region(res->start,
+                              resource_size(res),
+                              pdev->name) == NULL) {
+               dev_err(board->dev, "cannot claim registers\n");
+               return -ENXIO;
+       }
+       e_priv->gpib_iomem_res = res;
+
+       nec_priv->iobase = ioremap(e_priv->gpib_iomem_res->start,
+                                  resource_size(e_priv->gpib_iomem_res));
+       if (!nec_priv->iobase) {
+               dev_err(board->dev, "Could not map I/O memory for gpib\n");
+               return -ENOMEM;
+       }
+       dev_info(board->dev, "iobase 0x%lx remapped to %p, length=%ld\n",
+                (unsigned long)e_priv->gpib_iomem_res->start,
+                nec_priv->iobase, (unsigned long)resource_size(e_priv->gpib_iomem_res));
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma_fifos");
+       if (!res) {
+               dev_err(board->dev, "Unable to locate mmio resource for gpib dma port\n");
+               return -ENODEV;
+       }
+       if (request_mem_region(res->start,
+                              resource_size(res),
+                              pdev->name) == NULL) {
+               dev_err(board->dev, "cannot claim registers\n");
+               return -ENXIO;
+       }
+       e_priv->dma_port_res = res;
+       e_priv->fifo_base = ioremap(e_priv->dma_port_res->start,
+                                   resource_size(e_priv->dma_port_res));
+       if (!e_priv->fifo_base) {
+               dev_err(board->dev, "Could not map I/O memory for fifos\n");
+               return -ENOMEM;
+       }
+       dev_info(board->dev, "dma fifos 0x%lx remapped to %p, length=%ld\n",
+                (unsigned long)e_priv->dma_port_res->start, e_priv->fifo_base,
+                (unsigned long)resource_size(e_priv->dma_port_res));
+
+       irq = platform_get_irq(pdev, 0);
+       pr_info("gpib: irq %d\n", irq);
+       if (irq < 0) {
+               dev_err(board->dev, "fmh_gpib_gpib: request for IRQ failed\n");
+               return -EBUSY;
+       }
+       retval = request_irq(irq, fmh_gpib_interrupt, IRQF_SHARED, pdev->name, board);
+       if (retval) {
+               dev_err(board->dev,
+                       "cannot register interrupt handler err=%d\n",
+                       retval);
+               return retval;
+       }
+       e_priv->irq = irq;
+
+       if (acquire_dma) {
+               e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx");
+               if (!e_priv->dma_channel) {
+                       dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n");
+                       return -EIO;
+               }
+       }
+       /* in the future we might want to know the half-fifo size
+        * (dma_burst_length) even when not using dma, so go ahead an
+        * initialize it unconditionally.
+        */
+       e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) &
+               fifo_max_burst_length_mask;
+
+       return fmh_gpib_init(e_priv, board, handshake_mode);
+}
+
+int fmh_gpib_attach_holdoff_all(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return fmh_gpib_attach_impl(board, config, HR_HLDA, 0);
+}
+
+int fmh_gpib_attach_holdoff_end(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return fmh_gpib_attach_impl(board, config, HR_HLDE, 1);
+}
+
+void fmh_gpib_detach(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv;
+
+       if (e_priv) {
+               if (e_priv->dma_channel)
+                       dma_release_channel(e_priv->dma_channel);
+               nec_priv = &e_priv->nec7210_priv;
+
+               if (e_priv->irq)
+                       free_irq(e_priv->irq, board);
+               if (e_priv->fifo_base)
+                       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+               if (nec_priv->iobase) {
+                       write_byte(nec_priv, 0, ISR0_IMR0_REG);
+                       nec7210_board_reset(nec_priv, board);
+               }
+               if (e_priv->fifo_base)
+                       iounmap(e_priv->fifo_base);
+               if (nec_priv->iobase)
+                       iounmap(nec_priv->iobase);
+               if (e_priv->dma_port_res) {
+                       release_mem_region(e_priv->dma_port_res->start,
+                                          resource_size(e_priv->dma_port_res));
+               }
+               if (e_priv->gpib_iomem_res)
+                       release_mem_region(e_priv->gpib_iomem_res->start,
+                                          resource_size(e_priv->gpib_iomem_res));
+       }
+       fmh_gpib_generic_detach(board);
+}
+
+static int fmh_gpib_pci_attach_impl(gpib_board_t *board, const gpib_board_config_t *config,
+                                   unsigned int handshake_mode)
+{
+       struct fmh_priv *e_priv;
+       struct nec7210_priv *nec_priv;
+       int retval;
+       struct pci_dev *pci_device;
+
+       retval = fmh_gpib_generic_attach(board);
+       if (retval)
+               return retval;
+
+       e_priv = board->private_data;
+       nec_priv = &e_priv->nec7210_priv;
+
+       // find board
+       pci_device = gpib_pci_get_device(config, BOGUS_PCI_VENDOR_ID_FLUKE,
+                                        BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, NULL);
+       if (!pci_device)        {
+               pr_err("No matching fmh_gpib_core pci device was found, attach failed.");
+               return -ENODEV;
+       }
+       board->dev = &pci_device->dev;
+
+       // bladerunner prototype has offset of 4 between gpib control/status registers
+       nec_priv->offset = 4;
+
+       if (pci_enable_device(pci_device)) {
+               dev_err(board->dev, "error enabling pci device\n");
+               return -EIO;
+       }
+       if (pci_request_regions(pci_device, KBUILD_MODNAME)) {
+               dev_err(board->dev, "pci_request_regions failed\n");
+               return -EIO;
+       }
+       e_priv->gpib_iomem_res = &pci_device->resource[gpib_control_status_pci_resource_index];
+       e_priv->dma_port_res =  &pci_device->resource[gpib_fifo_pci_resource_index];
+
+       nec_priv->iobase = ioremap(pci_resource_start(pci_device,
+                                                     gpib_control_status_pci_resource_index),
+                                  pci_resource_len(pci_device,
+                                                   gpib_control_status_pci_resource_index));
+       dev_info(board->dev, "base address for gpib control/status registers remapped to 0x%p\n",
+                nec_priv->iobase);
+
+       if (e_priv->dma_port_res->flags & IORESOURCE_MEM) {
+               e_priv->fifo_base = ioremap(pci_resource_start(pci_device,
+                                                              gpib_fifo_pci_resource_index),
+                                           pci_resource_len(pci_device,
+                                                            gpib_fifo_pci_resource_index));
+               dev_info(board->dev, "base address for gpib fifo registers remapped to 0x%p\n",
+                        e_priv->fifo_base);
+       } else {
+               e_priv->fifo_base = NULL;
+               dev_info(board->dev, "hardware has no gpib fifo registers.\n");
+       }
+
+       if (pci_device->irq) {
+               retval = request_irq(pci_device->irq, fmh_gpib_interrupt, IRQF_SHARED,
+                                    KBUILD_MODNAME, board);
+               if (retval) {
+                       dev_err(board->dev,
+                               "cannot register interrupt handler err=%d\n",
+                               retval);
+                       return retval;
+               }
+       }
+       e_priv->irq = pci_device->irq;
+
+       e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) &
+               fifo_max_burst_length_mask;
+
+       return fmh_gpib_init(e_priv, board, handshake_mode);
+}
+
+int fmh_gpib_pci_attach_holdoff_all(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       return fmh_gpib_pci_attach_impl(board, config, HR_HLDA);
+}
+
+int fmh_gpib_pci_attach_holdoff_end(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       int retval;
+       struct fmh_priv *e_priv;
+
+       retval = fmh_gpib_pci_attach_impl(board, config, HR_HLDE);
+       e_priv = board->private_data;
+       if (retval == 0 && e_priv && e_priv->supports_fifo_interrupts == 0) {
+               pr_err("fmh_gpib: your fmh_gpib_core does not appear to support fifo interrupts.  Try the fmh_gpib_pci_unaccel board type instead.");
+               return -EIO;
+       }
+       return retval;
+}
+
+void fmh_gpib_pci_detach(gpib_board_t *board)
+{
+       struct fmh_priv *e_priv = board->private_data;
+       struct nec7210_priv *nec_priv;
+
+       if (e_priv)     {
+               nec_priv = &e_priv->nec7210_priv;
+
+               if (e_priv->irq)
+                       free_irq(e_priv->irq, board);
+               if (e_priv->fifo_base)
+                       fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG);
+               if (nec_priv->iobase) {
+                       write_byte(nec_priv, 0, ISR0_IMR0_REG);
+                       nec7210_board_reset(nec_priv, board);
+               }
+               if (e_priv->fifo_base)
+                       iounmap(e_priv->fifo_base);
+               if (nec_priv->iobase)
+                       iounmap(nec_priv->iobase);
+               if (e_priv->dma_port_res || e_priv->gpib_iomem_res)
+                       pci_release_regions(to_pci_dev(board->dev));
+               if (board->dev)
+                       pci_dev_put(to_pci_dev(board->dev));
+       }
+       fmh_gpib_generic_detach(board);
+}
+
+static int fmh_gpib_platform_probe(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static const struct of_device_id fmh_gpib_of_match[] = {
+       { .compatible = "fmhess,fmh_gpib_core"},
+       { {0} }
+};
+MODULE_DEVICE_TABLE(of, fmh_gpib_of_match);
+
+static struct platform_driver fmh_gpib_platform_driver = {
+       .driver = {
+               .name = "fmh_gpib",
+               .owner = THIS_MODULE,
+               .of_match_table = fmh_gpib_of_match,
+       },
+       .probe = &fmh_gpib_platform_probe
+};
+
+static int fmh_gpib_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+       return 0;
+}
+
+static const struct pci_device_id fmh_gpib_pci_match[] = {
+       { BOGUS_PCI_VENDOR_ID_FLUKE, BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, 0, 0, 0 },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, fmh_gpib_pci_match);
+
+static struct pci_driver fmh_gpib_pci_driver = {
+       .name = "fmh_gpib",
+       .id_table = fmh_gpib_pci_match,
+       .probe = &fmh_gpib_pci_probe
+};
+
+static int __init fmh_gpib_init_module(void)
+{
+       int result;
+
+       result = platform_driver_register(&fmh_gpib_platform_driver);
+       if (result) {
+               pr_err("fmh_gpib: platform_driver_register failed!\n");
+               return result;
+       }
+
+       result = pci_register_driver(&fmh_gpib_pci_driver);
+       if (result) {
+               pr_err("fmh_gpib: pci_driver_register failed!\n");
+               return result;
+       }
+
+       gpib_register_driver(&fmh_gpib_unaccel_interface, THIS_MODULE);
+       gpib_register_driver(&fmh_gpib_interface, THIS_MODULE);
+       gpib_register_driver(&fmh_gpib_pci_unaccel_interface, THIS_MODULE);
+       gpib_register_driver(&fmh_gpib_pci_interface, THIS_MODULE);
+
+       pr_info("fmh_gpib\n");
+       return 0;
+}
+
+static void __exit fmh_gpib_exit_module(void)
+{
+       gpib_unregister_driver(&fmh_gpib_pci_interface);
+       gpib_unregister_driver(&fmh_gpib_pci_unaccel_interface);
+       gpib_unregister_driver(&fmh_gpib_unaccel_interface);
+       gpib_unregister_driver(&fmh_gpib_interface);
+
+       pci_unregister_driver(&fmh_gpib_pci_driver);
+       platform_driver_unregister(&fmh_gpib_platform_driver);
+}
+
+module_init(fmh_gpib_init_module);
+module_exit(fmh_gpib_exit_module);
diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.h b/drivers/staging/gpib/fmh_gpib/fmh_gpib.h
new file mode 100644 (file)
index 0000000..43bfc89
--- /dev/null
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/***************************************************************************
+ *    Author: Frank Mori Hess <fmh6jj@gmail.com>
+ *   Copyright: (C) 2006, 2010, 2015 Fluke Corporation
+ *     (C) 2017 Frank Mori Hess
+ ***************************************************************************/
+
+#include <linux/dmaengine.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include "nec7210.h"
+
+static const int fifo_reg_offset = 2;
+
+static const int gpib_control_status_pci_resource_index;
+static const int gpib_fifo_pci_resource_index = 1;
+
+/* We don't have a real pci vendor/device id, the following will need to be
+ * patched to match prototype hardware.
+ */
+#define BOGUS_PCI_VENDOR_ID_FLUKE 0xffff
+#define BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER 0x0
+
+struct fmh_priv {
+       struct nec7210_priv nec7210_priv;
+       struct resource *gpib_iomem_res;
+       struct resource *write_transfer_counter_res;
+       struct resource *dma_port_res;
+       int irq;
+       struct dma_chan *dma_channel;
+       u8 *dma_buffer;
+       int dma_buffer_size;
+       int dma_burst_length;
+       void *fifo_base;
+       unsigned supports_fifo_interrupts : 1;
+};
+
+static inline int fmh_gpib_half_fifo_size(struct fmh_priv *priv)
+{
+       return priv->dma_burst_length;
+}
+
+// registers beyond the nec7210 register set
+enum fmh_gpib_regs {
+       EXT_STATUS_1_REG = 0x9,
+       STATE1_REG = 0xc,
+       ISR0_IMR0_REG = 0xe,
+       BUS_STATUS_REG = 0xf
+};
+
+/* IMR0 -- Interrupt Mode Register 0 */
+enum imr0_bits {
+       ATN_INTERRUPT_ENABLE_BIT = 0x4,
+       IFC_INTERRUPT_ENABLE_BIT = 0x8
+};
+
+/* ISR0 -- Interrupt Status Register 0 */
+enum isr0_bits {
+       ATN_INTERRUPT_BIT = 0x4,
+       IFC_INTERRUPT_BIT = 0x8
+};
+
+enum state1_bits {
+       SOURCE_HANDSHAKE_SIDS_BITS = 0x0, /* source idle state */
+       SOURCE_HANDSHAKE_SGNS_BITS = 0x1, /* source generate state */
+       SOURCE_HANDSHAKE_SDYS_BITS = 0x2, /* source delay state */
+       SOURCE_HANDSHAKE_STRS_BITS = 0x5, /* source transfer state */
+       SOURCE_HANDSHAKE_MASK = 0x7
+};
+
+enum fmh_gpib_auxmr_bits {
+       AUX_I_REG = 0xe0,
+};
+
+enum aux_reg_i_bits {
+       LOCAL_PPOLL_MODE_BIT = 0x4
+};
+
+enum ext_status_1_bits {
+       DATA_IN_STATUS_BIT = 0x01,
+       DATA_OUT_STATUS_BIT = 0x02,
+       COMMAND_OUT_STATUS_BIT = 0x04,
+       RFD_HOLDOFF_STATUS_BIT = 0x08,
+       END_STATUS_BIT = 0x10
+};
+
+/* dma fifo reg and bits */
+enum dma_fifo_regs {
+       FIFO_DATA_REG = 0x0,
+       FIFO_CONTROL_STATUS_REG = 0x1,
+       FIFO_XFER_COUNTER_REG = 0x2,
+       FIFO_MAX_BURST_LENGTH_REG = 0x3
+};
+
+enum fifo_data_bits {
+       FIFO_DATA_EOI_FLAG = 0x100
+};
+
+enum fifo_control_bits {
+       TX_FIFO_DMA_REQUEST_ENABLE = 0x0001,
+       TX_FIFO_CLEAR = 0x0002,
+       TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE = 0x0008,
+       RX_FIFO_DMA_REQUEST_ENABLE = 0x0100,
+       RX_FIFO_CLEAR = 0x0200,
+       RX_FIFO_HALF_FULL_INTERRUPT_ENABLE = 0x0800
+};
+
+enum fifo_status_bits {
+       TX_FIFO_EMPTY = 0x0001,
+       TX_FIFO_FULL = 0x0002,
+       TX_FIFO_HALF_EMPTY = 0x0004,
+       TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED = 0x0008,
+       TX_FIFO_DMA_REQUEST_IS_ENABLED = 0x0010,
+       RX_FIFO_EMPTY = 0x0100,
+       RX_FIFO_FULL = 0x0200,
+       RX_FIFO_HALF_FULL = 0x0400,
+       RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED = 0x0800,
+       RX_FIFO_DMA_REQUEST_IS_ENABLED = 0x1000
+};
+
+static const unsigned int fifo_data_mask = 0x00ff;
+static const unsigned int fifo_xfer_counter_mask = 0x0fff;
+static const unsigned int fifo_max_burst_length_mask = 0x00ff;
+
+static inline uint8_t gpib_cs_read_byte(struct nec7210_priv *nec_priv,
+                                       unsigned int register_num)
+{
+       return readb(nec_priv->iobase + register_num * nec_priv->offset);
+}
+
+static inline void gpib_cs_write_byte(struct nec7210_priv *nec_priv, uint8_t data,
+                                     unsigned int register_num)
+{
+       writeb(data, nec_priv->iobase + register_num * nec_priv->offset);
+}
+
+static inline uint16_t fifos_read(struct fmh_priv *fmh_priv, int register_num)
+{
+       if (!fmh_priv->fifo_base)
+               return 0;
+       return readw(fmh_priv->fifo_base + register_num * fifo_reg_offset);
+}
+
+static inline void fifos_write(struct fmh_priv *fmh_priv, uint16_t data, int register_num)
+{
+       if (!fmh_priv->fifo_base)
+               return;
+       writew(data, fmh_priv->fifo_base + register_num * fifo_reg_offset);
+}
+
+enum bus_status_bits {
+       BSR_ATN_BIT = 0x01,
+       BSR_EOI_BIT = 0x02,
+       BSR_SRQ_BIT = 0x04,
+       BSR_IFC_BIT = 0x08,
+       BSR_REN_BIT = 0x10,
+       BSR_DAV_BIT = 0x20,
+       BSR_NRFD_BIT = 0x40,
+       BSR_NDAC_BIT = 0x80,
+};
+
+enum fmh_gpib_aux_cmds {
+       /* AUX_RTL2 is an auxiliary command which causes the cb7210 to assert
+        * (and keep asserted) the local rtl message.  This is used in conjunction
+        * with the normal nec7210 AUX_RTL command, which
+        * pulses the rtl message, having the effect of clearing rtl if it was left
+        * asserted by AUX_RTL2.
+        */
+       AUX_RTL2 = 0x0d,
+       AUX_RFD_HOLDOFF_ASAP = 0x15,
+       AUX_REQT = 0x18,
+       AUX_REQF = 0x19,
+       AUX_LO_SPEED = 0x40,
+       AUX_HI_SPEED = 0x41
+};