/* 
 * GPIB driver for FreeBSD.
 * Version 0.1 (No interrupts, no DMA)
 * Supports National Instruments AT-GPIB and AT-GPIB/TNT boards.
 * (AT-GPIB not tested, but it should work)
 *
 * Written by Fred Cawthorne (fcawth@delphi.umd.edu)
 * Some sections were based partly on the lpt driver.
 *  (some remnants may remain)
 *
 * This software is distributed with NO WARRANTIES, not even the implied
 * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * The author grants any other persons or organizations permission to use
 * or modify this software as long as this message is kept with the software,
 * all derivative works or modified versions.
 *
 * $FreeBSD$
 */

/* Please read the README file for usage information */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <i386/isa/gpibreg.h>
#include <i386/isa/gpib.h>
#include <i386/isa/isa_device.h>

#ifndef COMPAT_OLDISA
#error "The gpib device requires the old isa compatibility shims"
#endif

#define GPIBPRI  (PZERO + 8) | PCATCH
#define SLEEP_MAX 1000
#define SLEEP_MIN 4

static int initgpib(void);
static void closegpib(void);
static int sendgpibfifo(unsigned char device, char *data, int count);
static int sendrawgpibfifo(unsigned char device, char *data, int count);
static int readgpibfifo(unsigned char device, char *data, int count);
#if 0
static void showregs(void);
#endif
static void enableremote(unsigned char device);
static void gotolocal(unsigned char device);
static void menableremote(unsigned char *device);
static void mgotolocal(unsigned char *device);
static void mtrigger(unsigned char *device);
static void trigger(unsigned char device);
static char spoll(unsigned char device);

static int gpprobe(struct isa_device *dvp);
static int gpattach(struct isa_device *dvp);

struct   isa_driver gpdriver = {
	INTR_TYPE_TTY,
	gpprobe,
	gpattach,
	"gp"
};
COMPAT_ISA_DRIVER(gp, gpdriver);

static	d_open_t	gpopen;
static	d_close_t	gpclose;
static	d_write_t	gpwrite;
static	d_ioctl_t	gpioctl;

#define CDEV_MAJOR 44
static struct cdevsw gp_cdevsw = {
	.d_open =	gpopen,
	.d_close =	gpclose,
	.d_write =	gpwrite,
	.d_ioctl =	gpioctl,
	.d_name =	"gp",
	.d_maj =	CDEV_MAJOR,
};

#define	BUFSIZE		1024
#define	ATTACHED	0x08
#define	OPEN		0x04
#define	INIT		0x02

static struct gpib_softc {
	char	*sc_cp;		/* current data to send		*/
	int	sc_count;	/* bytes queued in sc_inbuf	*/
	int	sc_type;	/* Type of gpib controller	*/
	u_char	sc_flags;	/* flags (open and internal)	*/
        char	sc_unit;	/* gpib device number		*/
        char	*sc_inbuf;	/* buffer for data		*/
} gpib_sc;	/* only support one of these? */

static int oldcount;
static char oldbytes[2];

/*
 * Probe routine
 * This needs to be changed to be a bit more robust
 */
static int
gpprobe(struct isa_device *dvp)
{
	int	status;
        struct gpib_softc *sc = &gpib_sc;

	gpib_port = dvp->id_iobase;
        status = 1;
        sc->sc_type = 3;
	if ((inb(KSR) & 0xF7) == 0x34)
		sc->sc_type = 3;
	else if ((inb(KSR) & 0xF7) == 0x24)
		sc->sc_type = 2;
	else if ((inb(KSR) & 0xF7) == 0x14)
		sc->sc_type = 1;
	else
		status = 0;
        return (status);
}

/* 
 * gpattach()
 *  Attach device and print the type of card to the screen.
 */
static int
gpattach(isdp)
	struct isa_device *isdp;
{
	struct   gpib_softc   *sc = &gpib_sc;

	sc->sc_unit = isdp->id_unit;
        if (sc->sc_type == 3)
		printf ("gp%d: type AT-GPIB/TNT\n", sc->sc_unit);
        if (sc->sc_type == 2)
		printf ("gp%d: type AT-GPIB chip NAT4882B\n", sc->sc_unit);
        if (sc->sc_type == 1)
		printf ("gp%d: type AT-GPIB chip NAT4882A\n", sc->sc_unit);
        sc->sc_flags |= ATTACHED;
	make_dev(&gp_cdevsw, 0, 0, 0, 0600, "gp");
        return (1);
}

/* 
 * gpopen()
 *	New open on device.
 *
 * More than 1 open is not allowed on the entire device.
 * i.e. even if gpib5 is open, we can't open another minor device
 */
static	int
gpopen(dev, flags, fmt, td)
	dev_t dev;
	int flags;
	int fmt;
	struct thread *td;
{
	struct gpib_softc *sc = &gpib_sc;
	u_char unit;
	int status;

	unit = minor(dev);

	/* minor number out of limits ? */
	if (unit >= 32)
		return (ENXIO);

	/* Attached ? */
	if (!(sc->sc_flags&ATTACHED))	/* not attached */
		return (ENXIO);

	/* Already open */
	if (sc->sc_flags&OPEN)	/* too late .. */
		return (EBUSY);

	/* Have memory for buffer? */
	sc->sc_inbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK);
	if (sc->sc_inbuf == 0)
		return (ENOMEM);

        if (initgpib()) return (EBUSY);
        sc->sc_flags |= OPEN;
	sc->sc_count = 0;
        oldcount = 0;
	if (unit != 0) {	/* Someone is trying to access an actual device */
		/* So.. we'll address it to listen */
		enableremote(unit);
		do {
			status = inb(ISR2);
		} while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);

		outb(CDOR, (unit & 31) + 32);	/* address device to listen */

		do
			status = inb(ISR2);
		while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			   "gpibpoll", 1) == EWOULDBLOCK);
		outb(CDOR, 64);		/* Address controller (me) to talk */
		do {
			status = inb(ISR2);
		} while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);
		outb(AUXMR, gts);	/* Set to Standby (Controller) */


		do {
			status = inb(ISR1);
		} while (!(status & 2) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);

		/* Set up the TURBO488 registers */
		outb(IMR2, 0x30);	/* we have to enable DMA (0x30) for turbo488 to work */
		outb(CNT0, 0);		/* NOTE this does not enable DMA to the host computer!! */
		outb(CNT1, 0);
		outb(CNT2, 0);
		outb(CNT3, 0);
		outb(CMDR, 0x20);
		outb(CFG, 0x47);	/* 16 bit, write, fifo B first, TMOE TIM */
		outb(CMDR, 0x10);	/* RESET fifos */
		outb(CMDR, 0x04);	/* Tell TURBO488 to GO */
	}
	return (0);
}


/* 
 * gpclose()
 *	Close gpib device.
 */
static	int
gpclose(dev, flags, fmt, td)
	dev_t dev;
	int flags;
	int fmt;
	struct thread *td;
{
	struct gpib_softc *sc = &gpib_sc;
        unsigned char unit;
        unsigned char status;

        unit = minor(dev);
	if (unit != 0) {	/* Here we need to send the last character with EOS */
		/* and unaddress the listening device */

		status = EWOULDBLOCK;

		/* Wait for fifo to become empty */
		do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while ((inb(ISR3) & 0x04) && status == EWOULDBLOCK);	/* Fifo is not empty */

		outb(CMDR, 0x08);	/* Issue STOP to TURBO488 */

		/* Wait for DONE and STOP */
		if (status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR3) & 0x11) && status == EWOULDBLOCK);	/* not done and stop */

		/* Shut down TURBO488 */
		outb(IMR2, 0x00);	/* DISABLE DMA to turbo488 */
		outb(CMDR, 0x20);	/* soft reset turbo488 */
		outb(CMDR, 0x10);	/* reset fifos */

		/* Send last byte with EOI set */
		/* Send second to last byte if there are 2 bytes left */
		if (status == EWOULDBLOCK)  {
			do {
				if (!(inb(ISR1) & 2)) status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
			} while (!(inb(ISR1) & 2) && (status == EWOULDBLOCK));
			if (oldcount == 2) {
				outb(CDOR, oldbytes[0]);	/* Send second to last byte */
				while (!(inb(ISR1) & 2) && (status == EWOULDBLOCK));
				status = tsleep((caddr_t)&gpib_sc, GPIBPRI,
				    "gpibpoll", 1);
			}

			outb(AUXMR, seoi);	/* Set EOI for the last byte */
			outb(AUXMR, 0x5E);	/* Clear SYNC */
			if (oldcount == 1)
				outb(CDOR, oldbytes[0]);
			else
				if (oldcount == 2)
					outb(CDOR, oldbytes[1]);
				else {
					outb(CDOR, 13);	/* Send a CR.. we've got trouble */
					printf("gpib: Warning: gpclose called with nothing left in buffer\n");
				}
		}

		do {
			if (!(inb(ISR1) & 2)) status = tsleep((caddr_t)&gpib_sc,
			    GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR1) & 2) && (status == EWOULDBLOCK));

		if (!(inb(ISR1) & 2) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR1) & 2) && status == EWOULDBLOCK);

		outb(AUXMR, tca); /* Regain full control of the bus */

		do {
			status = inb(ISR2);
		} while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);
		outb(CDOR, 63);	/* unlisten */
		do {
			status = inb(ISR2);
		} while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);
		outb(AUXMR, 0x5E);	/* Clear SYNC */
		outb(CDOR, 95);	/* untalk */
		do {
			status = inb(ISR2);
		} while (!(status & 8) && tsleep((caddr_t)&gpib_sc, GPIBPRI,
			     "gpibpoll", 1) == EWOULDBLOCK);
#if 0
		gotolocal(minor(dev));
#endif
	}
	closegpib();
	sc->sc_flags = ATTACHED;
	free(sc->sc_inbuf, M_DEVBUF);
	sc->sc_inbuf = 0;	/* Sanity */
	return (0);
}

/* 
 * gpwrite()
 *	Copy from user's buffer, then write to GPIB device referenced
 *    by minor(dev).
 */
static	int
gpwrite(dev, uio, ioflag)
	dev_t dev;
	struct uio *uio;
	int ioflag;
{
	int err, count;

	/* main loop */
	while ((gpib_sc.sc_count = MIN(BUFSIZE-1, uio->uio_resid)) > 0) {
		/* If there were >1 bytes left over, send them */
                if (oldcount == 2)
			sendrawgpibfifo(minor(dev), oldbytes, 2);

		/* If there was 1 character left, put it at the beginning
		   of the new buffer */
                if (oldcount == 1) {
			(gpib_sc.sc_inbuf)[0] = oldbytes[0];
			gpib_sc.sc_cp = gpib_sc.sc_inbuf;
			/* get from user-space */
			uiomove(gpib_sc.sc_inbuf + 1, gpib_sc.sc_count, uio);
			gpib_sc.sc_count++;
		} else {
			gpib_sc.sc_cp = gpib_sc.sc_inbuf;
			/* get from user-space */
			uiomove(gpib_sc.sc_inbuf, gpib_sc.sc_count, uio);
		}

		/*
		 * NOTE we always leave one byte in case this is the last write
		 * so close can send EOI with the last byte There may be 2 bytes
		 * since we are doing 16 bit transfers.(note the -1 in the count below)
		 */

		/* If count <= 2 we'll either pick it up on the next write or on close */
		if (gpib_sc.sc_count>2) {
			count = sendrawgpibfifo(minor(dev), gpib_sc.sc_cp, gpib_sc.sc_count-1);
			err = !count;
			if (err)
				return (1);
			oldcount = gpib_sc.sc_count-count;	/* Set # of remaining bytes */
			gpib_sc.sc_count -= count;
			gpib_sc.sc_cp += count;	/* point char pointer to remaining bytes */
		}
                else
			oldcount = gpib_sc.sc_count;
		oldbytes[0] = gpib_sc.sc_cp[0];
                if (oldcount == 2)
			oldbytes[1] = gpib_sc.sc_cp[1];
	}
	return (0);
}

/*
 * Here is how you would usually access a GPIB device
 * An exception would be a plotter or printer that you can just
 * write to using a minor device = its GPIB address
 */

static	int
gpioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td)
{
	struct gpibdata *gd = (struct gpibdata *)data;
	int	error, result;
	error = 0;

	switch (cmd) {
	case GPIBWRITE:
		sendgpibfifo(gd->address, gd->data, *(gd->count));
		error = 0;
		break;
        case GPIBREAD:
                result = readgpibfifo(gd->address, gd->data, *(gd->count));
                *(gd->count) = result;
                error = 0;
                break;
        case GPIBINIT:
                initgpib();
                error = 0;
                break;
        case GPIBTRIGGER:
                trigger(gd->address);
                error = 0;
                break;
        case GPIBREMOTE:
                enableremote(gd->address);
                error = 0;
                break;
        case GPIBLOCAL:
                gotolocal(gd->address);
                error = 0;
                break;
        case GPIBMTRIGGER:
                mtrigger(gd->data);
                error = 0;
                break;
        case GPIBMREMOTE:
                menableremote(gd->data);
                error = 0;
                break;
        case GPIBMLOCAL:
                mgotolocal(gd->data);
                error = 0;
                break;
        case GPIBSPOLL:
                *(gd->data) = spoll(gd->address);
                error = 0;
                break;
        default:
		error = ENODEV;
	}

	return (error);
}

#if 0
/* Just in case you want a dump of the registers... */

static void showregs() {
	printf ("NAT4882:\n");
	printf ("ISR1=%X\t", inb(ISR1));
	printf ("ISR2=%X\t", inb(ISR2));
	printf ("SPSR=%X\t", inb(SPSR));
	printf ("KSR =%X\t", inb(KSR));
	printf ("ADSR=%X\t", inb(ADSR));
	printf ("CPTR=%X\t", inb(CPTR));
	printf ("SASR=%X\t", inb(SASR));
	printf ("ADR0=%X\t", inb(ADR0));
	printf ("ISR0=%X\t", inb(ISR0));
	printf ("ADR1=%X\t", inb(ADR1));
	printf ("BSR =%X\n", inb(BSR));

	printf ("Turbo488\n");
	printf ("STS1=%X ", inb(STS1));
	printf ("STS2=%X ", inb(STS2));
	printf ("ISR3=%X ", inb(ISR3));
	printf ("CNT0=%X ", inb(CNT0));
	printf ("CNT1=%X ", inb(CNT1));
	printf ("CNT2=%X ", inb(CNT2));
	printf ("CNT3=%X ", inb(CNT3));
	printf ("IMR3=%X ", inb(IMR3));
	printf ("TIMER=%X\n", inb(TIMER));
}
#endif

/*
 * Set up the NAT4882 and TURBO488 registers
 * This will be nonsense to you unless you have a data sheet from
 * National Instruments.  They should give you one if you call them
 */

static int
initgpib()
{
	outb(CMDR, 0x20);
	outb(CFG, 0x16);
	outb(IMR3, 0);
	outb(CMDR, 0x10);
	outb(CNT0, 0);
	outb(CNT1, 0);
	outb(CNT2, 0);
	outb(CNT3, 0);
	outb(INTR, 0);		/* Put interrupt line in tri-state mode?? */
	outb(AUXMR, chip_reset);

	outb(IMR1, 0x10);	/* send interrupt to TURBO488 when END received */
	outb(IMR2, 0);
	outb(IMR0, 0x90);	/* Do we want nba here too??? */
	outb(ADMR, 1);
	outb(ADR, 0);
	outb(ADR, 128);
	outb(AUXMR, 0xE9);
	outb(AUXMR, 0x49);
	outb(AUXMR, 0x70);
	outb(AUXMR, 0xD0);
	outb(AUXMR, 0xA0);

	outb(EOSR, 10);	/* set EOS message to newline */
	/* should I make the default to interpret END as EOS? */
	/* It isn't now.  The following changes this */
	outb(AUXMR, 0x80);	/* No special EOS handling */
#if 0
	outb(AUXMR, 0x88)	/* Transmit END with EOS */
	outb(AUXMR, 0x84)	/* Set END on EOS received */
	outb(AUXMR, 0x8C)	/* Do both of the above */
#endif

#if 0
	/* Not currently supported */
	outb(AUXMR, hldi);	/* Perform RFD Holdoff for all data in */
#endif

	outb(AUXMR, pon);
	outb(AUXMR, sic_rsc);
	tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);

	outb(AUXMR, sic_rsc_off);

	return (0);
}

/* This is kind of Brute force..  But it works */

static void 
closegpib() 
{
	outb(AUXMR, chip_reset);
}

/*
 * GPIB ROUTINES:
 * These will also make little sense unless you have a data sheet.
 * Note that the routines with an "m" in the beginning are for
 * accessing multiple devices in one call
 */

/*
 * This is one thing I could not figure out how to do correctly.
 * I tried to use the auxilary  command to enable remote, but it
 * never worked.  Here, I bypass everything and write to the BSR
 * to enable the remote line.  NOTE that these lines are effectively
 * "OR'ed" with the actual lines, so writing a 1 to the bit in the BSR
 * forces the GPIB line true, no matter what the fancy circuitry of the
 * NAT4882 wants to do with it
 */

static void
enableremote(unsigned char device)
{
	int status;

	status = EWOULDBLOCK;
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(BSR, 1);	/* Set REN bit on GPIB */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
	outb(CDOR, (device & 31) + 32);	/* address device to listen */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
	outb(CDOR, 63);	/* Unaddress device */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

}

/*
 * This does not release the REM line on the gpib port, because if it did,
 * all the remote devices would go to local mode.  This only sends the
 * gotolocal message to one device.  Currently, REM is always held true
 * after enableremote is called, and is reset only on a close of the
 * gpib device
 */

static void
gotolocal(unsigned char device)
{
	int status;

	status = EWOULDBLOCK;
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(CDOR, (device & 31) + 32);

	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(AUXMR, 0x5E);	/* Clear SYNC */
	outb(CDOR, 1);

	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(AUXMR, 0x5E);
	outb(CDOR, 63);	/* unaddress device */

	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
}

static void
menableremote(unsigned char *device)
{
	int status, counter = 0;

	status = EWOULDBLOCK;
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(BSR, 1);	/* Set REN bit on GPIB */
	do {
		if (status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(CDOR, (device[counter] & 31) + 32);	/* address device to listen */
		counter++;
	} while (device[counter] < 32);

	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(CDOR, 63);	/* Unaddress device */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

}

static void
mgotolocal(unsigned char *device)
{
	int status;
	int counter = 0;

	status = EWOULDBLOCK;
	if (device[counter] < 32) do {
		if (status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(CDOR, (device[counter] & 31) + 32);
		counter++;
	} while (device[counter] < 32);
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

	outb(AUXMR, 0x5E);	/* Clear SYNC */
	outb(CDOR, 1);

	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
	outb(AUXMR, 0x5E);
	outb(CDOR, 63);		/* unaddress device */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 2);
	} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
}

/* Trigger a device.  What happens depends on how the device is configured. */

static void 
trigger(unsigned char device)
{
	int status;

	status = EWOULDBLOCK;
	if (device < 32)  {
		if (!(inb(ISR2) & 0x08)) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(CDOR, (device & 31) + 32);	/* address device to listen */
		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */

		outb(CDOR, 8);	/* send GET */

		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(AUXMR, 0x5E);
		outb(CDOR, 63);	/* unaddress device */
		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
	}
}

/*
 * Trigger multiple devices by addressing them all to listen, and then
 * sending GET
 */

static void
mtrigger(unsigned char *device)
{
	int status = EWOULDBLOCK;
	int counter = 0;
	if (device[0] < 32) {
		do {
			if (device[counter] < 32)
				if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
					status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
				} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
			outb(CDOR, (device[counter] & 31) + 32);	/* address device to listen */
			counter++;
		} while (device[counter] < 32);
		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(CDOR, 8);	/* send GET */

		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
		outb(AUXMR, 0x5E);
		outb(CDOR, 63);	/* unaddress device */
		if (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR2) & 0x08) && status == EWOULDBLOCK);	/* Wait to send next cmd */
	}
}

/*
 * This is not used now, but it should work with NI's 8 bit gpib board
 * since it does not use the TURBO488 registers at all
 */

/*
 * Send data through the TURBO488 FIFOS to a device that is already
 * addressed to listen.  This is used by the write call when someone is
 * writing to a printer or plotter, etc...
 *
 * The last byte of each write is held off until either the next
 * write or close, so it can be sent with EOI set
 */

static int
sendrawgpibfifo(unsigned char device, char *data, int count)
{
	int status;
	int counter;
	int fifopos;
	int sleeptime;

	sleeptime = SLEEP_MIN;
	counter = 0;

	fifopos = 0;

	status = EWOULDBLOCK;
	do {
		/* Wait for fifo to become not full if it is full */
		sleeptime = SLEEP_MIN;
		if (!(inb(ISR3) & 0x08)) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", sleeptime);
			if (sleeptime < SLEEP_MAX) sleeptime = sleeptime * 2;
		} while (!(inb(ISR3) & 0x08) && (status == EWOULDBLOCK));	/* Fifo is full */

		if ((count>1) && (inb(ISR3) & 0x08)) {
			outw(FIFOB, *(unsigned *)(data + counter));
#if 0
			printf ("gpib: sent:%c, %c\n", data[counter], data[counter + 1]);
#endif
			counter += 2;
			count -= 2;
		}
	}
	while ((count>1) && (status == EWOULDBLOCK));

	/*
	 * The write routine and close routine must check if there is 1
	 * byte left and handle it accordingly
	 */

	/* Return the number of bytes written to the device */
	return (counter);
}

static int
sendgpibfifo(unsigned char device, char *data, int count)
{
	int status;
	int counter;
	int fifopos;
	int sleeptime;

	outb(IMR2, 0x30);	/* we have to enable DMA (0x30) for turbo488 to work */
	outb(CNT0, 0);
	outb(CNT1, 0);
	outb(CNT2, 0);
	outb(CNT3, 0);
	status = EWOULDBLOCK;
	if (!(inb(ISR2) & 8)) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, (device & 31) + 32);	/* address device to listen */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);
	outb(CDOR, 64);		/* Address controller (me) to talk */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(AUXMR, gts);	/* Set to Standby (Controller) */
	fifopos = 0;

	sleeptime = SLEEP_MIN;
	counter = 0;

	fifopos = 0;

	outb(CMDR, 0x20);
	outb(CFG, 0x47);	/* 16 bit, write, fifo B first, TMOE TIM */
	outb(CMDR, 0x10);	/* RESET fifos */
	outb(CCRG, seoi);	/* program to send EOI at end */
	outb(CMDR, 0x04);	/* Tell TURBO488 to GO */
	status = EWOULDBLOCK;
	do {
		/* Wait for fifo to become not full if it is full */
		sleeptime = SLEEP_MIN;
		if (!(inb(ISR3) & 0x08)) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", sleeptime);
			if (sleeptime < SLEEP_MAX) sleeptime = sleeptime * 2;
		} while (!(inb(ISR3) & 0x08) && (status == EWOULDBLOCK));	/* Fifo is full */

		if ((count>1) && (inb(ISR3) & 0x08)) {
#if 0
			if (count == 2) outb(CFG, 15 + 0x40);	/* send eoi when done */
#endif
			outw(FIFOB, *(unsigned *)(data + counter));

			counter += 2;
			count -= 2;
		}
	} while ((count>2) && (status == EWOULDBLOCK));

	if (count == 2 && status == EWOULDBLOCK) {
		/* Wait for fifo to become not full */
		if (status == EWOULDBLOCK && !(inb(ISR3) & 0x08)) do {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", SLEEP_MIN);
		} while (!(inb(ISR3) & 0x08) && status == EWOULDBLOCK);	/* Fifo is full */
#if 0
		outb(CFG, 0x40 + 15);	/* send eoi when done */
#endif
		outb(FIFOB, data[counter]);
		counter++;
		count--;
	}

#if 0
	outb(CMDR, 0x04);
#endif

	/* Wait for fifo to become empty */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while ((inb(ISR3) & 0x04) && status == EWOULDBLOCK);	/* Fifo is not empty */

	outb(CMDR, 0x08);	/* Issue STOP to TURBO488 */

	/* Wait for DONE and STOP */
	if (status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR3) & 0x11) && status == EWOULDBLOCK);	/* not done and stop */

	outb(IMR2, 0x00);	/* we have to enable DMA (0x30) for turbo488 to work */
	outb(CMDR, 0x20);	/* soft reset turbo488 */
	outb(CMDR, 0x10);	/* reset fifos */


	/*
	 * Send last byte with EOI set
	 * Here EOI is handled correctly since the string to be sent
	 * is actually all sent during the ioctl.  (See above)
	 */
	if (count == 1 && status == EWOULDBLOCK)  {	/* Count should always=1 here */

		do {
			if (!(inb(ISR1) & 2)) status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
		} while (!(inb(ISR1) & 2) && (status == EWOULDBLOCK));

		outb(AUXMR, seoi);	/* Set EOI for the last byte */
		outb(AUXMR, 0x5E);	/* Clear SYNC */
		outb(CDOR, data[counter]);
		counter++;
		count--;
	}

	do {
		if (!(inb(ISR1) & 2)) status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR1) & 2) && (status == EWOULDBLOCK));


	if (!(inb(ISR1) & 2) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR1) & 2) && status == EWOULDBLOCK);
	outb(AUXMR, tca);	/* Regain full control of the bus */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, 63);		/* unlisten */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);


	outb(AUXMR, 0x5E);	/* Clear SYNC */
	outb(CDOR, 95);		/* untalk */
	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	return (counter);
}

static int
readgpibfifo(unsigned char device, char *data, int count)
{
	int status;
	int status2 = 0;
	int status1;
	int counter;
	int fifopos;
	unsigned inword;

	outb(IMR2, 0x30);	/* we have to enable DMA (0x30) for turbo488 to work */
#if 0
	outb(IMR3, 0x1F);
	outb(INTR, 1);
#endif
	outb(CMDR, 0x20);

	outb(CFG, 14 + 0x60 + 1); /* Halt on int, read, fifo B first, CCEN TMOE TIM */
	outb(CMDR, 0x10);	/* RESET fifos */
	outb(CCRG, tcs);	/* program to tcs at end */
	outb(CMDR, 0x08);	/* STOP?? */

	status = EWOULDBLOCK;
	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, 32);		/* Address controller (me) to listen */

	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, (device & 31) + 64);	/* address device to talk */

	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(AUXMR, gts);	/* Set to Standby (Controller) */

	counter = 0;
	fifopos = 0;

	outb(CMDR, 0x04);	/* Tell TURBO488 to GO */

	do {
		status1 = inb(ISR3);
		if (!(status1 & 0x01) && (status1 & 0x04)) {
			status2 = inb(STS2);
			inword = inw(FIFOB);
			*(unsigned *)(data + counter) = inword;
#if 0
			printf ("Read:%c, %c\n", data[counter], data[counter + 1]);
#endif
			counter += 2;
		} else {
			status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 4);
		}
	} while (!(status1 & 0x01) && status == EWOULDBLOCK);
	if (!(status2 & 0x04)) {	/* Only 1 byte came in on last 16 bit transfer */
		data[counter-1] = 0;
		counter--;
	} else
		data[counter] = 0;
	outb(CMDR, 0x08);	/* send STOP */

	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR3) & 0x11) && status == EWOULDBLOCK);	/* wait for DONE and STOP */
	outb(AUXMR, 0x55);

	outb(IMR2, 0x00);	/* we have to enable DMA (0x30) for turbo488 to work */
	outb(CMDR, 0x20);	/* soft reset turbo488 */
	outb(CMDR, 0x10);	/* reset fifos */

#if 0
	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR1) & 2));
#endif
	outb(AUXMR, tca);	/* Regain full control of the bus */

	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);
	outb(CDOR, 63);		/* unlisten */

	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(AUXMR, 0x5E);	/* Clear SYNC */
	outb(CDOR, 95);		/* untalk */
	do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	return (counter);
}


/* Return the status byte from device */
static char
spoll(unsigned char device)
{
	int status = EWOULDBLOCK;
	unsigned int statusbyte;

	if (!(inb(ISR2) & 8)) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, (device & 31) + 64);	/* address device to talk */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, 32);		/* Address controller (me) to listen */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);
	outb(AUXMR, 0x5E);
	outb(CDOR, 0x18);	/* Send SPE (serial poll enable) */
	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	/* wait for bus to be synced */
	if (!(inb(ISR0) & 1) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR0) & 1) && status == EWOULDBLOCK);

	outb(AUXMR, gts);	/* Set to Standby (Controller) */

	if (!(inb(ISR1) & 1) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR1) & 1) && status == EWOULDBLOCK);
	outb(AUXMR, 0x5E);
	outb(AUXMR, tcs);	/* Take control after next read */
	statusbyte = inb(DIR);

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, 0x19);	/* SPD (serial poll disable) */

	/* wait for bus to be synced */
	if (!(inb(ISR0) & 1) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR0) & 1) && status == EWOULDBLOCK);


	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	outb(CDOR, 95);		/* untalk */

	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);
	outb(AUXMR, 0x5E);
	outb(CDOR, 63);		/* unlisten */
	if (!(inb(ISR2) & 8) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR2) & 8) && status == EWOULDBLOCK);

	/* wait for bus to be synced */
	if (!(inb(ISR0) & 1) && status == EWOULDBLOCK) do {
		status = tsleep((caddr_t)&gpib_sc, GPIBPRI, "gpibpoll", 1);
	} while (!(inb(ISR0) & 1) && status == EWOULDBLOCK);

	return (statusbyte);
}