/* Basic RCI driver for a XYCOM XVME 240 digital I/O module connected
   to a DEC DRV11-A (or equivalent).

   Like most RCI drivers, all the really does is provide the 
   connect to interrupt. Once RCI has control, the actual passing
   of data back and forth is done by an RCI-specific driver
   (which often has the same name as this UNIX driver) located
   in $RCCL/drivers.
*/

#include "xyd.h"                        /* File defines number of fifo units*/
#if NXYD > 0                            /* If there are any fifo devices .. */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/dir.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <machine/psl.h>
#include <sundev/mbvar.h>

#include <rciext/spl.h>
#include <rciext/xydreg.h>

#include <rciext/spl.h>
#include <rciext/rci.h>
#include <rciext/rciioctl.h>

/* This takes a unit number as an argument and tells whether it is an	 */
/*  RCI exclusive-open unit or not.  You may want to change this if the	 */
/*  unit number for your device already has magic bits.			 */

#define RCI_UNIT_P(unit) ((unit)&0x80)

/* This complements the above definition.  It takes a unit number and	 */
/*  returns the "real" unit number.  This may be called regardless of	 */
/*  whether or not RCI_UNIT_P is true of the unit number.		 */

#define RCI_REAL_UNIT(unit) ((unit)&0x7f)

RCIINFO xydrciinfo[NXYD];

struct _xyd_softc {
	int	open;		/* device is open */
} xyd_softc[NXYD];

/* define entry points for driver routines */

int     xydprobe();
int	xydattach();

struct  mb_device *xyddinfo[NXYD];
struct  mb_driver xyddriver = 
    { xydprobe, 
      0,
      xydattach,
      0, 0, 0, 
      sizeof(struct xyddevice), 
      "xyd", 
      xyddinfo, 
      0, 0, 0, 0 
    };

/* RCI diagnostic trace routines */

static struct xyddevice *traceDev = NULL;

static setTrace(c)
int c;
{
	int v;
	if (traceDev)
	 { v = traceDev->port[7];
	   traceDev->port[7] = (unsigned char)(v|c);
	   return (v);
	 }
	else
	 { return (-1);
	 }
}

static clearTrace(c)
int c;
{
	int v;
	if (traceDev)
	 { v = traceDev->port[7];
	   traceDev->port[7] = (unsigned char)(v&~c);
	   return (v);	   
	 }
	else
	 { return (-1);
	 }
}

xydprobe(reg, unit)
caddr_t reg;
int unit;
{
	char *idstr = "VMEIDXYC240";
	int i;

	for (i=0; i<strlen(idstr); i++)
	 { if (peekc(&reg[2*i+1]) != idstr[i])
	    { break;
	    }
	 }
	if (i != strlen(idstr))
	 { return (0);
	 }
	for (i=0;i<NXYD;i++)
	 { RCI_cleari (&xydrciinfo[i]);
	 }
/*	RCI_init(); */
	return (sizeof (struct xyddevice));
}

xydattach(md)
struct mb_device *md;
{
	if (md->md_unit == 0)
	 { traceDev = (struct xyddevice *)md->md_addr;
	 }
	xydSetup (md, &xyd_softc[md->md_unit]);
	RCI_setTraceFxns (setTrace, clearTrace);
	return(0);
}

xydopen(dev, flag)
dev_t dev;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	int exclusive = RCI_UNIT_P(minor(dev));
	struct mb_device *mb = xyddinfo[unit];
	struct xyddevice *xyd;
	int s;

	if (unit < 0 || unit >= NXYD || !mb || !mb->md_alive)
	 { printf ("%d %d\n", unit, mb);
	   return(ENXIO);
	 }

	RCI_init();
	if (exclusive)
	 { if (! RCI_alive())
	    { printf ("RCI not alive\n");
	      return(ENXIO);
	    }
	   s = splhigh();
	   if (xydrciinfo[unit].flags & RCI_OPEN)
	    { splx(s);
	      return(EBUSY);
	    }
	   if (! RCI_openi(&xydrciinfo[unit]))
	    { splx(s);
	      return(ENFILE);
	    }
	   splx(s);
	 }
	else
	 { s = splhigh();
	   if (xydrciinfo[unit].flags & RCI_BUSY)
	    { splx(s);
	      return(EBUSY);
	    }
	   xydrciinfo[unit].flags |= RCI_OPEN;
	   splx(s);
	 }
	return(0);
}

xydclose(dev,flag)
dev_t dev;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	struct mb_device *mb = xyddinfo[unit];

	if (unit < 0 || unit >= NXYD || !mb || !mb->md_alive)
	 { return(ENXIO);
	 }
	RCI_closei(&xydrciinfo[unit]);
	return (0);
}

xydioctl(dev,cmd,data,flag)
dev_t dev;
int cmd;
caddr_t data;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	int exclusive = RCI_UNIT_P(minor(dev));
	struct mb_device *mb = xyddinfo[unit];
	struct xyddevice *xyd;
	int error = 0;
	int s;

	if (unit < 0 || unit >= NXYD || !mb || !mb->md_alive)
	 { return(ENXIO);
	 }
	switch (cmd)
	 {
	   default:
	      error = ENOTTY;
	      break;
	   case RCIIOC_CONFIRM:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      break;
	   case RCIIOC_ATTACH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 struct RCI_fxn_arg *ap;
		 rcii = &xydrciinfo[unit];
		 ap = (struct RCI_fxn_arg *) data;
		 s = splhigh();
		 RCI_setup_switch(rcii);
		 rcii->ifxn = ap->function;
		 rcii->iarg = ap->argument;

		 xyd = (struct xyddevice *)mb->md_addr;		 
		 xyd->intrClear = 0xff;
		 xyd->csr |= XYD_INTR_ENABLE;
		 xyd->intrMask = 0x1;
		 splx(s);
	       }
	      break;
    /* Execute a routine */
	   case RCIIOC_EXECUTE:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 struct RCI_fxn_arg *ap;
		 rcii = &xydrciinfo[unit];
		 ap = (struct RCI_fxn_arg *) data;
		 if (RCI_kut_call(ap->function,ap->argument,rcii))
		  { RCI_signal(rcii->pid,rcii->crashsig);
		  }
	       }
	      break;
	   case RCIIOC_KRNLEXEC:
	    { struct RCI_fxn_arg4 *ap;

	      if (! RCI_UNIT_P(unit))
	       { error = ENOTTY;
	       }
	      else
	       { ap = (struct RCI_fxn_arg4 *)data;
		 ap->rval = (*ap->fxn)(ap->arg[0],ap->arg[1],
				       ap->arg[2],ap->arg[3]);
	       }
	      break;
	    }
	   case RCIIOC_KUTEXEC:
	    { extern int RCI_kutexec();

	      if (! RCI_UNIT_P(unit))
	       { error = ENOTTY;
	       }
	      else
	       { kut_callit (RCI_kutexec, (int)data);
	       }
	      break;
	    }
    /* Detach from the device interrupt */
	    case RCIIOC_DETACH:
	       if (! exclusive)
		{ error = ENOTTY;
		} 
	       else
		{ s = splhigh();
		  xydrciinfo[unit].ifxn = 0;

		 xyd = (struct xyddevice *)mb->md_addr;		 
		 xyd->intrMask = 0;
		 xyd->csr &= ~XYD_INTR_ENABLE;
		 xyd->intrClear = 0xff;
		 splx(s);
		}
	       break;
	   /* Request signals from the slave task */
	   case RCIIOC_REQSIGS:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { s = splhigh();
		 xydrciinfo[unit].flags |= RCI_SIGS;
		 splx(s);
	       }
	      break;
	  /* Request a heartbeat */
	   case RCIIOC_HEARTBEAT:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { register RCIINFO *rcii;
		 register struct RCI_heartbeat *ap;
		 int s;
		 int RCI_heartbeat();
		 rcii = &xydrciinfo[unit];
		 ap = (struct RCI_heartbeat *) data;
		 s = splhigh();
		 RCI_setup_switch(rcii);
		 untimeout(RCI_heartbeat,(caddr_t)rcii);
		 rcii->hbticks = ap->howoften;
		 rcii->hbfxn = ap->function;
		 rcii->hbarg = ap->argument;
		 if ((rcii->hbticks > 0) && rcii->hbfxn)
		  { timeout(RCI_heartbeat,(caddr_t)rcii,rcii->hbticks);
		  }
		 splx(s);
	       }
	      break;
	   /* Set up crash signal */
	   case RCIIOC_ONCRASH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { s = splhigh();
		 xydrciinfo[RCI_REAL_UNIT(unit)].crashsig = * (int *) data;
		 splx(s);
	       }
	      break;
	   /* Get crash info */
	   case RCIIOC_GETCRASH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 rcii = &xydrciinfo[unit];
		 if (copyout ((caddr_t)&rcii->lastcrash, 
			      (caddr_t)*(CRASHINFO**)data, 
			       sizeof(CRASHINFO)) == EFAULT)
		  { return (EFAULT);
		  }
		 rcii->lastcrash.type = CI_NULL;
	       }
	      break;
	   /* Get interface information */
	   case RCIIOC_INTERFACE:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCI_get_interface((struct RCI_interface *)data);
	       }
	      break;
	   case RCIIOC_TIMEOUT:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCI_setup_timeout( &xydrciinfo[unit],
				    (struct RCI_timeout *)data );
	       }
	      break;
	 }
	return(error);
 }

xydintrPrint = 0;

xydintr (unit)
int unit;
{
	register RCIINFO *p;
	register struct xyddevice *xyd;
	int s;

	xyd = (struct xyddevice *)xyddinfo[unit]->md_addr;
	xyd->intrClear = 0xff;

	if (xydintrPrint)
	 { printf ("xyd interrupt, unit %d\n", unit);
	 }
	s = splhigh();
	p = &xydrciinfo[unit];
	if (p->flags & RCI_BUSY)
	 {if (p->ifxn)
	    { if (RCI_switch(p,p->ifxn,p->iarg))
	       { RCI_signal(p->pid,p->crashsig);
		 p->ifxn = 0;
	       }
	    }
	 }
	splx(s);
}

xydSetup(md, soft)
struct mb_device *md;
struct xyd_softc *soft;
{
	register struct xyddevice *xyd;

	xyd = (struct xyddevice*)(md->md_addr);

	xyd->csr = 0;
	xyd->csr = XYD_RESET;
	xyd->csr = XYD_GREEN_ON | XYD_RED_OFF;
	xyd->portDirection = 0xf2;	/* port directions used by RCI */
	xyd->intrVector = md->md_intr->v_vec;
	xyd->intrClear = 0xff;
}

#endif
