#include <sys/fcntl.h>
#include <sys/open.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/edt.h>
#include <sys/sysmacros.h>

#include "rciioctl.h"
#include "xydreg.h"

/* the idiots the idiots the *idiots*!  This is perfectly legal!
   I had hoped nobody but Sun was shipping a compiler this backward..... */
/* static unsigned char idcheck[20] = "VMEIDXYC240    1 11 "; */
static unsigned char idcheck[] = "VMEIDXYC240    1 11 ";

extern int xyd_internal_major;
extern XydSoftc xydi[];

static int goodid();

/* determines the device sub-type from the device type */
#define DEV_SUBTYPE(d)	(minor(d)>>NUM_UNIT_BITS)

/* determines the device unit number from the device type */
#define DEV_UNIT(d)	((minor(d) & ((1<<NUM_UNIT_BITS)-1)))

/* macro to construct a bitmask */
#define BITMASK(n)		(1<<(n))

/* flags to test for any device being open */
#define ANY_OPEN   		(BITMASK(XYD_TYPE) | BITMASK(XY2DA_TYPE) | \
				 BITMASK(XY2DB_TYPE))

xydintr(unit)
int unit;
{
	/* Called in response to an interrupt from the XYD */

	XydSoftc *info;
	XydSubInfo *subi;
	int pending;
	int subtype;
	int s;

	info = &xydi[unit];

	while (pending = info->xyd->intpend)
	 { info->xyd->intclear = pending;

	   /* poll for each subtype */
	   for (subtype=0; subtype<NUM_DEV_TYPES && pending; subtype++)
	    { if (info->flags & BITMASK(subtype))/* is sub device open? */
	       { subi = &info->subi[subtype];
		 if (pending & subi->intrMask)
		  { 
		    s = splhi();
		    if (subi->intrCnt < subi->qsize)
		     { subi->intrCnt++;		/* bump the counter */
		       pollwakeup (&subi->inpq);/* wakeup pending selects */
		     }
		    splx(s);
		    pending &= ~(subi->intrMask);
		  }
	       }
	    }
	   if (pending)
	    { /* declare a stray interrupt */
	      printf ("xyd%d: stray interrupt(s); mask=0x%x\n",unit,pending);
	    }
	 }
}

static int goodid(xyd)
volatile Xyd *xyd;
{
	/* Verify that the board is really an XVME-240, using the
	   ID signature built in to the board. */

	int i;

	for (i=0;i<20;i++)
	 { if (badaddr(&xyd->id[i+i+1],1))
	    { printf ("xyd0: not present");
	      return(0);
	    }
	   if (xyd->id[i+i+1] != idcheck[i])
	    { printf ("xyd0: not an xyd");
	      return(0);
	    }
	 }
	return(1);
}

xydedtinit(e)
struct edt *e;
{
	/* Initialization routine. Verify that the board is there, and
	   initialize its registers to something reasonable. */

	volatile Xyd *xyd;
	int i;

	xydi[e->e_intr_info->v_unit].xyd = 0;
	xyd = (volatile Xyd *) e->e_base;
	if (! goodid(xyd))
	 { return;
	 }
	xyd->csr = XYD_RESET;
	xyd->csr = 0;
	xyd->csr = XYD_RED_OFF | XYD_GREEN_OFF;
	xyd->dirs = XY2D_DIRMASK;
	xyd->vector = e->e_intr_info->v_vec;
	xydi[e->e_intr_info->v_unit].xyd = xyd;
	printf("xydedtinit: unit %d vector %x\n",e->e_intr_info->v_unit,e->e_intr_info->v_vec);
}

xydopen(dev,flag,otyp)
dev_t dev;
int flag;
int otyp;
{
	/* This device is an exclusive open device: only one process
	   may open it at a time. This prevents contention problems
	   and makes the driver software easier to implement. */

	XydSoftc *info;
	XydSubInfo *subi;
	volatile Xyd *xyd;
	int oldflags;
	int subtype;
	int dirmask;
	int s;

	info = &xydi[DEV_UNIT(dev)];
	subtype = DEV_SUBTYPE(dev);
	subi = &info->subi[subtype];

	oldflags = info->flags;

	if (! info->xyd)
	 { u.u_error = ENXIO;
	   return;
	 }
	xyd = info->xyd;
	s = splhi();
	if ((info->flags & BITMASK(XYD_TYPE)) || 
	    (info->flags & BITMASK(subtype)) )
	 { splx(s);
	   u.u_error = EBUSY;
/*DBG*/printf ("failed open (EBUSY) of subtype %d\n", subtype);
	   return;
	 }
	else
	 { info->flags |= BITMASK(subtype);
	 }
	splx(s);
/*DBG*/printf ("open of subtype %d\n", subtype);

	subi->qsize = TQ_DEFAULT_SIZE;
	subi->intrCnt = 0;
	subi->type = subtype;
	subi->buf1 = 0xdead;
	subi->buf2 = 0xbeef;

	switch (subtype)
	 { case XYD_TYPE:
	    { dirmask = XYD_DIRMASK;
	      subi->intrMask = XYD_INTRMASK;
	      subi->intrClear = XYD_INTRCLEAR;
	      break;
	    }
	   case XY2DA_TYPE:
	    { dirmask = XY2D_DIRMASK;
	      subi->intrMask = XY2DA_INTRMASK;
	      subi->intrClear = XY2DA_INTRCLEAR;
	      break;
	    }
	   case XY2DB_TYPE:
	    { dirmask = XY2D_DIRMASK;
	      subi->intrMask = XY2DB_INTRMASK;
	      subi->intrClear = XY2DB_INTRCLEAR;
	      break;
	    }
	 }

	if (xyd->dirs != dirmask)
	 { xyd->dirs = dirmask;
	 }
	if (oldflags & BITMASK(XY2DA_TYPE))
	 { if (xyd->intmask & ~XY2DA_INTRMASK)
	    { xyd->intmask &= XY2DA_INTRMASK;
	    }
	 }
	else if (oldflags & BITMASK(XY2DB_TYPE))
	 { if (xyd->intmask & ~XY2DB_INTRMASK)
	    { xyd->intmask &= XY2DB_INTRMASK;
	    }
	 }
	xyd->intclear = subi->intrClear;
}

static doclose(info, subi)
XydSoftc *info;
XydSubInfo *subi;
{
	volatile Xyd *xyd;
	int s;

	xyd = info->xyd;

	if ((info->flags & ANY_OPEN) == BITMASK(subi->type))
	 { /* we are the last device to close */

	   xyd->csr = XYD_RED_OFF | XYD_GREEN_OFF;
	   xyd->intmask = 0;
	   xyd->intclear = 0xff;
	 }
	else
	 { /* be more conservative in what we do when leaving */

	   if (xyd->intmask & subi->intrMask)
	    { xyd->intmask &= ~(subi->intrMask);
	    }
	   xyd->intclear = subi->intrClear;
	 }

	s = splhi();
	info->flags &= ~(BITMASK(subi->type));
	splx(s);
}


xydclose(dev,flag,otyp)
dev_t dev;
int flag;
int otyp;
{
	/* When closing the device, reset the registers to what
	   they were when the device was openned. */

	XydSoftc *info;
	XydSubInfo *subi;

	info = &xydi[DEV_UNIT(dev)];
	subi = &info->subi[DEV_SUBTYPE(dev)];

	/* check for soft-close */
	if (! (info->flags & BITMASK(subi->type)) )
	 { /* don't return an error, since this may happen normally */
/*DBG*/printf ("hard close returned, nothing to do for %d\n", subi->type);
	   return;
	 }
	doclose (info, subi);
/*DBG*/printf ("hard close called for %d\n", subi->type);
}

xydread(dev)
dev_t dev;
{
	/* The read routine returns a single byte (set to 1) if an interrupt
	   arrived. */

	XydSubInfo *subi;
	int cnt, s;
	char ack = 1;

	subi = &xydi[DEV_UNIT(dev)].subi[DEV_SUBTYPE(dev)];

	/* check for soft-close */
	if (! (xydi[DEV_UNIT(dev)].flags & BITMASK(subi->type)) )
	 { u.u_error = EBADF;
	   return;
	 }

	s = splhi();
	cnt = subi->intrCnt;
	if (cnt)
	 { subi->intrCnt--;
	 }
	splx(s);
	if (cnt)
	 { if (copyout (&ack, u.u_base, 1))
	    { u.u_error = EFAULT;
	      return;
	    }
	   u.u_count--;
	 }
}

xydwrite(dev)
dev_t dev;
{
	u.u_error = ENXIO;
}

xydpoll(dev, events)
dev_t dev;
int *events;
{
	/* Check to see if any interrupt events have arrived. If not,
	   then queue the process for a possible select wakeup. */

	XydSubInfo *subi;
	int ready;
	int cnt, s;

	subi = &xydi[DEV_UNIT(dev)].subi[DEV_SUBTYPE(dev)];

	/* check for soft-close */
	if (! (xydi[DEV_UNIT(dev)].flags & BITMASK(subi->type)) )
	 { u.u_error = EBADF;
	   return;
	 }

	ready = *events;
	if (ready & POLLIN)
	 { s = splhi();
	   if (subi->intrCnt == 0)
	    { pollqueue (&subi->inpq, u.u_procp);
	      *events &= ~POLLIN;
	    }
	   splx(s);
	 }
}

xydioctl(dev,cmd,arg)
dev_t dev;
int cmd, arg;
{
	XydSoftc *info;
	XydSubInfo *subi;

	info = &xydi[DEV_UNIT(dev)];
	subi = &info->subi[DEV_SUBTYPE(dev)];

	/* check for soft-close */
	if (! (info->flags & BITMASK(subi->type)) )
	 { u.u_error = EBADF;
	   return;
	 }

	switch (cmd)
	 { default:
	    { u.u_error = ENOTTY;
	      break;
	    }
	   case RCIIOC_CONFIRM:
	    { break;
	    }
	   case RCIIOC_INTR_ENABLE:
	    { int mask = arg;
	      volatile Xyd *xyd = info->xyd;
	      int s;

	      s = splhi(); /* is this really necessary? */
	      if (mask)
	       { xyd->intmask |= subi->intrMask;
		 xyd->intclear = subi->intrClear;
		 if (! (xyd->csr & XYD_INTENB))
		  { xyd->csr |= XYD_INTENB;
		  }
	       }
	      else
	       { if ((info->flags & ANY_OPEN) == BITMASK(subi->type))
		  { /* then we are the only ones, so we can clear all */
		    xyd->csr &= ~XYD_INTENB;
		    xyd->intmask = 0;
		    xyd->intclear = 0xff;
		  }
		 else
		  { xyd->intmask &= ~subi->intrMask;
		    xyd->intclear = subi->intrClear;
		  }
	       }
	      splx(s);
	      break;
	    }
	   case RCIIOC_SET_INTRQ:
	    { int size = arg, s;

	      if (size <= 0)
	       { u.u_error = EINVAL;
	       }
	      else
	       { s = splhi();
		 subi->qsize = size;
		 subi->intrCnt = 0;
		 splx(s);
	       }
	      break;
	    }
	   case RCIIOC_GET_INTRQ:
	    { int size;

	      size = subi->qsize;
	      if (copyout ((char*)&size, (char*)arg, sizeof(int)))
	       { u.u_error = EFAULT;
		 break;
	       }
	      break;
	    }
/* RCIIOC_SOFT_CLOSE is a special hack just for the SGI machines, since
   it seems there is a bug: calling close(2) does not always result
   in the driver close routine being called right away. So we fake
   it with an ioctl, just to be sure. What a botch! */
	   case RCIIOC_SOFT_CLOSE:
	    { doclose (info, subi);      
/*DBG*/printf ("soft close called for %d\n", subi->type);
	      break;
	    }
	 }
}
