/* drvNi1014Gpib.c */
/***********************************************************************
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory, and the Regents of the University of
* California, as Operator of Los Alamos National Laboratory, and
* Berliner Elektronenspeicherring-Gesellschaft m.b.H. (BESSY).
* gpibCore is distributed subject to a Software License Agreement
* found in file LICENSE that is included with this distribution.
***********************************************************************/
/******************************************************************************
 *
 * TODO:
 * - Autodetect the need to use a bounce buffer (saves time on boards that have
 *   "malloc"-able A24 space.
 *
 * - Launch campaign against the use of National Instruments hardware.
 *
 ******************************************************************************
 *
 *Current Author: Benjamin Franksen
 *Original Author: John Winans
 *Date:   09-10-91
 *GPIB driver for the NI-1014 and NI-1014D VME cards.
 * Modification Log:
 * -----------------
 * .01  09-13-91        jrw     Written on Friday the 13th :-(
 *                		Much of the code in physIo() was stolen from
 *                		a gpib driver that came from Los Alamos.  It
 *                              referenced little more than National
 *                              Instruments and Bob Daly (from ANL) in its
 *                              credits.
 * .02	12-03-91	jrw	changed the setup of the ibLink and niLink structs
 * .03	01-21-92	jrw	moved task parameters into task_params.h
 * .04	01-31-92	jrw	added ibSrqLock code
 * .05	02-26-92	jrw	changed pnode references in the link task's
 *                		busy-list checking, was an endless loop
 * .06	04-10-92	jrw	moved the device configs into module_types.h
 * .07	08-02-92	mrk	Added call to taskwdInsert
 * .08	02-15-2001      mrk     Converted old drvGpib to NI support only
 *
 ******************************************************************************
 *
 * Notes:
 *  If 1014D cards are used, make sure that the W7 switch is set to LMR.
 *  The internals of the 1014D are such that the DMAC can NEVER be hard-reset
 *  unless the SYSRESET* vme line is asserted.  The LMR mode allows the
 *  initGpib() function to reset the DMAC properly.
 *
 *
 */

#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

/*vxWorks includes*/
#include <vxWorks.h>
#include <logLib.h>
#include <intLib.h>
#include <vxLib.h>
#include <sysLib.h>
#include <rebootLib.h>
#include <taskLib.h>
#include <iv.h>
#include <vme.h>
#include <cacheLib.h>
#include <wdLib.h>
#include <rngLib.h>

/* epics includes */
#include <dbDefs.h>
#include <devLib.h>
#include <ellLib.h>
#include <taskwd.h>
#include <errlog.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <cantProceed.h>
#include <epicsExport.h>
#include "drvHwGpibInterface.h"
#include "ni1014Registers.h"

#define STATIC static

typedef void (*VOIDFUNC) (void *);

int niIrqOneShot = 0;	/* Used for a one shot peek at the DMAC */
#define	STD_ADDRESS_MODE D_SUP|D_S24	/* mode to use when DMAC accesses RAM */

/*
 * The bounce buffer is where the DMA IO operation(s) take place.  If it
 * is not large enough, it will be reallocated at that time.  However,
 * It should be made large enough such this need not happen.
 */
#define DEFAULT_BOUNCE_BUFFER_SIZE	10*1024
STATIC int defaultTimeout;	/* in 60ths, for GPIB timeouts */
int niTimeoutSquelch = 0;	/* Used to quiet timeout msgs during polling */
int niDebug=0;
int niSrqDebug=0;
/* DMA timing bus error problem debugging in ni1014DMA */
int niDbaDebug = 0;	/* Turns on DMA debug messages from this driver */
int niDbaTimingError = 0;	/* count "bad memProbes"/call of ni1014DMA */
int niDbaTimingErrorTotal = 0;	/* count total "bad memProbes" in ni1014DMA */
int niDbaMaxError = 0;	/* max # bad calls per call of ni1014DMA */

/******************************************************************************
 *
 * This structure is used to hold the hardware-specific information for a
 * single GPIB link.  There is one for each link constructed in initGpib().
 *
 ******************************************************************************/
typedef struct niLink {
    ibLink          head; /* device independent part of link structure */
    size_t          base;
    unsigned int    vector;
    unsigned int    level;
    uchar           tmoFlag;/* timeout has occurred */
    epicsEventId    ioSem;   /* DMA I/O operation complete */
    volatile struct ibregs   *pibregs;	/* pointer to board registers */
    volatile DmaStuffStruct  *DmaStuff;
    uchar            r_isr1;
    uchar            r_isr2;
    int             first_read;
    unsigned long   cmdSpins;	/* total taskDelays while in niWriteCmd() */
    unsigned long   maxSpins;	/* most taskDelays in one call to niWriteCmd() */
    uchar            *A24BounceBuffer;	/* Where to DMA to */
    unsigned long   A24BounceSize;
} niLink;

typedef struct niLinkNode {
    ELLNODE node;
    niLink *pniLink;
}niLinkNode;

STATIC ELLLIST niLinkList;

STATIC void ni1014RebootHandler(void);
STATIC int ni1014DumpDmac(niLink *pniLink);
void ni1014IH(niLink *pniLink); /*Interrupt Handler*/
void ni1014IHError(niLink *pniLink);
STATIC int ni1014Resid(niLink *pniLink);
STATIC int ni1014TimeoutHandler(niLink *pniLink);
STATIC void ni1014WriteLong(volatile long *loc, int val);
STATIC long ni1014ReadLong(volatile long *loc);
STATIC int ni1014AllocA24(niLink *pniLink, int size);
STATIC int ni1014AssignA24(niLink *pniLink,unsigned short *cnt);
STATIC int ni1014DMA(int dir, niLink * pniLink,
    char *buffer, int length, int timeout);
STATIC long niWriteCmd(ibLink * pibLink, char *buffer, int length);
STATIC int ni1014ConfigFirstPass(niLink *pniLink);
STATIC int ni1014ConfigSecondPass(niLink *pniLink);
/* The drvHwGpibEt methods*/
STATIC long niGenLink(ibLink * pibLink, int pass);
STATIC long niDestroyLink(ibLink * pibLink);
STATIC long niReportLink(ibLink * pibLink, int level);
STATIC long niProbe(ibLink * pibLink);
STATIC long niRead(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout, int eos);
STATIC long niWrite(ibLink * pibLink,int gpibAddr, int secondary,
    char *buffer, int length, int timeout);
STATIC long niAddressedCmd(ibLink *pibLink,int gpibAddr,int secondary,
        char *buffer,int length,int timeout);
STATIC long niUniversialCmd(ibLink *pibLink,int cmd);
STATIC long niIfc(ibLink *pibLink);
STATIC long niRen(ibLink *pibLink,int onOff);
STATIC long niSrqStatus(ibLink * pibLink);
STATIC long niSrqEnable(ibLink * pibLink);
STATIC long niSrqDisable(ibLink * pibLink);
STATIC long niSerialPollBegin(ibLink *pibLink);
STATIC long niSerialPoll(ibLink *pibLink,int gpibAddr, int secondary,
    int timeout);
STATIC long niSerialPollEnd(ibLink *pibLink);

STATIC void ni1014RebootHandler(void)
{
    uchar probeValue = 0;
    niLinkNode *pniLinkNode;
    pniLinkNode = (niLinkNode *)ellFirst(&niLinkList);
    while(pniLinkNode) {
        niLink *pniLink = pniLinkNode->pniLink;
        probeValue = 0;
        vxMemProbe((char *)&(pniLink->pibregs->ch1.ccr),
            WRITE, 1, (char *) &probeValue);
        vxMemProbe((char *)&(pniLink->pibregs->ch0.ccr),
            WRITE, 1, (char *) &probeValue);
        taskDelay(1);	/* Let it settle */
        vxMemProbe((char *)&(pniLink->pibregs->cfg2),
            WRITE, 1, (char *) &probeValue);
        probeValue = D_LMR;
        vxMemProbe((char *)&(pniLink->pibregs->cfg2),
            WRITE, 1, (char *) &probeValue);
        pniLinkNode = (niLinkNode *)ellNext(&pniLinkNode->node);
    }
    taskDelay(2);
    return;
}

STATIC int ni1014DumpDmac(niLink *pniLink)
{
    logMsg("ch0: ccr=%2.2X csr=%2.2X cer=%2.2X mtc=%4.4X mar=%8.8X btc=%4.4X",
           pniLink->pibregs->ch0.ccr & 0xff,
           pniLink->pibregs->ch0.csr & 0xff,
           pniLink->pibregs->ch0.cer & 0xff,
           pniLink->pibregs->ch0.mtc & 0xffff,
           ni1014ReadLong(&(pniLink->pibregs->ch0.mar)),
           pniLink->pibregs->ch0.btc & 0xffff);
    logMsg(" bar=%8.8X\n",
        ni1014ReadLong(&(pniLink->pibregs->ch0.bar)),0,0,0,0,0);
    logMsg("ch1: ccr=%2.2X csr=%2.2X cer=%2.2X mtc=%4.4X mar=%8.8X btc=%4.4X",
           pniLink->pibregs->ch1.ccr & 0xff,
           pniLink->pibregs->ch1.csr & 0xff,
           pniLink->pibregs->ch1.cer & 0xff,
           pniLink->pibregs->ch1.mtc & 0xffff,
           ni1014ReadLong(&(pniLink->pibregs->ch1.mar)),
           pniLink->pibregs->ch1.btc & 0xffff);
    logMsg(" bar=%8.8X\n",
        ni1014ReadLong(&(pniLink->pibregs->ch1.bar)),0,0,0,0,0);
    return (0);
}

/* Interrupt handler for all normal DMAC interrupts.
 *
 * This is invoked at the termination of a DMA operation or if the TLC
 * requests an un-masked interrupt (typically SRQ from the GPIB bus.)
 *
 * Keep in mind that channel0's interrupts are related to the SRQs and that
 * the ints from channel1 are related to the DMA operations completing.
 *
 * Note:
 *  The isr2 status should always be read first since reading isr1 can reset
 *  some of the isr2 status.
 */
void ni1014IH(niLink *pniLink)
{
    if (niDebug)
        logMsg("GPIB interrupt from link %d\n", pniLink->head.link,0,0,0,0,0);
    if (pniLink->level == 4)	/* gotta ack ourselves on HK boards */
        sysBusIntAck(pniLink->level);
    if (niIrqOneShot) {
        ni1014DumpDmac(pniLink);
        niIrqOneShot--;
    }
    /* Check the DMA error status bits first */
    if (pniLink->pibregs->ch0.csr & D_ERR || pniLink->pibregs->ch1.csr & D_ERR) {
        logMsg("GPIB error during DMA from link %d\n", pniLink->head.link,0,0,0,0,0);
        /* read the status regs to clear any int status from the TLC */
        pniLink->r_isr2 |= pniLink->pibregs->isr2;
        pniLink->r_isr1 |= pniLink->pibregs->isr1;
        ni1014DumpDmac(pniLink);
        logMsg("r_isr1=%2.2X r_isr2=%2.2X\n",
               pniLink->r_isr1 & 0xff,
               pniLink->r_isr2 & 0xff,
               0,0,0,0);
        pniLink->pibregs->ch0.csr = ~D_PCLT;	/* Keep srq int status */
        pniLink->pibregs->ch1.csr = D_CLEAR;
        pniLink->pibregs->imr1 = 0;
        pniLink->pibregs->imr2 = 0;
        /* No semaphores are given in here because we don't know why we got */
        /* here.  It is best to let I/O time out if any was going on. */
        return;
    }
    /* channel 0 PCL status is the SRQ line for the link */
    if ((pniLink->pibregs->ch0.csr) & D_PCLT) {
        pniLink->pibregs->ch0.csr = D_PCLT;	/* Reset srq status */
        pniLink->head.srqIntFlag = 1;
        if (niDebug || niSrqDebug)
            errlogPrintf("GPIB SRQ interrupt on link %d\n", pniLink->head.link);
        drvGpibSrqHappened(&pniLink->head);
        return;
    }
    if (pniLink->tmoFlag == TRUE) return; /*let timeout win*/
    if (pniLink->pibregs->ch1.csr & D_PCLT) {
        if (niDebug)
            errlogPrintf("GPIB DMA completion interrupt from link %d\n",
                pniLink->head.link);
        /* read the status regs to clear any int status from the TLC */
        /* changed these to = from |= because they never got cleared! */
        pniLink->r_isr2 = pniLink->pibregs->isr2;
        pniLink->r_isr1 = pniLink->pibregs->isr1;
        if (pniLink->pibregs->ch1.csr & D_COC) {
            /* this should not be set because we ALWAYS ask for 1 too */
            /* many bytes to be transfered.  See 1014 docs on ints */
            logMsg("GPIB COC bit set after DMA on channel 1 link %d\n"
                , pniLink->head.link,0,0,0,0,0);
        }
        /* DMA complete via sync detect */
        pniLink->pibregs->imr1 = 0;
        pniLink->pibregs->imr2 = 0;
        pniLink->pibregs->ch1.csr = D_CLEAR;
        /* Leave Channel 0's ints alone since it did not generate the
         * interrupt */
        epicsEventSignal(pniLink->ioSem);
        return;
    }
    return;
}

/******************************************************************************
 *
 * An interrupt handler that catches the DMAC error interrupts.  These should
 * never occur.
 *
 ******************************************************************************/
void ni1014IHError(niLink *pniLink)
{
    logMsg("GPIB error interrupt generated on link %d\n",
        pniLink->head.link,0,0,0,0,0);
    ni1014DumpDmac(pniLink);
    pniLink->pibregs->ch0.ccr = D_SAB;
    pniLink->pibregs->ch1.ccr = D_SAB;
    return;
}


/******************************************************************************
 *
 * This function is used to figure out the difference in the transfer-length
 * requested in a read or write request, and that actually transfered.
 *
 ******************************************************************************/
STATIC int ni1014Resid(niLink *pniLink)
{
    register int cnt;
    cnt = pniLink->pibregs->ch0.mtc;
    if (pniLink->pibregs->ch1.mtc == 2 || cnt)	/* add one if carry-cycle */
    cnt++;  /* never started */
    return (cnt);
}
/******************************************************************************
 *
 * Sometimes we have to make sure that regs on the GPIB board are accessed as
 * 16-bit values.  This function writes out a 32-bit value in 2 16-bit pieces.
 *
 ******************************************************************************/
STATIC void ni1014WriteLong(volatile long *loc, int val)
{
    unsigned short *ptr = (unsigned short*)loc;
    *ptr = val >> 16;
    *(ptr + 1) = val & 0xffff;
}
STATIC long ni1014ReadLong(volatile long *loc)
{
    unsigned short *ptr = (unsigned short *)loc;
    int val;
    val = (unsigned long) (*ptr << 16) + (unsigned long) (*(ptr + 1) & 0xffff);
    return (val);
}

STATIC int ni1014AllocA24(niLink *pniLink, int size)
{
    if(pniLink->A24BounceBuffer) devLibA24Free(pniLink->A24BounceBuffer);
    if ((pniLink->A24BounceBuffer = devLibA24Malloc(size)) == NULL) {
        errlogPrintf("ni1014DMA ran out of A24 memory!");
        pniLink->A24BounceSize = 0;
        pniLink->A24BounceBuffer = NULL;
        return (-1);
    }
    pniLink->A24BounceSize = size;
    cacheFlush(DATA_CACHE,pniLink->A24BounceBuffer,pniLink->A24BounceSize);
    if (niDebug > 5)
        errlogPrintf("ni1014AllocA24 new A24 bounceBuffer at 0x%8.8X\n",pniLink->A24BounceBuffer);
    return(0);
}

STATIC int ni1014AssignA24(niLink *pniLink,unsigned short *cnt)
{
    volatile struct ibregs *b = pniLink->pibregs;
    int temp_addr;

    /* setup channel 1 (carry cycle) */
    if(sysLocalToBusAdrs(VME_AM_STD_SUP_DATA,
        (char *)&(pniLink->DmaStuff->cc_byte),
        (void *)&(pniLink->DmaStuff->cc_array.cc_ccb)) == -1) return (-1);
    if(sysLocalToBusAdrs(VME_AM_STD_SUP_DATA,
        (char *)&(pniLink->A24BounceBuffer[*cnt - 1]),
        (void *)&temp_addr) == -1) return (-1);
    pniLink->DmaStuff->cc_array.cc_n_1addr_HI = temp_addr >> 16;
    pniLink->DmaStuff->cc_array.cc_n_1addr_LO = temp_addr & 0xffff;
    cacheFlush (DATA_CACHE, (void *)&pniLink->DmaStuff->cc_array, sizeof(struct cc_ary));
    *cnt -= 1;
    if(sysLocalToBusAdrs(VME_AM_STD_SUP_DATA,
        (void *)&(pniLink->DmaStuff->cc_array),
        (void *)&temp_addr) == -1) return (-1);
    if (niDebug > 5)
        errlogPrintf("&cc_array=%8.8X, ", temp_addr);
    ni1014WriteLong(&b->ch1.bar, temp_addr);
    b->ch1.btc = 2;
    /* setup channel 0 (main transfer) */
    b->ch0.mtc = *cnt ? *cnt : 1;
    if(sysLocalToBusAdrs(VME_AM_STD_SUP_DATA,
        (char *)pniLink->A24BounceBuffer,
        (void *)&temp_addr) == -1) return (-1);
    if (niDebug > 5)
        errlogPrintf("pniLink->A24BounceBuffer=%8.8X\n", temp_addr);
    ni1014WriteLong(&b->ch0.mar, temp_addr);
    return(0);
}

/******************************************************************************
 *
 * This routine does DMA based I/O with the GPIB bus.  It sets up the NI board's
 * DMA registers, initiates the transfer and waits for it to complete.  It uses
 * a timeout in case the transfer dies.  It returns 0, or -1
 * depending on if the transfer succeeds or not.
 *
 ******************************************************************************/
STATIC int ni1014DMA(int dir, niLink * pniLink,
    char *buffer, int length, int timeout)
{
    int status = -1;
    unsigned short cnt;
    volatile struct ibregs *b = pniLink->pibregs;
    char w_imr2;
    int tmoTmp;

    if (length > 0x0fffe) {
        errlogPrintf("NI-1014 max length (65535) exceeded");
        return (-1);
    }
    cnt = length;
    if (pniLink->A24BounceSize < cnt) {
        if(ni1014AllocA24(pniLink,cnt)) return(-1);
    }
    b->auxmr = AUX_GTS;	/* go to standby mode */
    b->ch1.ccr = D_SAB;	/* halt channel activity */
    b->ch0.ccr = D_SAB;	/* halt channel activity */
    b->ch1.csr = D_CLEAR;
    b->ch0.csr = D_CLEAR & ~D_PCLT;
    b->imr2 = 0;	/* set these bits last */
    status = 0;
    if (dir == READ) {
        if (pniLink->first_read == 0)
            b->auxmr = AUX_FH;	/* finish handshake */
        else
            pniLink->first_read = 0;
        b->auxmr = AUXRA | HR_HLDE;	/* hold off on end */
        if (cnt != 1)
            pniLink->DmaStuff->cc_byte = AUXRA | HR_HLDA;/* (cc) holdoff on all */
        else
            pniLink->DmaStuff->cc_byte = b->auxmr = AUXRA | HR_HLDA;/* last byte, do now */
        b->ch0.ocr = D_DTM | D_XRQ;
        /* make sure I only alter the 1014D port-specific fields here! */
        b->cfg1 = D_ECC | D_IN | (pniLink->level << 5) | D_BRG3 | D_DBM;
        b->ch1.ocr = D_DTM | D_ACH | D_XRQ;
        b->ch1.ocr = D_DTM | D_ACH | D_XRQ;
        /* enable interrupts and dma */
        b->imr1 = HR_ENDIE;
        w_imr2 = HR_DMAI;
    } else {	/* (dir == WRITE) */
        /* We will be writing, copy data into the bounce buffer */
        memcpy(pniLink->A24BounceBuffer, buffer, cnt);
        cacheFlush(DATA_CACHE, pniLink->A24BounceBuffer, cnt);
        if (cnt != 1)
            pniLink->DmaStuff->cc_byte = AUX_SEOI;/* send EOI with last byte */
        else
            b->auxmr = AUX_SEOI;/* last byte, do it now */
        b->ch0.ocr = D_MTD | D_XRQ;
        /* make sure I only alter the 1014D port-specific fields here! */
        b->cfg1 = D_ECC | D_OUT | (pniLink->level << 5) | D_BRG3 | D_DBM;
        b->ch1.ocr = D_MTD | D_ACH | D_XRQ;
        /* enable interrupts and dma */
        b->imr1 = 0;
        w_imr2 = HR_DMAO;
    }	/* dir == READ) */
    if(ni1014AssignA24(pniLink,&cnt)) return(-1);
    /* setup GPIB response timeout handler */
    if (timeout == 0) timeout = defaultTimeout;
    pniLink->tmoFlag = FALSE;	/* assume no timeout */
    /* start dma (ch1 first) */
    if (cnt)
        b->ch1.ccr = D_EINT | D_SRT;	/* enable interrupts */
    else
        b->ch1.ccr = D_EINT;
    b->ch0.ccr = D_SRT;
    b->imr2 = w_imr2;	/* this must be done last */
    /* check for error in DMAC initialization */
    if ((b->ch0.csr & D_ERR) || (b->ch1.csr & D_ERR)) {
        errlogPrintf("DMAC error initialization on link %d.\n",
            pniLink->head.link);
        return (-1);
    }
    if (cnt) {
        if (niDebug == 1)
            errlogPrintf("Link %d waiting for DMA int or WD timeout.\n",
                pniLink->head.link);
        if (epicsEventWaitWithTimeout(pniLink->ioSem, timeout/1000.0) == epicsEventWaitTimeout)
            pniLink->tmoFlag = TRUE;
    } else if (b->ch0.mtc) {
        if (niDebug == 1)
            errlogPrintf("wd cnt =0 wait\n");
        tmoTmp = 0;
        while (b->ch0.mtc) {
            taskDelay(1);
            if (++tmoTmp == timeout) {
                pniLink->tmoFlag = TRUE;
                break;
            }
        }
    }
    if (pniLink->tmoFlag == TRUE) {
        status = -1;
        /* reset */
        pniLink->r_isr2 |= pniLink->pibregs->isr2;
        pniLink->r_isr1 |= pniLink->pibregs->isr1;
        pniLink->pibregs->imr1 = 0;
        pniLink->pibregs->imr2 = 0;
        pniLink->pibregs->ch1.csr = D_CLEAR;
        if (!niTimeoutSquelch)
            errlogPrintf("TIMEOUT GPIB DEVICE on link %d\n",pniLink->head.link);
    } else {
        status = 0;
        if (b->ch0.csr & D_ERR) {
            errlogPrintf("DMAC error on link %d, channel 0 = %x\n",
                pniLink->head.link, b->ch0.cer);
            status = -1;
        }
        if (b->ch1.csr & D_ERR) {
            errlogPrintf("DMAC error on link %d, channel 1 = %x\n",
                pniLink->head.link, b->ch1.cer);
            status = -1;
        }
    }
    /* DMA transfer complete.  Reset as per instructions in GPIB 'Programming
     * Considerations' 5-14 */
/* BUG -- Should halt and spin a while before aborting (NI recommendation) */
    b->ch0.ccr = D_SAB;	/* halt channel activity */
    b->ch0.csr = D_CLEAR & ~D_PCLT;
    b->ch1.ccr = D_SAB;
    b->ch1.csr = D_CLEAR;
    b->imr2 = 0;
    /* make sure I only alter the 1014D port-specific fields here! */
    b->cfg1 = (pniLink->level << 5) | D_BRG3 | D_DBM;
    if (dir == READ) {	/* Copy data from the bounce buffer to the user's
                       * buffer */
        cacheInvalidate(DATA_CACHE, pniLink->A24BounceBuffer, cnt);
        memcpy(buffer, pniLink->A24BounceBuffer, cnt);
    }
    return (status);
}

/* The controller is placed in the active state prior to the outputting of
 * the first command.
 * Before calling niWriteCmd() the first time, an niGpibIoctl(IBIFC) call must
 * be made to init the bus and enable the interface card.
 */
#define	TOOLONG	100	/* how many times to try to send the same byte */
#define	IDELAY	1000	/* how long to busy wait while sending a byte */
STATIC long niWriteCmd(ibLink * pibLink, char *buffer, int length)
{
    niLink *pniLink = (niLink *) pibLink;
    int iDelay;	/* how long to spin before doing a taskWait */
    int tooLong;	/* how long should I tolerate waiting */
    int lenCtr = length;
    unsigned spins = 0;	/* how many taskDelay() calls made in this function */

    if (niDebug)
        printf("niWriteCmd(%d,  %d): command string >%s<\n",
               pibLink->link,  length, buffer);
    tooLong = TOOLONG;	/* limit to wait for ctrlr's command buffer */
    pniLink->pibregs->auxmr = AUX_TCA;	/* take control of the bus */
    while (lenCtr) {
        pniLink->r_isr2 &= ~HR_CO;
        iDelay = IDELAY;	/* wait till the ctlr is ready */
        while(iDelay) {
            uchar isr2 = pniLink->pibregs->isr2;
            pniLink->r_isr2 |= isr2;
            if(isr2&HR_CO) break;
            iDelay--;
        }
        if (iDelay) {
            pniLink->pibregs->cdor = *buffer++;	/* output a byte */
            lenCtr--;
            tooLong = TOOLONG;	/* reset the limit again */
        } else {
            if (!(tooLong--)) {
                /* errMsg() */
                errlogPrintf("niWriteCmd(%d, 0x%8.8X, %d):"
                       " Timeout writing command >%s<\n",
                       pniLink->head.link, buffer, length, buffer);
                pniLink->pibregs->auxmr = AUX_GTS;
                if (spins > pniLink->maxSpins) pniLink->maxSpins = spins;
                return (-1);
            }
            spins++;
            pniLink->cmdSpins++;
            taskDelay(1);	/* ctlr is taking too long */
        }
    }
    tooLong = TOOLONG;
    while (tooLong--) {
        pniLink->r_isr2 &= ~HR_CO;
        iDelay = IDELAY;	/* wait till the ctlr is ready */
        while (iDelay && (((pniLink->r_isr2 |= pniLink->pibregs->isr2) & HR_CO) == 0))
            iDelay--;
        if (iDelay) {
            pniLink->pibregs->auxmr = AUX_GTS;
            if (spins > pniLink->maxSpins) pniLink->maxSpins = spins;
            return (length);
        } else {
            spins++;
            pniLink->cmdSpins++;
            taskDelay(1);
        }
    }
    /* errMsg() */
    errlogPrintf("niWriteCmd(%d, 0x%8.8X, %d): Timeout after writing command >%s<\n",
           pniLink->head.link, buffer, length, buffer);
    pniLink->pibregs->auxmr = AUX_GTS;
    if (spins > pniLink->maxSpins) pniLink->maxSpins = spins;
    return (-1);
}

STATIC int ni1014ConfigFirstPass(niLink *pniLink)
{
    ibregs *pibregs = 0;
    long status;
    uchar probeValue;

    status = devRegisterAddress("drvNi1014", atVMEA16, pniLink->base,
        sizeof(struct ibregs), (volatile void **)&pibregs);
    if(status) {
        errlogPrintf("ni1014Config devRegisterAddress failed\n",pniLink->base);
        return(-1);
    }
    /* When probing, send out a reset signal to reset the DMAC and the TLC */
    probeValue = D_LMR | D_SFL;
    if (vxMemProbe((char *)&(pibregs->cfg2),WRITE,1,(char *)&probeValue) < 0) {
        /* no GPIB board present here */
        errlogPrintf("NI1014 gpib link %d probe of address %p failed\n",
            pniLink->head.link,pibregs);
        return(-1);
    }
    taskDelay(1);	/* Wait at least 10 msec before continuing */
    pibregs->cfg2 = D_SFL;
    taskDelay(1);	/* Wait at least 10 msec before continuing */
    pibregs->auxmr = AUX_CR;
    taskDelay(1);
    pibregs->cfg2 = D_SFL | D_SC;	/* put board in operating mode */

    pniLink->A24BounceSize = DEFAULT_BOUNCE_BUFFER_SIZE;
    pniLink->A24BounceBuffer = devLibA24Malloc(DEFAULT_BOUNCE_BUFFER_SIZE);
    if(!pniLink->A24BounceBuffer) {
        errlogPrintf("ni1014DMA ran out of A24 memory!");
        return (-1);
    }
    pniLink->pibregs = pibregs;
    pniLink->ioSem = epicsEventMustCreate(epicsEventEmpty);
    pniLink->tmoFlag = 0;
    pniLink->cmdSpins = 0;
    pniLink->maxSpins = 0;
    pniLink->DmaStuff = (DmaStuffStruct *)
        devLibA24Malloc(sizeof(DmaStuffStruct));
    if(!(pniLink->DmaStuff)) {
        errlogPrintf("gpibNI1014 Can't malloc A24 memory!\n");
        return (-1);
    }
    pniLink->DmaStuff->cc_array.cc_ccb = 0;/* DMAC chaining structure */
    pniLink->DmaStuff->cc_array.cc_ONE = 1;
    pniLink->DmaStuff->cc_array.cc_n_1addr_HI = 0;
    pniLink->DmaStuff->cc_array.cc_n_1addr_LO = 0;
    pniLink->DmaStuff->cc_array.cc_TWO = 2;
    pniLink->first_read = 1;	/* used in physIo() */
    return 0;
}

STATIC int ni1014ConfigSecondPass(niLink *pniLink)
{
    uchar s;
    long status;

    /* attach the interrupt handler routines */
    status = devConnectInterrupt(intVME,pniLink->vector,
        (VOIDFUNC)ni1014IH,(void *)pniLink);
    if(status) errMessage(status,"niGenLink:devConnectInterrupt");
    status = devConnectInterrupt(intVME,pniLink->vector+1,
        (VOIDFUNC)ni1014IHError,(void *)pniLink);
    if(status) errMessage(status,"niGenLink:devConnectInterrupt");
    /* should have interrups running before I do any I/O */
    status = devEnableInterruptLevel(intVME,pniLink->level);
    if(status) errMessage(status,"niGenLink:devEnableInterruptLevel");
    /* 7210 TLC setup */
    /* clear status regs by reading them */
    s = pniLink->pibregs->cptr;
    pniLink->r_isr1 = pniLink->pibregs->isr1;
    pniLink->r_isr2 = pniLink->pibregs->isr2;
    /* disable all interrupts from the 7210 */
    pniLink->pibregs->imr1 = 0;	/* DMA and -1 IRQ mask reg */
    pniLink->pibregs->imr2 = 0;	/* SRQ IRQ mask reg */
    pniLink->pibregs->spmr = 0;	/* serial poll mode register */
    pniLink->pibregs->adr = 0;	/* device address = 0 */
    pniLink->pibregs->adr = HR_ARS | HR_DT | HR_DL;/* no secondary addressing */
    pniLink->pibregs->admr = HR_TRM1 | HR_TRM0 | HR_ADM0;
    pniLink->pibregs->eosr = 0;	/* end of string value */
    pniLink->pibregs->auxmr = ICR | 8;	/* internal counter = 8 */
    pniLink->pibregs->auxmr = PPR | HR_PPU;/* paralell poll unconfigure */
    pniLink->pibregs->auxmr = AUXRA | 0;
    pniLink->pibregs->auxmr = AUXRB | 0;
    pniLink->pibregs->auxmr = AUXRE | 0;
    /* DMAC setup */
    pniLink->pibregs->cfg1 = (pniLink->level << 5) | D_BRG3 | D_DBM;
    pniLink->pibregs->ch1.niv = pniLink->vector;	/* normal IRQ vector */
    pniLink->pibregs->ch1.eiv = pniLink->vector + 1;	/* error IRQ vector */
    pniLink->pibregs->ch0.niv = pniLink->vector;	/* normal IRQ vector */
    pniLink->pibregs->ch0.eiv = pniLink->vector + 1;	/* error IRQ vector */
    pniLink->pibregs->ch1.ccr = D_EINT;	/* stop operation, allow ints */
    pniLink->pibregs->ch0.ccr = 0;	/* stop all channel operation */
    pniLink->pibregs->ch0.cpr = 3;	/* highest priority */
    pniLink->pibregs->ch1.cpr = 3;	/* highest priority */
    pniLink->pibregs->ch1.dcr = D_CS | D_IACK | D_IPCL;
    pniLink->pibregs->ch0.dcr = D_CS | D_IACK | D_IPCL;
    pniLink->pibregs->ch1.scr = 0;	/* no counting during DMA */
    pniLink->pibregs->ch0.scr = D_MCU;	/* count up during DMA cycles */
    pniLink->pibregs->ch0.mfc = STD_ADDRESS_MODE;
    pniLink->pibregs->ch1.mfc = STD_ADDRESS_MODE;
    pniLink->pibregs->ch1.bfc = STD_ADDRESS_MODE;
    /* Fire up the TLCs and nudge all the addresses on the GPIB bus */
    /* by doing a serial poll on all of them.  If someone did a */
    /* srqPollInhibit() on a specific link, then skip it and continue. */
    pniLink->pibregs->auxmr = AUX_PON;	/* release pon state */
    /* fire out an Interface Clear pulse */
    taskDelay(1);
    pniLink->pibregs->auxmr = AUX_SIFC;	/* assert the line */
    taskDelay(1);
    pniLink->pibregs->auxmr = AUX_CIFC;	/* clear the line */
    taskDelay(1);
    pniLink->pibregs->auxmr = AUX_SREN;/*remote enable*/
    taskDelay(1);
    return (0);
}

STATIC long niGenLink(ibLink * pibLink, int pass)
{
    if(pass == 0) {
        return drvGpibLinkStart(pibLink);
    }
    return (0);
}

STATIC long niDestroyLink(ibLink * pibLink)
{
    errlogPrintf("niDestroyLink not implemented\n");
    return (-1);
}

STATIC long niReportLink(ibLink * pibLink, int level)
{
    niLink *pniLink = (niLink *) pibLink;
    printf("  NI1014 VMEaddr %p IV 0x%x pniLink %p\n",
           pniLink->pibregs,pniLink->vector,pniLink);
    printf("    total niWriteCmd taskDelay calls = %lu\n",
           pniLink->cmdSpins);
    printf("    worst case delay in niWriteCmd() = %lu\n",
           pniLink->maxSpins);
    printf("    DMA timing: error = %d, total = %d, max = %d\n",
           niDbaTimingError, niDbaTimingErrorTotal, niDbaMaxError);
    return (0);
}

STATIC long niProbe(ibLink * pibLink)
{
    niLink *pniLink = (niLink *) pibLink;
    printf("niProbe called for link %d I don't know what to do.\n",
           pniLink->head.link);
    return (0);
}

STATIC long niRead(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout, int eos)
{
    niLink *pniLink = (niLink *) pibLink;
    int err;
    char attnCmd[4];
    int lenCmd;

    if (niDebug) errlogPrintf("niGpibRead(%d, 0x%8.8X, %d, %d)\n",
            pibLink->link, buffer, length, timeout);
    attnCmd[0] = 0+LADBASE;         /* mta = 0 */
    attnCmd[1] = gpibAddr+TADBASE;  /* tad = gpibAddr */
    if(secondary== IBNOSECONDARY) {
    	attnCmd[2] = '\0';              /* in case debugging prints it */
        lenCmd = 2;
    } else {
        attnCmd[2] = SADBASE + secondary;
    	attnCmd[3] = '\0';              /* in case debugging prints it */
        lenCmd = 3;
    }
    if(niWriteCmd(pibLink,attnCmd,lenCmd) != lenCmd) return(-1);
    err = ni1014DMA(READ, pniLink, buffer, length, timeout);
    attnCmd[0] = '_';               /* global untalk */
    attnCmd[1] = '?';               /* global unlisten */
    if(niWriteCmd(pibLink,attnCmd,2) != 2) return(-1);
    pniLink->r_isr1 &= ~HR_END;
    return (err ? err : length - ni1014Resid(pniLink));
}

STATIC long niWrite(ibLink * pibLink,int gpibAddr, int secondary,
    char *buffer, int length, int timeout)
{
    niLink *pniLink = (niLink *) pibLink;
    int err;
    char attnCmd[4];
    int lenCmd;

    attnCmd[0] = 0+TADBASE;         /* mta = 0 */
    attnCmd[1] = gpibAddr+LADBASE;  /* lad = gpibAddr */
    if(secondary== IBNOSECONDARY) {
    	attnCmd[2] = '\0';              /* in case debugging prints it */
        lenCmd = 2;
    } else {
        attnCmd[2] = SADBASE + secondary;
    	attnCmd[3] = '\0';              /* in case debugging prints it */
        lenCmd = 3;
    }
    if(niWriteCmd(pibLink,attnCmd,lenCmd) != lenCmd) return(-1);
    err = ni1014DMA(WRITE, pniLink, buffer, length, timeout);
    attnCmd[0] = '_';               /* global untalk */
    attnCmd[1] = '?';               /* global unlisten */
    if(niWriteCmd(pibLink,attnCmd,2) != 2) return(-1);
    return (err ? err : length - ni1014Resid(pniLink));
}

STATIC long niAddressedCmd(ibLink *pibLink,int gpibAddr,int secondary,
        char *buffer,int length,int timeout)
{
    char attnCmd[4];
    int lenCmd;

    attnCmd[0] = 0+TADBASE;         /* mta = 0 */
    attnCmd[1] = gpibAddr+LADBASE;  /* lad = gpibAddr */
    if(secondary== IBNOSECONDARY) {
    	attnCmd[2] = '\0';              /* in case debugging prints it */
        lenCmd = 2;
    } else {
        attnCmd[2] = SADBASE + secondary;
    	attnCmd[3] = '\0';              /* in case debugging prints it */
        lenCmd = 3;
    }
    if(niWriteCmd(pibLink,attnCmd,lenCmd) != lenCmd) return(-1);
    if(niWriteCmd(pibLink, buffer, length) !=length) return(-1);
    attnCmd[0] = '_';               /* global untalk */
    attnCmd[1] = '?';               /* global unlisten */
    if(niWriteCmd(pibLink,attnCmd,2) != 2) return(-1);
    return(0);
}

STATIC long niUniversialCmd(ibLink *pibLink,int cmd)
{
    char buffer[2];
    buffer[1] = cmd;
    buffer[2] = 0;
    return niWriteCmd(pibLink,buffer,1);
}

STATIC long niIfc(ibLink *pibLink)
{
    niLink *pniLink = (niLink *) pibLink;
    pniLink->pibregs->auxmr = AUX_SIFC;	/* assert the line */
    taskDelay(1);
    pniLink->pibregs->auxmr = AUX_CIFC;	/* clear the line */
    return(0);
}

STATIC long niRen(ibLink *pibLink,int onOff)
{
    niLink *pniLink = (niLink *) pibLink;

    pniLink->pibregs->auxmr = 
        (onOff ? AUX_SREN : AUX_CREN);
    return(0);
}

STATIC long niSrqStatus(ibLink * pibLink)
{
    return (0);
}

STATIC long niSrqEnable(ibLink * pibLink)
{
    niLink *pniLink = (niLink *) pibLink;
    int lockKey;
    if (niDebug || niSrqDebug)
        errlogPrintf("niSrqIntEnable(%d): ch0.csr = 0x%2.2X, gsr=0x%2.2X\n",
            pibLink->link, pniLink->pibregs->ch0.csr, pniLink->pibregs->gsr);
    lockKey = intLock();/* lock out ints because something likes to glitch */
    if (!((pniLink->pibregs->ch0.csr) & D_NSRQ)) {
        /* SRQ line is CURRENTLY active, just give the event sem and return */
        drvGpibSrqHappened(pibLink);
        if (niDebug || niSrqDebug)
            errlogPrintf("niSrqIntEnable(%d): found SRQ active, setting srqIntFlag\n",
                pibLink->link);
        /* Clear the PCLT status if is already set to prevent unneeded int r */
        pniLink->pibregs->ch0.csr = D_PCLT;
    } else
        pniLink->pibregs->ch0.ccr = D_EINT;	/* Allow SRQ ints */
    intUnlock(lockKey);
    return (0);
}

STATIC long niSrqDisable(ibLink * pibLink)
{
    niLink *pniLink = (niLink *) pibLink;
    int lockKey;
    if (niDebug || niSrqDebug)
        errlogPrintf("niSrqIntDisable(%d): ch0.csr = 0x%2.2X, gsr=0x%2.2X\n",
            pibLink->link, pniLink->pibregs->ch0.csr, pniLink->pibregs->gsr);
    lockKey = intLock();/* lock out ints because something likes to glitch */
    pniLink->pibregs->ch0.ccr = 0;	/* Don't allow SRQ ints */
    intUnlock(lockKey);
    return (0);
}

STATIC long niSerialPollBegin(ibLink *pibLink)
{
    char cmd[2];

    cmd[0] = '?';               /* global unlisten */
    cmd[1] = IBSPE;
    if(niWriteCmd(pibLink,cmd,2) != 2) return(-1);
    return(0);
}

STATIC long niSerialPoll(ibLink *pibLink,int gpibAddr, int secondary,
    int timeout)
{
    niLink *pniLink = (niLink *) pibLink;
    int err;
    char cmd[4];
    int lenCmd;
    unsigned char srqStatus;

    cmd[0] = gpibAddr+TADBASE;  /* tad = gpibAddr */
    if(secondary== IBNOSECONDARY) {
    	cmd[1] = '\0';              /* in case debugging prints it */
        lenCmd = 1;
    } else {
        cmd[1] = SADBASE + secondary;
    	cmd[2] = '\0';              /* in case debugging prints it */
        lenCmd = 2;
    }
    if(niWriteCmd(pibLink,cmd,lenCmd) != lenCmd) return(-1);
    err = ni1014DMA(READ, pniLink, (char *)&srqStatus, 1, timeout);
    if(err) return(-1);
    cmd[0] = '_';               /* global untalk */
    if(niWriteCmd(pibLink,cmd,1) != 1) return(-1);
    pniLink->r_isr1 &= ~HR_END;
    return (srqStatus);
}

STATIC long niSerialPollEnd(ibLink *pibLink)
{
    char cmd[2];

    cmd[0] = IBSPD;
    cmd[1] = '_';               /* global untalk */
    if(niWriteCmd(pibLink,cmd,2) != 2) return(-1);
    return(0);
}

/* The entry table to this driver. */
drvHwGpibEt drvNi1014Gpib =
{
    niGenLink,
    niDestroyLink,
    niReportLink,
    niProbe,
    niRead,
    niWrite,
    niAddressedCmd,
    niUniversialCmd,
    niIfc,
    niRen,
    niSrqStatus,
    niSrqEnable,
    niSrqDisable,
    niSerialPollBegin,
    niSerialPoll,
    niSerialPollEnd
};

/******************************************************************************
 * Initialization function to be called (only) from the shell task
 * (ie from the startup file) prior to iocInit.
 ******************************************************************************/
#define MAXPORTS 2
int drvNi1014GpibConfig(int link,int nPorts, int base, int vector, int level)
{
    static int firstTime=1;
    int indPort;
    niLink *paniLink[MAXPORTS]; /*pointer to array of niLinks*/

    if(firstTime) {
        firstTime=0;
        defaultTimeout=sysClkRateGet();
        ellInit(&niLinkList);
        rebootHookAdd((FUNCPTR)ni1014RebootHandler);
    }
    if(nPorts<1 || nPorts>2) {
        errlogPrintf("drvNi1014GpibConfig illegal number of ports\n");
        return(-1);
    }
    for(indPort=0; indPort<nPorts; indPort++) {
        niLink *pniLink;
        niLinkNode *pniLinkNode;
        pniLink = (niLink *) calloc(1, sizeof(niLink));
        if (!pniLink) {
            errlogPrintf("Can't allocate niLink structure\n");
            return (-1);
        }
        paniLink[indPort] = pniLink;
        pniLinkNode = (niLinkNode *)calloc(1,sizeof(niLinkNode));
        pniLinkNode->pniLink = pniLink;
        ellAdd(&niLinkList,&pniLinkNode->node);
        pniLink->base = base;
        pniLink->vector = vector;
        pniLink->level = level;
        pniLink->head.link = link + indPort;
        pniLink->head.pdrvHwGpibEt = &drvNi1014Gpib;
        if(ni1014ConfigFirstPass(pniLink)) return(-1);
        base = base + sizeof(ibregs);
        vector += 2;
    }
    /*Must ni1014ConfigSecondPass AFTER ni1014ConfigFirstPass for both ports*/
    for(indPort=0; indPort<nPorts; indPort++) {
        if(ni1014ConfigSecondPass(paniLink[indPort])) {
            errlogPrintf("drvNi1014GpibConfig drvGpibLinkInit failed???\n");
            return(-1);
        }
    }
    /*Must call drvGpibLinkInit AFTER both ports are configured*/
    for(indPort=0; indPort<nPorts; indPort++) {
        if (drvGpibLinkInit(&paniLink[indPort]->head) != 0) {
            errlogPrintf("drvNi1014GpibConfig drvGpibLinkInit failed???\n");
            return(-1);
        }
    }
    return 0;
}

/*
 * IOC shell command registration
 */
#include <iocsh.h>
static const iocshArg drvNi1014ConfigArg0 = { "link",iocshArgInt};
static const iocshArg drvNi1014ConfigArg1 = { "nPorts",iocshArgInt};
static const iocshArg drvNi1014ConfigArg2 = { "base",iocshArgInt};
static const iocshArg drvNi1014ConfigArg3 = { "vector",iocshArgInt};
static const iocshArg drvNi1014ConfigArg4 = { "level",iocshArgInt};
static const iocshArg *drvNi1014ConfigArgs[5] = {&drvNi1014ConfigArg0,
    &drvNi1014ConfigArg1, &drvNi1014ConfigArg2, &drvNi1014ConfigArg3,
    &drvNi1014ConfigArg4};
static const iocshFuncDef drvNi1014ConfigFuncDef =
    {"drvNi1014Config",5,drvNi1014ConfigArgs};
static void drvNi1014ConfigCallFunc(const iocshArgBuf *args)
{
    drvNi1014GpibConfig(args[0].ival,args[1].ival,
        args[2].ival,args[3].ival,args[4].ival);
}

void drvNi1014RegisterCommands(void)
{
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        iocshRegister(&drvNi1014ConfigFuncDef,drvNi1014ConfigCallFunc);
    }
}
epicsExportRegistrar(drvNi1014RegisterCommands);
