staging: gpib: Add gpio bitbang GPIB driver
authorDave Penkler <dpenkler@gmail.com>
Wed, 18 Sep 2024 12:19:01 +0000 (14:19 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 10 Oct 2024 13:28:40 +0000 (15:28 +0200)
GPIO bitbang driver for Rasbberry Pi 2/3/4/5

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

diff --git a/drivers/staging/gpib/gpio/Makefile b/drivers/staging/gpib/gpio/Makefile
new file mode 100644 (file)
index 0000000..a31ded6
--- /dev/null
@@ -0,0 +1,4 @@
+
+obj-m += gpib_bitbang.o
+
+
diff --git a/drivers/staging/gpib/gpio/gpib_bitbang.c b/drivers/staging/gpib/gpio/gpib_bitbang.c
new file mode 100644 (file)
index 0000000..81a952b
--- /dev/null
@@ -0,0 +1,1513 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*************************************************************************
+ *  This code has been developed at the Institute of Sensor and Actuator  *
+ *  Systems (Technical University of Vienna, Austria) to enable the GPIO  *
+ *  lines (e.g. of a raspberry pi) to function as a GPIO master device   *
+ *                                                                       *
+ *  authors             : Thomas Klima                                   *
+ *                        Marcello Carla'                                *
+ *                        Dave Penkler                                   *
+ *                                                                       *
+ *  copyright           : (C) 2016 Thomas Klima                          *
+ *                                                                       *
+ *************************************************************************/
+
+/*
+ * limitations:
+ *     works only on RPi
+ *     cannot function as non-CIC system controller with SN7516x because
+ *     SN75161B cannot simultaneously make ATN input with IFC and REN as
+ *     outputs.
+ * not implemented:
+ *     parallel poll
+ *     return2local
+ *     device support (non master operation)
+ */
+
+#define NAME KBUILD_MODNAME
+
+#define ENABLE_IRQ(IRQ, TYPE) irq_set_irq_type(IRQ, TYPE)
+#define DISABLE_IRQ(IRQ) irq_set_irq_type(IRQ, IRQ_TYPE_NONE)
+
+/* Debug print levels:
+ *  0 = load/unload info and errors that make the driver fail;
+ *  1 = + warnings for unforeseen events that may break the current
+ *      operation and lead to a timeout, but do not affect the
+ *       driver integrity (mainly unexpected interrupts);
+ *  2 = + trace of function calls;
+ *  3 = + trace of protocol codes;
+ *  4 = + trace of interrupt operation.
+ */
+#define dbg_printk(level, frm, ...)                                    \
+       do { if (debug >= (level))                                      \
+                       pr_info("%s:%s - " frm, NAME, __func__, ## __VA_ARGS__); } \
+       while (0)
+
+#define LINVAL gpiod_get_value(DAV),           \
+               gpiod_get_value(NRFD),          \
+               gpiod_get_value(NDAC),          \
+               gpiod_get_value(SRQ)
+#define LINFMT "DAV: %d         NRFD:%d  NDAC: %d SRQ: %d"
+
+#include "gpibP.h"
+#include "gpib_state_machines.h"
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/leds.h>
+
+static int sn7516x_used = 1, sn7516x;
+module_param(sn7516x_used, int, 0660);
+
+#define PINMAP_0 "elektronomikon"
+#define PINMAP_1 "gpib4pi-1.1"
+#define PINMAP_2 "yoga"
+static char *pin_map = PINMAP_0;
+module_param(pin_map, charp, 0660);
+MODULE_PARM_DESC(pin_map, " valid values: " PINMAP_0 " " PINMAP_1 " " PINMAP_2);
+
+/**********************************************
+ *  Signal pairing and pin wiring between the *
+ *  Raspberry-Pi connector and the GPIB bus   *
+ *                                           *
+ *              signal           pin wiring  *
+ *           GPIB  Pi-gpio     GPIB  ->  RPi *
+ **********************************************
+ */
+enum lines_t {
+       D01_pin_nr =  20,     /*   1  ->  38  */
+       D02_pin_nr =  26,     /*   2  ->  37  */
+       D03_pin_nr =  16,     /*   3  ->  36  */
+       D04_pin_nr =  19,     /*   4  ->  35  */
+       D05_pin_nr =  13,     /*  13  ->  33  */
+       D06_pin_nr =  12,     /*  14  ->  32  */
+       D07_pin_nr =   6,     /*  15  ->  31  */
+       D08_pin_nr =   5,     /*  16  ->  29  */
+       EOI_pin_nr =   9,     /*   5  ->  21  */
+       DAV_pin_nr =  10,     /*   6  ->  19  */
+       NRFD_pin_nr = 24,     /*   7  ->  18  */
+       NDAC_pin_nr = 23,     /*   8  ->  16  */
+       IFC_pin_nr =  22,     /*   9  ->  15  */
+       SRQ_pin_nr =  11,     /*  10  ->  23  */
+       _ATN_pin_nr = 25,     /*  11  ->  22  */
+       REN_pin_nr =  27,     /*  17  ->  13  */
+/*
+ *  GROUND PINS
+ *    12,18,19,20,21,22,23,24  => 14,20,25,30,34,39
+ */
+
+/*
+ *  These lines are used to control the external
+ *  SN75160/161 driver chips when used.
+ *  When not used there is reduced fan out;
+ *  currently tested with up to 4 devices.
+ */
+
+/*              Pi GPIO        RPI   75161B 75160B   Description       */
+       PE_pin_nr =    7,    /*  26  ->   nc     11   Pullup Enable     */
+       DC_pin_nr =    8,    /*  24  ->   12     nc   Direction control */
+       TE_pin_nr =   18,    /*  12  ->    2      1   Talk Enable       */
+       ACT_LED_pin_nr = 4,  /*   7  ->  LED  */
+
+/* YOGA adapter uses different pinout to ease layout */
+       YOGA_D03_pin_nr =  13,
+       YOGA_D04_pin_nr =  12,
+       YOGA_D05_pin_nr =  21,
+       YOGA_D06_pin_nr =  19,
+};
+
+/*
+ * GPIO descriptors and pins - WARNING: STRICTLY KEEP ITEMS ORDER
+ */
+
+#define GPIB_PINS 16
+#define SN7516X_PINS 4
+#define NUM_PINS (GPIB_PINS + SN7516X_PINS)
+
+DEFINE_LED_TRIGGER(ledtrig_gpib);
+#define ACT_LED_ON do {                                                        \
+               if (ACT_LED)                                    \
+                       gpiod_direction_output(ACT_LED, 1);             \
+               else                                                    \
+                       led_trigger_event(ledtrig_gpib, LED_FULL); }    \
+       while (0)
+#define ACT_LED_OFF do {                                               \
+               if (ACT_LED)                                    \
+                       gpiod_direction_output(ACT_LED, 0);             \
+               else                                                    \
+                       led_trigger_event(ledtrig_gpib, LED_OFF); }     \
+       while (0)
+
+struct gpio_desc *all_descriptors[GPIB_PINS + SN7516X_PINS];
+
+#define D01 all_descriptors[0]
+#define D02 all_descriptors[1]
+#define D03 all_descriptors[2]
+#define D04 all_descriptors[3]
+#define D05 all_descriptors[4]
+#define D06 all_descriptors[5]
+#define D07 all_descriptors[6]
+#define D08 all_descriptors[7]
+
+#define EOI all_descriptors[8]
+#define NRFD all_descriptors[9]
+#define IFC all_descriptors[10]
+#define _ATN all_descriptors[11]
+#define REN all_descriptors[12]
+#define DAV all_descriptors[13]
+#define NDAC all_descriptors[14]
+#define SRQ all_descriptors[15]
+
+#define PE all_descriptors[16]
+#define DC all_descriptors[17]
+#define TE all_descriptors[18]
+#define ACT_LED all_descriptors[19]
+
+/* YOGA dapter uses a global enable for the buffer chips, re-using the TE pin */
+#define YOGA_ENABLE TE
+
+int gpios_vector[] = {
+       D01_pin_nr,
+       D02_pin_nr,
+       D03_pin_nr,
+       D04_pin_nr,
+       D05_pin_nr,
+       D06_pin_nr,
+       D07_pin_nr,
+       D08_pin_nr,
+
+       EOI_pin_nr,
+       NRFD_pin_nr,
+       IFC_pin_nr,
+       _ATN_pin_nr,
+       REN_pin_nr,
+       DAV_pin_nr,
+       NDAC_pin_nr,
+       SRQ_pin_nr,
+
+       PE_pin_nr,
+       DC_pin_nr,
+       TE_pin_nr,
+       ACT_LED_pin_nr
+};
+
+/* Lookup table for general GPIOs */
+
+static struct gpiod_lookup_table gpib_gpio_table_0 = {
+       // for bcm2835/6
+       .dev_id = "",    // device id of board device
+       .table = {
+               GPIO_LOOKUP_IDX("GPIO_GCLK",  U16_MAX, NULL,  4, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO5",          U16_MAX, NULL,  5, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO6",          U16_MAX, NULL,  6, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_CE1_N",  U16_MAX, NULL,  7, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_CE0_N",  U16_MAX, NULL,  8, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_MISO",       U16_MAX, NULL,  9, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_MOSI",       U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_SCLK",       U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO12",         U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO13",         U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("TXD0",   U16_MAX, NULL, 14, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("RXD0",   U16_MAX, NULL, 15, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO16",         U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO17",         U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO18",         U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO19",         U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO20",         U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO21",         U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO22",         U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO23",         U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO24",         U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO25",         U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO26",         U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO27",         U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH),
+               { }
+       },
+};
+
+static struct gpiod_lookup_table gpib_gpio_table_1 = {
+       .dev_id = "",    // device id of board device
+       .table = {
+               // for bcm2837 based pis (3a+ 3b 3b+ )
+               GPIO_LOOKUP_IDX("GPIO_GCLK",  U16_MAX, NULL,  4, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO5",          U16_MAX, NULL,  5, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO6",          U16_MAX, NULL,  6, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_CE1_N",  U16_MAX, NULL,  7, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_CE0_N",  U16_MAX, NULL,  8, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_MISO",       U16_MAX, NULL,  9, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_MOSI",       U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("SPI_SCLK",       U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO12",         U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO13",         U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("TXD1",   U16_MAX, NULL, 14, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("RXD1",   U16_MAX, NULL, 15, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO16",         U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO17",         U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO18",         U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO19",         U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO20",         U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO21",         U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO22",         U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO23",         U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO24",         U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO25",         U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO26",         U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO27",         U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH),
+               { }
+       },
+};
+
+static struct gpiod_lookup_table gpib_gpio_table_2 = {
+       .dev_id = "",    // device id of board device
+       .table = {
+               // for bcm27xx based pis (b b+ 2b 3b 3b+ 4 5)
+               GPIO_LOOKUP_IDX("GPIO4",  U16_MAX, NULL,  4, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO5",  U16_MAX, NULL,  5, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO6",  U16_MAX, NULL,  6, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO7",  U16_MAX, NULL,  7, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO8",  U16_MAX, NULL,  8, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO9",  U16_MAX, NULL,  9, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO10", U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO11", U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO12", U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO13", U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO14", U16_MAX, NULL, 14, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO15", U16_MAX, NULL, 15, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO16", U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO17", U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO18", U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO19", U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO20", U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO21", U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO22", U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO23", U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO24", U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO25", U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO26", U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("GPIO27", U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH),
+               { }
+       },
+};
+
+static struct gpiod_lookup_table *lookup_tables[] = {
+       &gpib_gpio_table_0,
+       &gpib_gpio_table_1,
+       &gpib_gpio_table_2,
+       0
+};
+
+/* struct which defines private_data for gpio driver */
+
+struct bb_priv {
+       int irq_NRFD;
+       int irq_NDAC;
+       int irq_DAV;
+       int irq_SRQ;
+       int dav_mode;        /* dav  interrupt mode 0/1 -> edge/levels */
+       int nrfd_mode;       /* nrfd interrupt mode 0/1 -> edge/levels */
+       int ndac_mode;       /* nrfd interrupt mode 0/1 -> edge/levels */
+       int dav_tx;          /* keep trace of DAV status while sending */
+       int dav_rx;          /* keep trace of DAV status while receiving */
+       u8 eos;      // eos character
+       short eos_flags;     // eos mode
+       short eos_check;     /* eos check required in current operation ... */
+       short eos_check_8;   /* ... with byte comparison */
+       short eos_mask_7;    /* ... with 7 bit masked character */
+       short int end;
+       int request;
+       int count;
+       int direction;
+       int t1_delay;
+       u8 *rbuf;
+       u8 *wbuf;
+       int end_flag;
+       int r_busy;        /* 0==idle   1==busy  */
+       int w_busy;
+       int write_done;
+       int cmd;           /* 1 = cmd write in  progress */
+       size_t w_cnt;
+       size_t length;
+       u8 *w_buf;
+       spinlock_t rw_lock; // protect mods to rw_lock
+       int phase;
+       int ndac_idle;
+       int ndac_seq;
+       int nrfd_idle;
+       int nrfd_seq;
+       int dav_seq;
+       long all_irqs;
+       int dav_idle;
+       int atn_asserted;
+
+       enum talker_function_state talker_state;
+       enum listener_function_state listener_state;
+};
+
+inline long usec_diff(struct timespec64 *a, struct timespec64 *b);
+static void bb_buffer_print(unsigned char *buffer, size_t length, int cmd, int eoi);
+static void set_data_lines(u8 byte);
+static u8 get_data_lines(void);
+static void set_data_lines_input(void);
+static void set_data_lines_output(void);
+static inline int check_for_eos(struct bb_priv *priv, uint8_t byte);
+static void set_atn(struct bb_priv *priv, int atn_asserted);
+
+static inline void SET_DIR_WRITE(struct bb_priv *priv);
+static inline void SET_DIR_READ(struct bb_priv *priv);
+
+#define DIR_READ 0
+#define DIR_WRITE 1
+
+MODULE_LICENSE("GPL");
+
+/****  global variables         ****/
+#ifdef CONFIG_GPIB_DEBUG
+static int debug = 1;
+#else
+static int debug;
+#endif
+module_param(debug, int, 0644);
+
+static char printable(char x)
+{
+       if (x < 32 || x > 126)
+               return ' ';
+       return x;
+}
+
+/***************************************************************************
+ *                                                                        *
+ * READ                                                                           *
+ *                                                                        *
+ ***************************************************************************/
+
+static int bb_read(gpib_board_t *board, uint8_t *buffer, size_t length,
+                  int *end, size_t *bytes_read)
+{
+       struct bb_priv *priv = board->private_data;
+       unsigned long flags;
+       int retval = 0;
+
+       ACT_LED_ON;
+       SET_DIR_READ(priv);
+
+       dbg_printk(2, "board: %p  lock %d  length: %zu\n",
+                  board, mutex_is_locked(&board->user_mutex), length);
+
+       priv->end = 0;
+       priv->count = 0;
+       priv->rbuf = buffer;
+       if (length == 0)
+               goto read_end;
+       priv->request = length;
+       priv->eos_check = (priv->eos_flags & REOS) == 0; /* do eos check */
+       priv->eos_check_8 = priv->eos_flags & BIN;       /* over 8 bits */
+       priv->eos_mask_7 = priv->eos & 0x7f;             /* with this 7 bit eos */
+
+       dbg_printk(3, ".........." LINFMT "\n", LINVAL);
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+       priv->dav_mode = 1;
+       priv->dav_rx = 1;
+       ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_LEVEL_LOW);
+       priv->end_flag = 0;
+       gpiod_set_value(NRFD, 1); // ready for data
+       priv->r_busy = 1;
+       priv->phase = 100;
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+
+       /* wait for the interrupt routines finish their work */
+
+       retval = wait_event_interruptible(board->wait,
+                                         (priv->end_flag || board->status & TIMO));
+
+       dbg_printk(3, "awake from wait queue: %d\n", retval);
+
+       if (retval == 0 && board->status & TIMO) {
+               retval = -ETIMEDOUT;
+               dbg_printk(1, "timeout\n");
+       } else if (retval) {
+               retval = -ERESTARTSYS;
+       }
+
+       DISABLE_IRQ(priv->irq_DAV);
+       spin_lock_irqsave(&priv->rw_lock, flags);
+       gpiod_set_value(NRFD, 0); // DIR_READ line state
+       priv->r_busy = 0;
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+
+read_end:
+       ACT_LED_OFF;
+       *bytes_read = priv->count;
+       *end = priv->end;
+       priv->r_busy = 0;
+       dbg_printk(2, "return: %d  eoi|eos: %d count: %d\n\n", retval, priv->end, priv->count);
+       return retval;
+}
+
+/***************************************************************************
+ *                                                                        *
+ *     READ interrupt routine (DAV line)                                  *
+ *                                                                        *
+ ***************************************************************************/
+
+static irqreturn_t bb_DAV_interrupt(int irq, void *arg)
+{
+       gpib_board_t *board = arg;
+       struct bb_priv *priv = board->private_data;
+       int val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+
+       priv->all_irqs++;
+
+       if (priv->dav_mode) {
+               ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_EDGE_BOTH);
+               priv->dav_mode = 0;
+       }
+
+       if (priv->r_busy == 0) {
+               dbg_printk(1, "interrupt while idle after %d at %d\n",
+                          priv->count, priv->phase);
+               priv->dav_idle++;
+               priv->phase = 200;
+               goto dav_exit;  /* idle */
+       }
+
+       val = gpiod_get_value(DAV);
+       if (val == priv->dav_rx) {
+               dbg_printk(1, "out of order DAV interrupt %d/%d after %zu/%zu at %d cmd %d "
+                          LINFMT ".\n", val, priv->dav_rx, priv->w_cnt, priv->length,
+                          priv->phase, priv->cmd, LINVAL);
+               priv->dav_seq++;
+       }
+       priv->dav_rx = val;
+
+       dbg_printk(3, "> irq: %d  DAV: %d  st: %4lx dir: %d  busy: %d:%d\n",
+                  irq, val, board->status, priv->direction, priv->r_busy, priv->w_busy);
+
+       if (val == 0) {
+               gpiod_set_value(NRFD, 0); // not ready for data
+               priv->rbuf[priv->count++] = get_data_lines();
+               priv->end = !gpiod_get_value(EOI);
+               gpiod_set_value(NDAC, 1); // data accepted
+               priv->end |= check_for_eos(priv, priv->rbuf[priv->count - 1]);
+               priv->end_flag = ((priv->count >= priv->request) || priv->end);
+               priv->phase = 210;
+       } else {
+               gpiod_set_value(NDAC, 0);       // data not accepted
+               if (priv->end_flag) {
+                       priv->r_busy = 0;
+                       wake_up_interruptible(&board->wait);
+                       priv->phase = 220;
+               } else {
+                       gpiod_set_value(NRFD, 1);     // ready for data
+                       priv->phase = 230;
+               }
+       }
+
+dav_exit:
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+       dbg_printk(3, "< irq: %d  count %d\n", irq, priv->count);
+       return IRQ_HANDLED;
+}
+
+/***************************************************************************
+ *                                                                        *
+ * WRITE                                                                  *
+ *                                                                        *
+ ***************************************************************************/
+
+static int bb_write(gpib_board_t *board, uint8_t *buffer, size_t length,
+                   int send_eoi, size_t *bytes_written)
+{
+       unsigned long flags;
+       int retval = 0;
+
+       struct bb_priv *priv = board->private_data;
+
+       ACT_LED_ON;
+
+       priv->w_cnt = 0;
+       priv->w_buf = buffer;
+       dbg_printk(2, "board %p lock %d  length: %zu\n",
+                  board, mutex_is_locked(&board->user_mutex), length);
+
+       if (debug > 1)
+               bb_buffer_print(buffer, length, priv->cmd, send_eoi);
+       priv->count = 0;
+       priv->phase = 300;
+
+       if (length == 0)
+               goto write_end;
+       priv->end = send_eoi;
+       priv->length = length;
+
+       SET_DIR_WRITE(priv);
+
+       dbg_printk(2, "Enabling interrupts - NRFD: %d   NDAC: %d\n",
+                  gpiod_get_value(NRFD), gpiod_get_value(NDAC));
+
+       if (gpiod_get_value(NRFD) && gpiod_get_value(NDAC)) { /* check for listener */
+               retval = -ENODEV;
+               goto write_end;
+       }
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+       priv->w_busy = 1;          /* make the interrupt routines active */
+       priv->write_done = 0;
+       priv->nrfd_mode = 1;
+       priv->ndac_mode = 1;
+       priv->dav_tx = 1;
+       ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_LEVEL_HIGH);
+       ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_LEVEL_HIGH);
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+
+       /* wait for the interrupt routines finish their work */
+
+       retval = wait_event_interruptible(board->wait,
+                                         priv->write_done || (board->status & TIMO));
+
+       dbg_printk(3, "awake from wait queue: %d\n", retval);
+
+       if (retval == 0) {
+               if (board->status & TIMO) {
+                       retval = -ETIMEDOUT;
+                       dbg_printk(1, "timeout after %zu/%zu at %d " LINFMT " eoi: %d\n",
+                                  priv->w_cnt, length, priv->phase, LINVAL, send_eoi);
+               } else {
+                       // dbg_printk(1,"written %zu\n", priv->w_cnt);
+                       retval = priv->w_cnt;
+               }
+       } else {
+               retval = -ERESTARTSYS;
+       }
+
+       DISABLE_IRQ(priv->irq_NRFD);
+       DISABLE_IRQ(priv->irq_NDAC);
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+       priv->w_busy = 0;
+       gpiod_set_value(DAV, 1); // DIR_WRITE line state
+       gpiod_set_value(EOI, 1); // De-assert EOI (in case)
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+
+write_end:
+       *bytes_written = priv->w_cnt;
+       ACT_LED_OFF;
+       dbg_printk(2, "sent %zu bytes\r\n\r\n", *bytes_written);
+       priv->phase = 310;
+       return retval;
+}
+
+/***************************************************************************
+ *                                                                        *
+ *     WRITE interrupt routine (NRFD line)                                *
+ *                                                                        *
+ ***************************************************************************/
+
+static irqreturn_t bb_NRFD_interrupt(int irq, void *arg)
+{
+       gpib_board_t *board = arg;
+       struct bb_priv *priv = board->private_data;
+       unsigned long flags;
+       int nrfd;
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+
+       nrfd = gpiod_get_value(NRFD);
+       priv->all_irqs++;
+
+       dbg_printk(3, "> irq: %d  NRFD: %d   NDAC: %d   st: %4lx dir: %d  busy: %d:%d\n",
+                  irq, nrfd, gpiod_get_value(NDAC), board->status, priv->direction,
+                  priv->w_busy, priv->r_busy);
+
+       if (priv->nrfd_mode) {
+               ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_EDGE_RISING);
+               priv->nrfd_mode = 0;
+       }
+
+       if (priv->w_busy == 0) {
+               dbg_printk(1, "interrupt while idle after %zu/%zu at %d\n",
+                          priv->w_cnt, priv->length, priv->phase);
+               priv->nrfd_idle++;
+               goto nrfd_exit;  /* idle */
+       }
+       if (nrfd == 0) {
+               dbg_printk(1, "out of order interrupt after %zu/%zu at %d cmd %d " LINFMT ".\n",
+                          priv->w_cnt, priv->length, priv->phase, priv->cmd, LINVAL);
+               priv->phase = 400;
+               priv->nrfd_seq++;
+               goto nrfd_exit;
+       }
+       if (!priv->dav_tx) {
+               dbg_printk(1, "DAV low after %zu/%zu cmd %d " LINFMT ". No action.\n",
+                          priv->w_cnt, priv->length, priv->cmd, LINVAL);
+               priv->dav_seq++;
+               goto nrfd_exit;
+       }
+
+       if (priv->atn_asserted && priv->w_cnt >= priv->length) { // test for end of transfer
+               priv->write_done = 1;
+               priv->w_busy = 0;
+               wake_up_interruptible(&board->wait);
+               goto nrfd_exit;
+       }
+
+       dbg_printk(3, "sending %zu\n", priv->w_cnt);
+
+       set_data_lines(priv->w_buf[priv->w_cnt++]); // put the data on the lines
+
+       if (priv->w_cnt == priv->length && priv->end) {
+               dbg_printk(3, "Asserting EOI\n");
+               gpiod_set_value(EOI, 0); // Assert EOI
+       }
+
+       gpiod_set_value(DAV, 0); // Data available
+       priv->dav_tx = 0;
+       priv->phase = 410;
+
+nrfd_exit:
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+/***************************************************************************
+ *                                                                        *
+ *     WRITE interrupt routine (NDAC line)                                *
+ *                                                                        *
+ ***************************************************************************/
+
+static irqreturn_t bb_NDAC_interrupt(int irq, void *arg)
+{
+       gpib_board_t *board = arg;
+       struct bb_priv *priv = board->private_data;
+       unsigned long flags;
+       int ndac;
+
+       spin_lock_irqsave(&priv->rw_lock, flags);
+
+       ndac = gpiod_get_value(NDAC);
+       priv->all_irqs++;
+       dbg_printk(3, "> irq: %d  NRFD: %d   NDAC: %d   st: %4lx dir: %d  busy: %d:%d\n",
+                  irq, gpiod_get_value(NRFD), ndac, board->status, priv->direction,
+                  priv->w_busy, priv->r_busy);
+
+       if (priv->ndac_mode) {
+               ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_EDGE_RISING);
+               priv->ndac_mode = 0;
+       }
+
+       if (priv->w_busy == 0) {
+               dbg_printk(1, "interrupt while idle.\n");
+               priv->ndac_idle++;
+               goto ndac_exit;
+       }
+       if (ndac == 0) {
+               dbg_printk(1, "out of order interrupt at %zu:%d.\n", priv->w_cnt, priv->phase);
+               priv->phase = 500;
+               priv->ndac_seq++;
+               goto ndac_exit;
+       }
+       if (priv->dav_tx) {
+               dbg_printk(1, "DAV high after %zu/%zu cmd %d " LINFMT ". No action.\n",
+                          priv->w_cnt, priv->length, priv->cmd, LINVAL);
+               priv->dav_seq++;
+               goto ndac_exit;
+       }
+
+       dbg_printk(3, "accepted %zu\n", priv->w_cnt - 1);
+
+       if (!priv->atn_asserted && priv->w_cnt >= priv->length) { // test for end of transfer
+               priv->write_done = 1;
+               priv->w_busy = 0;
+               wake_up_interruptible(&board->wait);
+       } else {
+               gpiod_set_value(DAV, 1); // Data not available
+               priv->dav_tx = 1;
+               priv->phase = 510;
+       }
+
+ndac_exit:
+       spin_unlock_irqrestore(&priv->rw_lock, flags);
+       return IRQ_HANDLED;
+}
+
+/***************************************************************************
+ *                                                                        *
+ *     interrupt routine for SRQ line                                     *
+ *                                                                        *
+ ***************************************************************************/
+
+static irqreturn_t bb_SRQ_interrupt(int irq, void *arg)
+{
+       gpib_board_t  *board = arg;
+
+       int val = gpiod_get_value(SRQ);
+
+       dbg_printk(3, "> %d   st: %4lx\n", val, board->status);
+
+       if (!val)
+               set_bit(SRQI_NUM, &board->status);  /* set_bit() is atomic */
+
+       wake_up_interruptible(&board->wait);
+
+       return IRQ_HANDLED;
+}
+
+static int bb_command(gpib_board_t *board, uint8_t *buffer,
+                     size_t length, size_t *bytes_written)
+{
+       size_t ret;
+       struct bb_priv *priv = board->private_data;
+       int i;
+
+       dbg_printk(2, "%p  %p\n", buffer, board->buffer);
+
+       /* the _ATN line has already been asserted by bb_take_control() */
+
+       priv->cmd = 1;
+
+       ret = bb_write(board, buffer, length, 0, bytes_written); // no eoi
+
+       for (i = 0; i < length; i++) {
+               if (buffer[i] == UNT) {
+                       priv->talker_state = talker_idle;
+               } else {
+                       if (buffer[i] == UNL) {
+                               priv->listener_state = listener_idle;
+                       } else {
+                               if (buffer[i] == (MTA(board->pad))) {
+                                       priv->talker_state = talker_addressed;
+                                       priv->listener_state = listener_idle;
+                               } else if (buffer[i] == (MLA(board->pad))) {
+                                       priv->listener_state = listener_addressed;
+                                       priv->talker_state = talker_idle;
+                               }
+                       }
+               }
+       }
+
+       /* the _ATN line will be released by bb_go_to_stby */
+
+       priv->cmd = 0;
+
+       return ret;
+}
+
+/***************************************************************************
+ *                                                                        *
+ *     Buffer print with decode for debug/trace                           *
+ *                                                                        *
+ ***************************************************************************/
+
+static char *cmd_string[32] = {
+       "",    // 0x00
+       "GTL", // 0x01
+       "",    // 0x02
+       "",    // 0x03
+       "SDC", // 0x04
+       "PPC", // 0x05
+       "",    // 0x06
+       "",    // 0x07
+       "GET", // 0x08
+       "TCT", // 0x09
+       "",    // 0x0a
+       "",    // 0x0b
+       "",    // 0x0c
+       "",    // 0x0d
+       "",    // 0x0e
+       "",    // 0x0f
+       "",    // 0x10
+       "LLO", // 0x11
+       "",    // 0x12
+       "",    // 0x13
+       "DCL", // 0x14
+       "PPU", // 0x15
+       "",    // 0x16
+       "",    // 0x17
+       "SPE", // 0x18
+       "SPD", // 0x19
+       "",    // 0x1a
+       "",    // 0x1b
+       "",    // 0x1c
+       "",    // 0x1d
+       "",    // 0x1e
+       "CFE"  // 0x1f
+};
+
+static void bb_buffer_print(unsigned char *buffer, size_t length, int cmd, int eoi)
+{
+       int i;
+
+       if (cmd) {
+               dbg_printk(2, "<cmd len %zu>\n", length);
+               for (i = 0; i < length; i++) {
+                       if (buffer[i] < 0x20) {
+                               dbg_printk(3, "0x%x=%s\n", buffer[i], cmd_string[buffer[i]]);
+                       } else if (buffer[i] == 0x3f) {
+                               dbg_printk(3, "0x%x=%s\n", buffer[i], "UNL");
+                       } else if (buffer[i] == 0x5f) {
+                               dbg_printk(3, "0x%x=%s\n", buffer[i], "UNT");
+                       } else  if (buffer[i] < 0x60) {
+                               dbg_printk(3, "0x%x=%s%d\n", buffer[i],
+                                          (buffer[i] & 0x40) ? "TLK" : "LSN", buffer[i] & 0x1F);
+                       } else {
+                               dbg_printk(3, "0x%x\n", buffer[i]);
+                       }
+               }
+       } else {
+               dbg_printk(2, "<data len %zu %s>\n", length, (eoi) ? "w.EOI" : " ");
+               for (i = 0; i < length; i++)
+                       dbg_printk(2, "%3d  0x%x->%c\n", i, buffer[i], printable(buffer[i]));
+       }
+}
+
+/***************************************************************************
+ *                                                                        *
+ * STATUS Management                                                      *
+ *                                                                        *
+ ***************************************************************************/
+static void set_atn(struct bb_priv *priv, int atn_asserted)
+{
+       if (priv->listener_state != listener_idle &&
+           priv->talker_state != talker_idle) {
+               dbg_printk(0, "listener/talker state machine conflict\n");
+       }
+       if (atn_asserted) {
+               if (priv->listener_state == listener_active)
+                       priv->listener_state = listener_addressed;
+               if (priv->talker_state == talker_active)
+                       priv->talker_state = talker_addressed;
+       } else {
+               if (priv->listener_state == listener_addressed) {
+                       priv->listener_state = listener_active;
+                       SET_DIR_READ(priv); // make sure holdoff is active when we unassert ATN
+               }
+               if (priv->talker_state == talker_addressed)
+                       priv->talker_state = talker_active;
+       }
+       gpiod_direction_output(_ATN, !atn_asserted);
+       priv->atn_asserted = atn_asserted;
+}
+
+static int bb_take_control(gpib_board_t *board, int synchronous)
+{
+       dbg_printk(2, "%d\n", synchronous);
+       set_atn(board->private_data, 1);
+       set_bit(CIC_NUM, &board->status);
+       return 0;
+}
+
+static int bb_go_to_standby(gpib_board_t *board)
+{
+       dbg_printk(2, "\n");
+       set_atn(board->private_data, 0);
+       return 0;
+}
+
+static void bb_request_system_control(gpib_board_t *board, int request_control)
+{
+       dbg_printk(2, "%d\n", request_control);
+       if (request_control) {
+               set_bit(CIC_NUM, &board->status);
+               // drive DAV & EOI false, enable NRFD & NDAC irqs
+               SET_DIR_WRITE(board->private_data);
+       } else {
+               clear_bit(CIC_NUM, &board->status);
+       }
+}
+
+static void bb_interface_clear(gpib_board_t *board, int assert)
+{
+       struct bb_priv *priv = board->private_data;
+
+       dbg_printk(2, "%d\n", assert);
+       if (assert) {
+               gpiod_direction_output(IFC, 0);
+               priv->talker_state = talker_idle;
+               priv->listener_state = listener_idle;
+       } else {
+               gpiod_direction_output(IFC, 1);
+       }
+}
+
+static void bb_remote_enable(gpib_board_t *board, int enable)
+{
+       dbg_printk(2, "%d\n", enable);
+       if (enable) {
+               set_bit(REM_NUM, &board->status);
+               gpiod_direction_output(REN, 0);
+       } else {
+               clear_bit(REM_NUM, &board->status);
+               gpiod_direction_output(REN, 1);
+       }
+}
+
+static int bb_enable_eos(gpib_board_t *board, uint8_t eos_byte, int compare_8_bits)
+{
+       struct bb_priv *priv = board->private_data;
+
+       dbg_printk(2, "%s\n", "EOS_en");
+       priv->eos = eos_byte;
+       priv->eos_flags = REOS;
+       if (compare_8_bits)
+               priv->eos_flags |= BIN;
+
+       return 0;
+}
+
+static void bb_disable_eos(gpib_board_t *board)
+{
+       struct bb_priv *priv = board->private_data;
+
+       dbg_printk(2, "\n");
+       priv->eos_flags &= ~REOS;
+}
+
+static unsigned int bb_update_status(gpib_board_t *board, unsigned int clear_mask)
+{
+       struct bb_priv *priv = board->private_data;
+
+       board->status &= ~clear_mask;
+
+       if (gpiod_get_value(SRQ))              /* SRQ asserted low */
+               clear_bit(SRQI_NUM, &board->status);
+       else
+               set_bit(SRQI_NUM, &board->status);
+       if (gpiod_get_value(_ATN))                      /* ATN asserted low */
+               clear_bit(ATN_NUM, &board->status);
+       else
+               set_bit(ATN_NUM, &board->status);
+       if (priv->talker_state == talker_active ||
+           priv->talker_state == talker_addressed)
+               set_bit(TACS_NUM, &board->status);
+       else
+               clear_bit(TACS_NUM, &board->status);
+
+       if (priv->listener_state == listener_active ||
+           priv->listener_state == listener_addressed)
+               set_bit(LACS_NUM, &board->status);
+       else
+               clear_bit(LACS_NUM, &board->status);
+
+       dbg_printk(2, "0x%lx mask 0x%x\n", board->status, clear_mask);
+
+       return board->status;
+}
+
+static int bb_primary_address(gpib_board_t *board, unsigned int address)
+{
+       dbg_printk(2, "%d\n", address);
+       board->pad = address;
+       return 0;
+}
+
+static int bb_secondary_address(gpib_board_t *board, unsigned int address, int enable)
+{
+       dbg_printk(2, "%d %d\n", address, enable);
+       if (enable)
+               board->sad = address;
+       return 0;
+}
+
+static int bb_parallel_poll(gpib_board_t *board, uint8_t *result)
+{
+       dbg_printk(1, "%s\n", "not implemented");
+       return -EPERM;
+}
+
+static void bb_parallel_poll_configure(gpib_board_t *board, uint8_t config)
+{
+       dbg_printk(1, "%s\n", "not implemented");
+}
+
+static void bb_parallel_poll_response(gpib_board_t *board, int ist)
+{
+}
+
+static void bb_serial_poll_response(gpib_board_t *board, uint8_t status)
+{
+       dbg_printk(1, "%s\n", "not implemented");
+}
+
+static uint8_t bb_serial_poll_status(gpib_board_t *board)
+{
+       dbg_printk(1, "%s\n", "not implemented");
+       return 0; // -ENOSYS;
+}
+
+static unsigned int bb_t1_delay(gpib_board_t *board,  unsigned int nano_sec)
+{
+       struct bb_priv *priv = board->private_data;
+
+       if (nano_sec <= 350)
+               priv->t1_delay = 350;
+       else if (nano_sec <= 1100)
+               priv->t1_delay = 1100;
+       else
+               priv->t1_delay = 2000;
+
+       dbg_printk(2, "t1 delay set to %d nanosec\n", priv->t1_delay);
+
+       return priv->t1_delay;
+}
+
+static void bb_return_to_local(gpib_board_t *board)
+{
+       dbg_printk(1, "%s\n", "not implemented");
+}
+
+static int bb_line_status(const gpib_board_t *board)
+{
+       int line_status = ValidALL;
+
+//       dbg_printk(1,"\n");
+
+       if (gpiod_get_value(REN) == 0)
+               line_status |= BusREN;
+       if (gpiod_get_value(IFC) == 0)
+               line_status |= BusIFC;
+       if (gpiod_get_value(NDAC) == 0)
+               line_status |= BusNDAC;
+       if (gpiod_get_value(NRFD) == 0)
+               line_status |= BusNRFD;
+       if (gpiod_get_value(DAV) == 0)
+               line_status |= BusDAV;
+       if (gpiod_get_value(EOI) == 0)
+               line_status |= BusEOI;
+       if (gpiod_get_value(_ATN) == 0)
+               line_status |= BusATN;
+       if (gpiod_get_value(SRQ) == 0)
+               line_status |= BusSRQ;
+
+       dbg_printk(2, "status lines: %4x\n", line_status);
+
+       return line_status;
+}
+
+/***************************************************************************
+ *                                                                        *
+ * Module Management                                                      *
+ *                                                                        *
+ ***************************************************************************/
+
+static int allocate_private(gpib_board_t *board)
+{
+       board->private_data = kmalloc(sizeof(struct bb_priv), GFP_KERNEL);
+       if (!board->private_data)
+               return -1;
+       memset(board->private_data, 0, sizeof(struct bb_priv));
+       return 0;
+}
+
+static void free_private(gpib_board_t *board)
+{
+       kfree(board->private_data);
+       board->private_data = NULL;
+}
+
+static int bb_get_irq(gpib_board_t *board, char *name,
+                     struct gpio_desc *gpio, int *irq,
+                     irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags)
+{
+       if (!gpio)
+               return -1;
+       gpiod_direction_input(gpio);
+       *irq = gpiod_to_irq(gpio);
+       dbg_printk(2, "IRQ %s: %d\n", name, *irq);
+       if (*irq < 0) {
+               dbg_printk(0, "gpib: can't get IRQ for %s\n", name);
+               return -1;
+       }
+       if (request_threaded_irq(*irq, handler, thread_fn, flags, name, board)) {
+               dbg_printk(0, "gpib: can't request IRQ for %s %d\n", name, *irq);
+               *irq = 0;
+               return -1;
+       }
+       DISABLE_IRQ(*irq);
+       return 0;
+}
+
+static void bb_free_irq(gpib_board_t *board, int *irq, char *name)
+{
+       if (*irq) {
+               free_irq(*irq, board);
+               dbg_printk(2, "IRQ %d(%s) freed\n", *irq, name);
+               *irq = 0;
+       }
+}
+
+static void release_gpios(void)
+{
+       int j;
+
+       for (j = 0 ; j < NUM_PINS ; j++) {
+               if (all_descriptors[j]) {
+                       gpiod_put(all_descriptors[j]);
+                       all_descriptors[j] = 0;
+               }
+       }
+}
+
+static int allocate_gpios(gpib_board_t *board)
+{
+       int j, retval = 0;
+       bool error = false;
+       int table_index = 0;
+       char name[256];
+       struct gpio_desc *desc;
+       struct gpiod_lookup_table *lookup_table;
+
+       if (!board->gpib_dev) {
+               pr_err("NULL gpib dev for board\n");
+               return -ENOENT;
+       }
+
+       lookup_table = lookup_tables[0];
+       lookup_table->dev_id   = dev_name(board->gpib_dev);
+       gpiod_add_lookup_table(lookup_table);
+       dbg_printk(1, "Allocating gpios using table index %d\n", table_index);
+
+       for (j = 0 ; j < NUM_PINS ; j++) {
+               if (gpios_vector[j] < 0)
+                       continue;
+               /* name not really used in gpiod_get_index() */
+               sprintf(name, "GPIO%d", gpios_vector[j]);
+try_again:
+               dbg_printk(1, "Allocating gpio %s pin no %d\n", name, gpios_vector[j]);
+               desc = gpiod_get_index(board->gpib_dev, name, gpios_vector[j], GPIOD_IN);
+
+               if (IS_ERR(desc)) {
+                       gpiod_remove_lookup_table(lookup_table);
+                       table_index++;
+                       lookup_table = lookup_tables[table_index];
+                       if (lookup_table) {
+                               dbg_printk(1, "Allocation failed,  now  using table_index %d\n",
+                                          table_index);
+                               lookup_table->dev_id = dev_name(board->gpib_dev);
+                               gpiod_add_lookup_table(lookup_table);
+                               goto try_again;
+                       }
+                       dbg_printk(0, "Unable to obtain gpio descriptor for pin %d error %ld\n",
+                                  gpios_vector[j], PTR_ERR(desc));
+                       error = true;
+                       break;
+               }
+               all_descriptors[j] = desc;
+       }
+
+       if (error) { /* undo what already done */
+               release_gpios();
+               retval = -1;
+       }
+       if (lookup_table)
+               gpiod_remove_lookup_table(lookup_table);
+       // Initialize LED trigger
+       led_trigger_register_simple("gpib", &ledtrig_gpib);
+       return retval;
+}
+
+static void bb_detach(gpib_board_t *board)
+{
+       struct bb_priv *priv = board->private_data;
+
+       dbg_printk(2, "Enter with data %p\n", board->private_data);
+       if (!board->private_data)
+               return;
+
+       led_trigger_unregister_simple(ledtrig_gpib);
+
+       bb_free_irq(board, &priv->irq_DAV, NAME "_DAV");
+       bb_free_irq(board, &priv->irq_NRFD, NAME "_NRFD");
+       bb_free_irq(board, &priv->irq_NDAC, NAME "_NDAC");
+       bb_free_irq(board, &priv->irq_SRQ, NAME "_SRQ");
+
+       if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */
+               gpiod_set_value(YOGA_ENABLE, 0);
+       }
+
+       release_gpios();
+
+       dbg_printk(2, "detached board: %d\n", board->minor);
+       dbg_printk(0, "NRFD: idle %d, seq %d,  NDAC: idle %d, seq %d  DAV: idle %d  seq: %d  all: %ld",
+                  priv->nrfd_idle, priv->nrfd_seq,
+                  priv->ndac_idle, priv->ndac_seq,
+                  priv->dav_idle, priv->dav_seq, priv->all_irqs);
+
+       free_private(board);
+}
+
+static int bb_attach(gpib_board_t *board, const gpib_board_config_t *config)
+{
+       struct bb_priv *priv;
+       int retval = 0;
+
+       dbg_printk(2, "%s\n", "Enter ...");
+
+       board->status = 0;
+
+       if (allocate_private(board))
+               return -ENOMEM;
+       priv = board->private_data;
+       priv->direction = -1;
+       priv->t1_delay = 2000;
+       priv->listener_state = listener_idle;
+       priv->talker_state = talker_idle;
+
+       sn7516x = sn7516x_used;
+       if (strcmp(PINMAP_0, pin_map) == 0) {
+               if (!sn7516x) {
+                       gpios_vector[&(PE) - &all_descriptors[0]] = -1;
+                       gpios_vector[&(DC) - &all_descriptors[0]] = -1;
+                       gpios_vector[&(TE) - &all_descriptors[0]] = -1;
+               }
+       } else if (strcmp(PINMAP_1, pin_map) == 0) {
+               if (!sn7516x) {
+                       gpios_vector[&(PE) - &all_descriptors[0]] = -1;
+                       gpios_vector[&(DC) - &all_descriptors[0]] = -1;
+                       gpios_vector[&(TE) - &all_descriptors[0]] = -1;
+               }
+               gpios_vector[&(REN) - &all_descriptors[0]] = 0; /* 27 -> 0 REN on GPIB pin 0 */
+       } else if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */
+               sn7516x = 0;
+               gpios_vector[&(D03) - &all_descriptors[0]] = YOGA_D03_pin_nr;
+               gpios_vector[&(D04) - &all_descriptors[0]] = YOGA_D04_pin_nr;
+               gpios_vector[&(D05) - &all_descriptors[0]] = YOGA_D05_pin_nr;
+               gpios_vector[&(D06) - &all_descriptors[0]] = YOGA_D06_pin_nr;
+               gpios_vector[&(PE)  - &all_descriptors[0]] = -1;
+               gpios_vector[&(DC)  - &all_descriptors[0]] = -1;
+               gpios_vector[&(ACT_LED)  - &all_descriptors[0]] = -1;
+       } else {
+               dbg_printk(0, "Unrecognized pin mapping.\n");
+               goto bb_attach_fail;
+       }
+       dbg_printk(0, "Using pin map \"%s\" %s\n", pin_map, (sn7516x) ?
+                  " with SN7516x driver support" : "");
+
+       if (allocate_gpios(board))
+               goto bb_attach_fail;
+
+/* Configure SN7516X control lines.
+ * drive ATN, IFC and REN as outputs only when master
+ * i.e. system controller. In this mode can only be the CIC
+ * When not master then enable device mode ATN, IFC & REN as inputs
+ */
+       if (sn7516x) {
+               gpiod_direction_output(DC, 0);
+               gpiod_direction_output(TE, 1);
+               gpiod_direction_output(PE, 1);
+       }
+
+       if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA: enable level shifters */
+               gpiod_direction_output(YOGA_ENABLE, 1);
+       }
+
+       spin_lock_init(&priv->rw_lock);
+
+       /* request DAV interrupt for read */
+       if (bb_get_irq(board, NAME "_DAV", DAV, &priv->irq_DAV, bb_DAV_interrupt, NULL,
+                      IRQF_TRIGGER_NONE))
+               goto bb_attach_fail_r;
+
+       /* request NRFD interrupt for write */
+       if (bb_get_irq(board, NAME "_NRFD", NRFD, &priv->irq_NRFD, bb_NRFD_interrupt, NULL,
+                      IRQF_TRIGGER_NONE))
+               goto bb_attach_fail_r;
+
+       /* request NDAC interrupt for write */
+       if (bb_get_irq(board, NAME "_NDAC", NDAC, &priv->irq_NDAC, bb_NDAC_interrupt, NULL,
+                      IRQF_TRIGGER_NONE))
+               goto bb_attach_fail_r;
+
+       /* request SRQ interrupt for Service Request */
+       if (bb_get_irq(board, NAME "_SRQ", SRQ, &priv->irq_SRQ, bb_SRQ_interrupt, NULL,
+                      IRQF_TRIGGER_NONE))
+               goto bb_attach_fail_r;
+
+       ENABLE_IRQ(priv->irq_SRQ, IRQ_TYPE_EDGE_FALLING);
+
+       dbg_printk(0, "attached board %d\n", board->minor);
+       goto bb_attach_out;
+
+bb_attach_fail_r:
+       release_gpios();
+bb_attach_fail:
+       retval = -1;
+bb_attach_out:
+       return retval;
+}
+
+gpib_interface_t bb_interface = {
+name:                    NAME,
+attach : bb_attach,
+detach : bb_detach,
+read : bb_read,
+write : bb_write,
+command : bb_command,
+take_control : bb_take_control,
+go_to_standby : bb_go_to_standby,
+request_system_control : bb_request_system_control,
+interface_clear : bb_interface_clear,
+remote_enable : bb_remote_enable,
+enable_eos : bb_enable_eos,
+disable_eos : bb_disable_eos,
+parallel_poll : bb_parallel_poll,
+parallel_poll_configure : bb_parallel_poll_configure,
+parallel_poll_response : bb_parallel_poll_response,
+line_status : bb_line_status,
+update_status : bb_update_status,
+primary_address : bb_primary_address,
+secondary_address : bb_secondary_address,
+serial_poll_response : bb_serial_poll_response,
+serial_poll_status : bb_serial_poll_status,
+t1_delay : bb_t1_delay,
+return_to_local : bb_return_to_local,
+};
+
+static int __init bb_init_module(void)
+{
+       gpib_register_driver(&bb_interface, THIS_MODULE);
+
+       dbg_printk(0, "module loaded with pin map \"%s\"%s\n",
+                  pin_map, (sn7516x_used) ? " and SN7516x driver support" : "");
+       return 0;
+}
+
+static void __exit bb_exit_module(void)
+{
+       dbg_printk(0, "module unloaded!");
+
+       gpib_unregister_driver(&bb_interface);
+}
+
+module_init(bb_init_module);
+module_exit(bb_exit_module);
+
+/***************************************************************************
+ *                                                                        *
+ * UTILITY Functions                                                      *
+ *                                                                        *
+ ***************************************************************************/
+inline long usec_diff(struct timespec64 *a, struct timespec64 *b)
+{
+       return ((a->tv_sec - b->tv_sec) * 1000000 +
+               (a->tv_nsec - b->tv_nsec) / 1000);
+}
+
+static inline int check_for_eos(struct bb_priv *priv, uint8_t byte)
+{
+       if (priv->eos_check)
+               return 0;
+
+       if (priv->eos_check_8) {
+               if (priv->eos == byte)
+                       return 1;
+       } else {
+               if (priv->eos_mask_7 == (byte & 0x7f))
+                       return 1;
+       }
+       return 0;
+}
+
+static void set_data_lines_output(void)
+{
+       gpiod_direction_output(D01, 1);
+       gpiod_direction_output(D02, 1);
+       gpiod_direction_output(D03, 1);
+       gpiod_direction_output(D04, 1);
+       gpiod_direction_output(D05, 1);
+       gpiod_direction_output(D06, 1);
+       gpiod_direction_output(D07, 1);
+       gpiod_direction_output(D08, 1);
+}
+
+static void set_data_lines(u8 byte)
+{
+       gpiod_set_value(D01, !(byte & 0x01));
+       gpiod_set_value(D02, !(byte & 0x02));
+       gpiod_set_value(D03, !(byte & 0x04));
+       gpiod_set_value(D04, !(byte & 0x08));
+       gpiod_set_value(D05, !(byte & 0x10));
+       gpiod_set_value(D06, !(byte & 0x20));
+       gpiod_set_value(D07, !(byte & 0x40));
+       gpiod_set_value(D08, !(byte & 0x80));
+}
+
+static u8 get_data_lines(void)
+{
+       u8 ret;
+
+       ret = gpiod_get_value(D01);
+       ret |= gpiod_get_value(D02) << 1;
+       ret |= gpiod_get_value(D03) << 2;
+       ret |= gpiod_get_value(D04) << 3;
+       ret |= gpiod_get_value(D05) << 4;
+       ret |= gpiod_get_value(D06) << 5;
+       ret |= gpiod_get_value(D07) << 6;
+       ret |= gpiod_get_value(D08) << 7;
+       return ~ret;
+}
+
+static void set_data_lines_input(void)
+{
+       gpiod_direction_input(D01);
+       gpiod_direction_input(D02);
+       gpiod_direction_input(D03);
+       gpiod_direction_input(D04);
+       gpiod_direction_input(D05);
+       gpiod_direction_input(D06);
+       gpiod_direction_input(D07);
+       gpiod_direction_input(D08);
+}
+
+static inline void SET_DIR_WRITE(struct bb_priv *priv)
+{
+       if (priv->direction == DIR_WRITE)
+               return;
+
+       gpiod_direction_input(NRFD);
+       gpiod_direction_input(NDAC);
+       set_data_lines_output();
+       gpiod_direction_output(DAV, 1);
+       gpiod_direction_output(EOI, 1);
+
+       if (sn7516x) {
+               gpiod_set_value(PE, 1);  /* set data lines to transmit on sn75160b */
+               gpiod_set_value(TE, 1);  /* set NDAC and NRFD to receive and DAV to transmit */
+       }
+
+       priv->direction = DIR_WRITE;
+}
+
+static inline void SET_DIR_READ(struct bb_priv *priv)
+{
+       if (priv->direction == DIR_READ)
+               return;
+
+       gpiod_direction_input(DAV);
+       gpiod_direction_input(EOI);
+
+       set_data_lines_input();
+
+       if (sn7516x) {
+               gpiod_set_value(PE, 0);  /* set data lines to receive on sn75160b */
+               gpiod_set_value(TE, 0);  /* set NDAC and NRFD to transmit and DAV to receive */
+       }
+
+       gpiod_direction_output(NRFD, 0);  // hold off the talker
+       gpiod_direction_output(NDAC, 0);  // data not accepted
+
+       priv->direction = DIR_READ;
+}