/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1987, 1991 by John E. Lloyd.			    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/

#include "../h/cdefs.h"
#include "../h/IOdevice.h"
#include "../h/xy2dDevice.h"
#include "../h/errorCodes.h"

#include "../drivers/driErrorMsgs.h"

#define CYCLES_PER_MS	1000		/* rough, for a SUN 4 */
#define DEFAULT_TIMEOUT	10*CYCLES_PER_MS

extern unsigned char *rciIOpage;

#define SEND_SIGNAL() \
	xy2d->outFlags ^= sigOut;

static char *format = "oflags=0x%x iflags=0x%x obuf=0x%x ibuf=0x%x\n";

#define WAIT_FOR_SIGNAL(x) \
	while (!(xy2d->intrInputs & sigIn) && --k) \
	   ; \
	xy2d->intrClear = sigIn; \
	if (k == 0) \
	 { errorMsg (0, format,xy2d->outFlags,xy2d->intrInputs,*obuf,*ibuf); \
	   errorMsg (ETimeout, driTimeoutMsg, fxnName, (x)); \
	   return (-1); \
	 }

/* macro to determine device sub-unit number */
#define SUB_UNIT(dev)	(((dev)->address & 0x1) ? 1 : 0)

static int hardwareInit();
static int interruptEnable();
static int probeTest();

xy2dRead (dev, buf, size)
IO_DEVICE *dev;
void *buf;
int size;
{
	char *fxnName = "xy2dRead()";
	struct xy2dreg *xy2d = (struct xy2dreg *)dev->location;

	register volatile unsigned short *ibuf;
	register volatile unsigned short *obuf;
	register unsigned short *dataPtr;
	register int count;		    /* word count register	*/
	register int i;

	short myChecksum;		/* computes checksum		*/
	short theirChecksum;
	unsigned long k;		/* timeout counter		*/
	int byteCount;			/* number of bytes read in  	*/
	short sigIn;
	short sigOut;

	if (SUB_UNIT(dev) == 0)
	 { sigIn = XY2D_SIGIN1;
	   sigOut = XY2D_SIGOUT1;
	   obuf = &xy2d->buf[0];
	   ibuf = &xy2d->buf[1];
	 }
	else
	 { sigIn = XY2D_SIGIN2;
	   sigOut = XY2D_SIGOUT2;
	   obuf = &xy2d->buf[2];
	   ibuf = &xy2d->buf[3];
	 }

	*obuf = XY2D_READ_READY;

	k = dev->blocktime * CYCLES_PER_MS * 5;
	WAIT_FOR_SIGNAL(1);

	*obuf = 0;
	count = *ibuf;
	dev->byteCount = count*sizeof(short);

	/* ----- check for bogus count: negative, or too big ----- */

	if (count < 0 || size < dev->byteCount)
	 { errorMsg (ECount, driCountMsg, fxnName, count);
	   xy2d->outFlags &= ~sigOut;
	   return (-1);
	 }

	/* ----- now read in all the words ----- */

	k = (count+2)*dev->timeout;

	dataPtr = (unsigned short *)buf;
	myChecksum = 0;
	i = count;
	while (i--)
         { SEND_SIGNAL();
	   WAIT_FOR_SIGNAL(2);
	  	
	   *dataPtr = *ibuf;
	   myChecksum += *dataPtr++;
	 }

	/* ----- now get their checksum ----- */

	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(3);

	theirChecksum = *ibuf;
	
	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(4);

	/* input buffer is now clear */

	if (myChecksum != theirChecksum)
	 { errorMsg (ETransmit, driChecksumMsg, 
				fxnName, theirChecksum, myChecksum);
	   rciPrintf ("xy2dRead: bad checksum. Count = %d\n", count);
	   for (i=0; i<count; i++)
	    { rciPrintf ("%2d: 0x%4x\n", i, ((unsigned short*)buf)[i] );
	    }
	   return (-1);
	 }
	return (dev->byteCount);
}

xy2dWrite (dev, buf, size)
IO_DEVICE *dev;
void *buf;
int size;
{
	char *fxnName = "xy2dWrite()";
	struct xy2dreg *xy2d = (struct xy2dreg *)dev->location;

	register volatile unsigned short *ibuf;
	register volatile unsigned short *obuf;
	register unsigned short *dataPtr;   /* pointer to data buffer	*/
	register int count;		    /* word count register	*/

	short checksum;			    /* computes checksum	*/
	unsigned long k;		    /* timeout counter		*/
	int byteCount;			    /* number of bytes read in  */
	short sigIn;
	short sigOut;

	if (SUB_UNIT(dev) == 0)
	 { sigIn = XY2D_SIGIN1;
	   sigOut = XY2D_SIGOUT1;
	   obuf = &xy2d->buf[0];
	   ibuf = &xy2d->buf[1];
	 }
	else
	 { sigIn = XY2D_SIGIN2;
	   sigOut = XY2D_SIGOUT2;
	   obuf = &xy2d->buf[2];
	   ibuf = &xy2d->buf[3];
	 }

        count = size/sizeof(short);
	if (size % sizeof(short))
	 { count++;
	 }
        if (count < 0)
         { errorMsg (ECount, driCountMsg, fxnName, count);
	   *obuf = 0;
	   xy2d->outFlags &= ~sigOut;
	   return (-1);
	 }

	*obuf = XY2D_WRITE_READY;
	
	k = dev->blocktime * CYCLES_PER_MS;
	while (*ibuf != XY2D_READ_READY && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 0);
	   *obuf = 0;
	   xy2d->outFlags &= ~sigOut;
	   return (-1);
	 }
        *obuf = count;

	k = (count+2)*dev->timeout;
	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(1);

	dataPtr = (unsigned short *)buf;
	
	/* ----- send words down the chute ----- */

	checksum = 0;
	while (count--)
	 { checksum += *dataPtr;
	   *obuf = *dataPtr++;		/* load the xy2d */

	   SEND_SIGNAL();
	   WAIT_FOR_SIGNAL(2);
         }

	*obuf = checksum;
	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(3);

	*obuf = 0;
	SEND_SIGNAL();

	/* make sure signal bit is clear before we leave routine */

	if (xy2d->outFlags & sigOut)
	 { WAIT_FOR_SIGNAL(4);
	   SEND_SIGNAL();
	 }

	k = 1000;
	while (--k)
	   ;

	return (size);
}

xy2dProbe (dev)
IO_DEVICE *dev;
{
	unsigned char *location;
	struct xy2dreg *xy2d;

	location = rciIOpage + (dev->address & ~0x1);

	if (!probeTest (location))
	 { return (-1);
	 }
	xy2d = (struct xy2dreg *)(location);

	dev->location = location;
	dev->timeout = DEFAULT_TIMEOUT;
	dev->blocktime = XY2D_DEFAULT_BLOCKTIME;
	dev->size = sizeof (struct xy2dreg);

	/* set up the hardware properly */
	if (xy2d->portDirection != XY2D_PORT_DIRECTIONS)
	 { xy2d->portDirection = XY2D_PORT_DIRECTIONS;
	 }
	if (SUB_UNIT(dev) == 0)
	 { xy2d->buf[0] = 0;
	   xy2d->outFlags &= ~0xf0;
	   xy2d->intrClear = 0x0f;
	 }
	else
	 { xy2d->buf[2] = 0;
	   xy2d->outFlags &= ~0x0f;
	   xy2d->intrClear = 0xf0;
	 }
	return (dev->size);
}

xy2dAttach (dev)
IO_DEVICE *dev;
{
	char *fxnName = "xy2dAttach()";
	struct xy2dreg *xy2d = (struct xy2dreg *)dev->location;
	unsigned long k;
	register volatile unsigned short *ibuf;
	register volatile unsigned short *obuf;
	short sigIn;
	short sigOut;

	if (SUB_UNIT(dev) == 0)
	 { sigIn = XY2D_SIGIN1;
	   sigOut = XY2D_SIGOUT1;
	   obuf = &xy2d->buf[0];
	   ibuf = &xy2d->buf[1];
	 }
	else
	 { sigIn = XY2D_SIGIN2;
	   sigOut = XY2D_SIGOUT2;
	   obuf = &xy2d->buf[2];
	   ibuf = &xy2d->buf[3];
	 }

	if (*ibuf != XY2D_LISTEN)
	 { errorMsg (EConnect, driListeningMsg, fxnName);
	   return (-1);
	 }
	*obuf = XY2D_LISTEN;

	/* send interrupt to other end */
	xy2d->outFlags &= ~sigOut;	
	xy2d->outFlags |= sigOut;

	k = dev->timeout*100;

	while (*ibuf && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 1);
	   return (-1);
	 }

	xy2d->outFlags &= ~sigOut;
	*obuf = 0;

	k = dev->timeout*100;
	while (!(xy2d->intrInputs & sigIn) && --k)
	   ;

	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 1);
	   return (-1);
	 }
	xy2d->intrClear = sigIn;

	return (1);	
}

xy2dDetach (dev)
IO_DEVICE *dev;
{
	struct xy2dreg *xy2d;

	if (dev->location)
	 { xy2d = (struct xy2dreg *)dev->location;

	   if (SUB_UNIT(dev) == 0)
	    { xy2d->outFlags &= 0xf0;
/*	      xy2d->intrMask &= 0xf0; */
	      xy2d->buf[0] = 0;
	    }
	   else
	    { xy2d->outFlags &= 0x0f;
/*	      xy2d->intrMask &= 0x0f; */
	      xy2d->buf[2] = 0;
	    }
	   return (1);
	 }
	else
	 { return (0);
	 }
}

xy2dSignal (dev, n)
IO_DEVICE *dev;
int n;
{
	char *fxnName = "xy2dSignal()";
	struct xy2dreg *xy2d = (struct xy2dreg *)dev->location;
       	int k;
	int retval = 1;

	register volatile unsigned short *ibuf;
	register volatile unsigned short *obuf;
	short intrSend;

	if (SUB_UNIT(dev) == 0)
	 { intrSend = XY2D_INTR_SEND1;
	   obuf = &xy2d->buf[0];
	   ibuf = &xy2d->buf[1];
	 }
	else
	 { intrSend = XY2D_INTR_SEND2;
	   obuf = &xy2d->buf[2];
	   ibuf = &xy2d->buf[3];
	 }

	if (n < 1 || n > 2)
	 { errorMsg (ECount, driCountMsg, fxnName, n);
	   return (-1);
	 }
	if (xy2d->outFlags & intrSend || *ibuf == XY2D_SIGNAL_ACK)
	 { errorMsg (ESync, driSyncMsg, fxnName);
	   return (-1);
	 }
	*obuf = n;
	xy2d->outFlags |= intrSend;
	k = dev->timeout * 50;
	while (*ibuf != XY2D_SIGNAL_ACK && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 1);
	   retval = -1;
	 }
	xy2d->outFlags &= ~intrSend;	/* clear interrupt */
	*obuf = 0;

	k = dev->timeout * 100;
	while (*ibuf == XY2D_SIGNAL_ACK && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 2);
	   return (-1);
	 }

	return (retval);
}

xy2dIoctl (dev, com, data)
IO_DEVICE *dev;
int com;
void *data;
{
	char *fxnName = "xy2dIoctl()";
	struct xy2dreg *xy2d = (struct xy2dreg *)dev->location;

	switch (com)
	 { case IOC_SIGINTER_ENABLE:
	    { /* interruptEnable (xy2d, SUB_UNIT(dev), 1); */
	      break;
	    }
	   case IOC_SIGINTER_DISABLE:
	    { /* interruptEnable (xy2d, SUB_UNIT(dev), 0); */
	      break;
	    }
	   case IOC_TIMEOUT:
	    { dev->timeout = *(int *)data;
	      break;
	    }
	   case IOC_GET_BLOCKTIME:
	    { *(int *)data = dev->blocktime;
	      break;
	    }
	   case IOC_SET_BLOCKTIME:
	    { dev->blocktime = *(int *)data;
	      break;
	    }
	   default:
	    { errorMsg (EBadIoctl, driIoctlMsg, fxnName, com);
	      return (-1);
	    }
	 }
	return (1);
}

xy2dTestRead (dev)
IO_DEVICE *dev;
{
	unsigned short *ibuf;

	ibuf = &((struct xy2dreg *)(dev->location))->buf[1+SUB_UNIT(dev)*2];
	if (*ibuf == XY2D_WRITE_READY)
	 { return (1);
	 }
 	else
	 { return (0);
	 }
}

xy2dTestWrite (dev)
IO_DEVICE *dev;
{
	unsigned short *ibuf;

	ibuf = &((struct xy2dreg *)(dev->location))->buf[1+SUB_UNIT(dev)*2];
	if (*ibuf == XY2D_READ_READY)
	 { return (1);
	 }
 	else
	 { return (0);
	 }
}

xy2dIntrAttach (intr, enable)
IO_INTERRUPT *intr;
int enable;
{
	/* This function is called at high ipl. */

	unsigned char *location;
	struct xy2dreg *xy2d;

	location = (rciIOpage + intr->address);
	if (!probeTest(location))
	 { return (-1);
	 }
	xy2d = (struct xy2dreg *)(location);
	intr->location = location;

	if (enable)
	 { hardwareInit (xy2d);
	   xy2d->intrVector = intr->vector;
	   interruptEnable (xy2d, 1);
	 }
	else
	 { interruptEnable (xy2d, 0);
	 }
	return (1);
}

xy2dIntrHandler(intr)
IO_INTERRUPT *intr;
{
	struct xy2dreg *xy2d = (struct xy2dreg*)(intr->location);

	xy2d->intrClear = 0xff;
}

static hardwareInit (xy2d)
struct xy2dreg *xy2d;
{
	if (xy2d->csr != (XY2D_GREEN_ON|XY2D_RED_OFF))
	 { xy2d->csr = 0;
	   xy2d->csr = XY2D_RESET;
	   xy2d->csr = XY2D_GREEN_ON | XY2D_RED_OFF;
	 }
	if (xy2d->portDirection != XY2D_PORT_DIRECTIONS)
	 { xy2d->portDirection = XY2D_PORT_DIRECTIONS;
	 }
}

static interruptEnable (xy2d, enable)
struct xy2dreg *xy2d;
int enable;
{
	if (enable)
	 { xy2d->csr |= XY2D_INTR_ENABLE;
	   xy2d->intrMask |= (XY2D_INTR_MASK);
	 }
	else
	 { xy2d->csr &= ~XY2D_INTR_ENABLE;
	   xy2d->intrMask &= ~(XY2D_INTR_MASK);
	 }
}

static probeTest (loc)
unsigned char *loc;
{
	char *idStr = "VMEIDXYC240";
	char *idp;
	int i;

	idp = (char*)loc + 1;
	for (i=0; i<strlen(idStr); i++)
	 { if (rciBadAddr (idp, sizeof(char)))
	    { return (0);
	    }
	   if (*idp != idStr[i])
	    { return (0);
	    }
	   idp += 2;
	 }
	return (1);
}
