root/drivers/gpib/gpio/gpib_bitbang.c
// 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define dev_fmt pr_fmt
#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))                                      \
                        dev_dbg(board->gpib_dev, frm, ## __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>

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 ACT_LED_ON do {                                         \
                if (ACT_LED)                                    \
                        gpiod_direction_output(ACT_LED, 1);     \
        } while (0)
#define ACT_LED_OFF do {                                        \
                if (ACT_LED)                                    \
                        gpiod_direction_output(ACT_LED, 0);     \
        } while (0)

static 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 adapter uses a global enable for the buffer chips, re-using the TE pin */
#define YOGA_ENABLE TE

static 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_1 = {
        // 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("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_0 = {
        .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("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,
        NULL
};

/* 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;

        enum talker_function_state talker_state;
        enum listener_function_state listener_state;
};

static inline long usec_diff(struct timespec64 *a, struct timespec64 *b);
static void bb_buffer_print(struct gpib_board *board, 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, u8 byte);
static void set_atn(struct gpib_board *board, 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");
MODULE_DESCRIPTION("GPIB helper functions for bitbanging I/O");

/****  global variables  ****/
static int debug;
module_param(debug, int, 0644);

static char printable(char x)
{
        if (x < 32 || x > 126)
                return ' ';
        return x;
}

/***************************************************************************
 *                                                                         *
 * READ                                                                    *
 *                                                                         *
 ***************************************************************************/

static int bb_read(struct gpib_board *board, u8 *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)
{
        struct gpib_board *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(struct gpib_board *board, u8 *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(board, 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 = -ENOTCONN;
                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 {
                        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)
{
        struct gpib_board *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->w_cnt >= priv->length) { // test for missed NDAC end of transfer
                dev_err(board->gpib_dev, "Unexpected NRFD exit\n");
                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)
{
        struct gpib_board *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);

        gpiod_set_value(DAV, 1); // Data not available
        priv->dav_tx = 1;
        priv->phase = 510;

        if (priv->w_cnt >= priv->length) { // test for end of transfer
                priv->write_done = 1;
                priv->w_busy = 0;
                wake_up_interruptible(&board->wait);
        }

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)
{
        struct gpib_board  *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(struct gpib_board *board, u8 *buffer,
                      size_t length, size_t *bytes_written)
{
        int 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(struct gpib_board *board, 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 gpib_board *board, int atn_asserted)
{
        struct bb_priv *priv = board->private_data;

        if (priv->listener_state != listener_idle &&
            priv->talker_state != talker_idle) {
                dev_err(board->gpib_dev, "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;
                SET_DIR_WRITE(priv);  // need to be able to read bus NRFD/NDAC
        } 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);
}

static int bb_take_control(struct gpib_board *board, int synchronous)
{
        dbg_printk(2, "%d\n", synchronous);
        set_atn(board, 1);
        return 0;
}

static int bb_go_to_standby(struct gpib_board *board)
{
        dbg_printk(2, "\n");
        set_atn(board, 0);
        return 0;
}

static int bb_request_system_control(struct gpib_board *board, int request_control)
{
        struct bb_priv *priv = board->private_data;

        dbg_printk(2, "%d\n", request_control);
        if (!request_control)
                return -EINVAL;

        gpiod_direction_output(REN, 1); /* user space must enable REN if needed */
        gpiod_direction_output(IFC, 1); /* user space must toggle IFC if needed */
        if (sn7516x)
                gpiod_direction_output(DC, 0); /* enable ATN as output on SN75161/2 */

        gpiod_direction_input(SRQ);

        ENABLE_IRQ(priv->irq_SRQ, IRQ_TYPE_EDGE_FALLING);

        return 0;
}

static void bb_interface_clear(struct gpib_board *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;
                set_bit(CIC_NUM, &board->status);
        } else {
                gpiod_direction_output(IFC, 1);
        }
}

static void bb_remote_enable(struct gpib_board *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(struct gpib_board *board, u8 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(struct gpib_board *board)
{
        struct bb_priv *priv = board->private_data;

        dbg_printk(2, "\n");
        priv->eos_flags &= ~REOS;
}

static unsigned int bb_update_status(struct gpib_board *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(struct gpib_board *board, unsigned int address)
{
        dbg_printk(2, "%d\n", address);
        board->pad = address;
        return 0;
}

static int bb_secondary_address(struct gpib_board *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(struct gpib_board *board, u8 *result)
{
        return -ENOENT;
}

static void bb_parallel_poll_configure(struct gpib_board *board, u8 config)
{
}

static void bb_parallel_poll_response(struct gpib_board *board, int ist)
{
}

static void bb_serial_poll_response(struct gpib_board *board, u8 status)
{
}

static u8 bb_serial_poll_status(struct gpib_board *board)
{
        return 0; // -ENOENT;
}

static int bb_t1_delay(struct gpib_board *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(struct gpib_board *board)
{
}

static int bb_line_status(const struct gpib_board *board)
{
        int line_status = VALID_ALL;

        if (gpiod_get_value(REN) == 0)
                line_status |= BUS_REN;
        if (gpiod_get_value(IFC) == 0)
                line_status |= BUS_IFC;
        if (gpiod_get_value(NDAC) == 0)
                line_status |= BUS_NDAC;
        if (gpiod_get_value(NRFD) == 0)
                line_status |= BUS_NRFD;
        if (gpiod_get_value(DAV) == 0)
                line_status |= BUS_DAV;
        if (gpiod_get_value(EOI) == 0)
                line_status |= BUS_EOI;
        if (gpiod_get_value(_ATN) == 0)
                line_status |= BUS_ATN;
        if (gpiod_get_value(SRQ) == 0)
                line_status |= BUS_SRQ;

        dbg_printk(2, "status lines: %4x\n", line_status);

        return line_status;
}

/***************************************************************************
 *                                                                         *
 * Module Management                                                       *
 *                                                                         *
 ***************************************************************************/

static int allocate_private(struct gpib_board *board)
{
        board->private_data = kzalloc_obj(struct bb_priv);
        if (!board->private_data)
                return -ENOMEM;
        return 0;
}

static void free_private(struct gpib_board *board)
{
        kfree(board->private_data);
        board->private_data = NULL;
}

static int bb_get_irq(struct gpib_board *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) {
                dev_err(board->gpib_dev, "can't get IRQ for %s\n", name);
                return -1;
        }
        if (request_threaded_irq(*irq, handler, thread_fn, flags, name, board)) {
                dev_err(board->gpib_dev, "can't request IRQ for %s %d\n", name, *irq);
                *irq = 0;
                return -1;
        }
        DISABLE_IRQ(*irq);
        return 0;
}

static void bb_free_irq(struct gpib_board *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] = NULL;
                }
        }
}

static int allocate_gpios(struct gpib_board *board)
{
        int j;
        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[table_index];
        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) {
                                dev_err(board->gpib_dev, "Unable to obtain gpio descriptor for pin %d error %ld\n",
                                        gpios_vector[j], PTR_ERR(desc));
                                goto alloc_gpios_fail;
                        }
                        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;
                }
                all_descriptors[j] = desc;
        }

        gpiod_remove_lookup_table(lookup_table);

        return 0;

alloc_gpios_fail:
        release_gpios();
        return -1;
}

static void bb_detach(struct gpib_board *board)
{
        struct bb_priv *priv = board->private_data;

        dbg_printk(2, "Enter with data %p\n", board->private_data);
        if (!board->private_data)
                return;

        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(struct gpib_board *board, const struct gpib_board_config *config)
{
        struct bb_priv *priv;
        int retval;

        dbg_printk(2, "%s\n", "Enter ...");

        board->status = 0;

        retval = allocate_private(board);
        if (retval)
                return retval;
        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;
        } else {
                dev_err(board->gpib_dev, "Unrecognized pin map %s\n", pin_map);
                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);
        }
/* Set main control lines to a known state */
        gpiod_direction_output(IFC, 1);
        gpiod_direction_output(REN, 1);
        gpiod_direction_output(_ATN, 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;

        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;
}

static struct gpib_interface 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)
{
        int result = gpib_register_driver(&bb_interface, THIS_MODULE);

        if (result) {
                pr_err("gpib_register_driver failed: error = %d\n", result);
                return result;
        }

        return 0;
}

static void __exit bb_exit_module(void)
{
        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, u8 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;
}