/*-=-=-=-=-=-=-=-=-=-=-=- 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/xydDevice.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()	  *xydPbuf ^= XYD_SIGOUT; bitTest ^= XYD_SIGIN;

#ifdef VXWORKS_CFG /* GRRRR! */
static char *format = "pbuf=0x%x obuf=0x%x ibuf=0x%x test=0x%x\n";
#else
static char *format = "pbuf=0x%hx obuf=0x%hx ibuf=0x%hx test=0x%hx\n";
#endif
#define WAIT_FOR_SIGNAL(x) \
	while (((XYD_SIGIN & *xydPbuf) == bitTest || \
		(XYD_SIGIN & *xydPbuf) == bitTest) && --k) \
	   ; \
	if (k == 0) \
	 { errorMsg (0, format, xydPbuf[0], xydPbuf[1], xydPbuf[2], bitTest); \
	   errorMsg (ETimeout, driTimeoutMsg, fxnName, (x)); \
	   return (-1); \
	 }

static union { unsigned short s;
	unsigned char c[2];
  } swap;

#define	READ_AND_SWAP(p) \
      (swap.c[1]= *(unsigned char*)(p), \
       swap.c[0]=((unsigned char*)(p))[1], swap.s)

#define	SWAP_AND_WRITE(p,v) \
      (swap.s=(v),*(unsigned char*)(p)=swap.c[1], \
       ((unsigned char*)(p))[1]=swap.c[0])

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

xydRead (dev, buf, size)
IO_DEVICE *dev;
void *buf;
int size;
{
	char *fxnName = "xydRead()";
	struct xydreg *xyddev = (struct xydreg *)dev->location;

	register volatile unsigned short *xydPbuf;   /* parallel port protocal bits */
	register volatile unsigned short *xydBuf;    /* parallel port input buf	*/
	register volatile unsigned short *dataPtr;   /* pointer to data buffer	*/
	register unsigned short volatile bitTest;  /* used to test for 'ready'	*/
	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  	*/

	xydBuf = &(xyddev->ibuf);
	xydPbuf = &(xyddev->pbuf);
	bitTest = (*xydPbuf & XYD_SIGIN);	/* get initial csr value */

	SWAP_AND_WRITE(&xyddev->obuf, XYD_READ_READY);

	k = dev->blocktime * CYCLES_PER_MS * 5;
#if 0
	while (READ_AND_SWAP(&xyddev->ibuf) != XYD_WRITE_READY && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 0);
	   *xydBuf = 0;
	   *xydPbuf &= ~XYD_SIGOUT;
	   return (-1);
	 }
#endif
	WAIT_FOR_SIGNAL(1);

	xyddev->obuf = 0;
	count = (int)READ_AND_SWAP(xydBuf);
	dev->byteCount = count*sizeof(short);

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

	if (count < 0 || size < dev->byteCount)
	 { errorMsg (ECount, driCountMsg, fxnName, count);
	   *xydPbuf &= ~XYD_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 = READ_AND_SWAP(xydBuf);
	   myChecksum += *dataPtr++;
	 }

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

	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(3);

	theirChecksum = READ_AND_SWAP(xydBuf);
	
	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(4);

	/* input buffer is now clear */

	if (myChecksum != theirChecksum)
	 { errorMsg (ETransmit, driChecksumMsg, 
				fxnName, theirChecksum, myChecksum);
	   rciPrintf ("xydRead: 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);
}

xydWrite (dev, buf, size)
IO_DEVICE *dev;
void *buf;
int size;
{
	char *fxnName = "xydWrite()";
	struct xydreg *xyddev = (struct xydreg *)dev->location;

	register unsigned short *xydPbuf;   /* parallel port protocal bits */
	register unsigned short *xydBuf;    /* parallel port output buf	*/
	register unsigned short *dataPtr;   /* pointer to data buffer	*/
	register unsigned short bitTest;    /* used to test for 'ready'	*/
	register int count;		    /* word count register	*/

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

	xydBuf = &(xyddev->obuf);
	xydPbuf = &(xyddev->pbuf);

        count = size/sizeof(short);
	if (size % sizeof(short))
	 { count++;
	 }
        if (count < 0)
         { errorMsg (ECount, driCountMsg, fxnName, count);
	   *xydBuf = 0;
	   *xydPbuf &= ~XYD_SIGOUT;
	   return (-1);
	 }

	SWAP_AND_WRITE(&xyddev->obuf, XYD_WRITE_READY);
	
	k = dev->blocktime * CYCLES_PER_MS;
	while (READ_AND_SWAP(&xyddev->ibuf) != XYD_READ_READY && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 0);
	   *xydBuf = 0;
	   *xydPbuf &= ~XYD_SIGOUT;
	   return (-1);
	 }
        SWAP_AND_WRITE(xydBuf,count);
	bitTest = (*xydPbuf & XYD_SIGIN) ^ XYD_SIGIN;	/* get signal bit */

	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;
	   SWAP_AND_WRITE (xydBuf, *dataPtr++);		/* load the xyd */

	   SEND_SIGNAL();
	   WAIT_FOR_SIGNAL(2);
         }

	SWAP_AND_WRITE (xydBuf, checksum);
	SEND_SIGNAL();
	WAIT_FOR_SIGNAL(3);

	*xydBuf = 0;
	SEND_SIGNAL();

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

#if 1
	if (*xydPbuf & XYD_SIGOUT)
	 { WAIT_FOR_SIGNAL(4);
	   SEND_SIGNAL();
	 }
#else
	k = 100;
	while (--k)
	   ;
	*xydPbuf &= ~XYD_SIGOUT;
#endif
	k = 1000;
	while (--k)
	   ;

	return (size);
}

xydProbe (dev)
IO_DEVICE *dev;
{
	unsigned char *location;
	struct xydreg *xyd;

	location = rciIOpage + dev->address;
	if (!probeTest (location))
	 { return (-1);
	 }
	xyd = (struct xydreg *)(location);

	dev->location = location;
	dev->timeout = DEFAULT_TIMEOUT;
	dev->blocktime = XYD_DEFAULT_BLOCKTIME;
	dev->size = sizeof (struct xydreg);

	/* set up the hardware properly */
	xyd->portDirection = XYD_PORT_DIRECTIONS;

	return (dev->size);
}

xydAttach (dev)
IO_DEVICE *dev;
{
	char *fxnName = "xydAttach()";
	struct xydreg *xyddev = (struct xydreg *)dev->location;
	unsigned long k;
	unsigned short *obuf;

	if (READ_AND_SWAP(&xyddev->ibuf) != XYD_LISTEN)
	 { errorMsg (EConnect, driListeningMsg, fxnName);
	   return (-1);
	 }
	obuf = &xyddev->obuf;
	SWAP_AND_WRITE(obuf, XYD_LISTEN);

	xyddev->pbuf &= ~XYD_SIGOUT;
	xyddev->pbuf |= XYD_SIGOUT;

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

	xyddev->pbuf &= ~XYD_SIGOUT;
	xyddev->obuf = 0;

#if 1
	k = dev->timeout*100;
	while ((xyddev->pbuf & XYD_SIGIN) && --k)
	   ;
	if (k == 0)
	 { errorMsg (ETimeout, driTimeoutMsg, fxnName, 1);
	   return (-1);
	 }
#else
	for (k=1000; k>0; k--)	/* XXX Give other device 1.5 msec. to notice */
	   ;
#endif

	return (1);	
}

xydDetach (dev)
IO_DEVICE *dev;
{
	struct xydreg *xyddev;

	if (dev->location)
	 { xyddev = (struct xydreg *)dev->location;

	   xyddev->pbuf = 0;
	   xyddev->intrMask = 0;
	   xyddev->obuf = 0;
	   return (1);
	 }
	else
	 { return (0);
	 }
}

xydSignal (dev, n)
IO_DEVICE *dev;
int n;
{
	char *fxnName = "xydSignal()";
	struct xydreg *xyddev = (struct xydreg *)(rciIOpage +
						  (dev->address));
       	int k;
	int retval = 1;

	unsigned short *ibuf;
	unsigned short *obuf;

	ibuf = &xyddev->ibuf;
	obuf = &xyddev->obuf;

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

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

	return (retval);
}

xydIoctl (dev, com, data)
IO_DEVICE *dev;
int com;
void *data;
{
	char *fxnName = "xydIoctl()";
	struct xydreg *xyddev = (struct xydreg *)dev->location;

	switch (com)
	 { case IOC_SIGINTER_ENABLE:
	    { interruptEnable (xyddev, 1);
	      break;
	    }
	   case IOC_SIGINTER_DISABLE:
	    { interruptEnable (xyddev, 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);
}

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

	ibuf = &((struct xydreg *)(dev->location))->ibuf;
	if (READ_AND_SWAP(ibuf) == XYD_WRITE_READY)
	 { return (1);
	 }
 	else
	 { return (0);
	 }
}

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

	ibuf = &((struct xydreg *)(dev->location))->ibuf;
	if (READ_AND_SWAP(ibuf) == XYD_READ_READY)
	 { return (1);
	 }
 	else
	 { return (0);
	 }
}

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

	unsigned char *location;
	struct xydreg *xyd;

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

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

xydIntrHandler(intr)
IO_INTERRUPT *intr;
{
	struct xydreg *xyd = (struct xydreg*)(intr->location);

	xyd->intrClear = 0xff;
}

static hardwareInit (xyd)
struct xydreg *xyd;
{
	xyd->csr = 0;
	xyd->csr = XYD_RESET;
	xyd->csr = XYD_GREEN_ON | XYD_RED_OFF;
	xyd->portDirection = XYD_PORT_DIRECTIONS;
	xyd->obuf = 0;
	xyd->pbuf = 0;
	xyd->intrClear = 0xff;
}

static interruptEnable (xyd, enable)
struct xydreg *xyd;
int enable;
{
	if (enable)
	 { xyd->csr |= XYD_INTR_ENABLE;
	   xyd->intrMask |= (XYD_INTR_MASK);
	 }
	else
	 { xyd->csr &= ~XYD_INTR_ENABLE;
	   xyd->intrMask &= ~(XYD_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);
}
