/*
 * ptirci.c   -  device driver for sbus-connected RCI devices 
 *               (the VME240 parallel card and the VME203 clock card)
 *               (via Performance Technologies PT-SBS915 sbus-to-vme adapter)
 *
 *               which type of sub-device is opened is based on the minor 
 *               device number
 *
 *               Currently hard-coded to allow only one instantiation of each 
 *                     sub-device.
 *
 *               The parallel device is exclusive-open only.
 *
 *    The PTI is setup (and left) as such:
 *       Access Map 1 is VME16  0x0000 - 0xffff
 *       Access Map 2 is VME24  0x800000 - 0xbfffff
 *       Access Map 3 is VME24  0xc00000 - 0xffffff
 *       Access Map 4 is VME32  0x80000000 - 0x803fffff
 *       Access Map 5 is VME32  0x80400000 - 0x807fffff
 *       Access Map 6 is VME32  0x80800000 - 0x80bfffff
 *       Access Map 7 is VME32  0x80c00000 - 0x80ffffff
 *
 *    In short, SBus addrs G0400000 - G040ffff -> VME16 0x0000-0xffff
 *                         G0800000 - G0ffffff -> VME24 0x800000-0xffffff
 *                         G1000000 - G1ffffff -> VME32 0x80000000-0x80ffffff
 *      
 */


/* mapping of minor device numbers to sub-devices */
#define PTI_MINOR 0
#define CLK_MINOR 1
#define PAR_MINOR 2

#define IVEC_CLOCK  0x88
#define ADDR_CLOCK  0x1000

#define IVEC_VME240 0x89
#define ADDR_VME240 0x2000

/* Default clock speed, in milliseconds (max=100, normal=5) */
#define DEFAULT_RATE 5




/*<<<<<<<<<<<<<<<<<<<<<<<<<<  INCLUDED FILES  >>>>>>>>>>>>>>>>>>>>>>>>>>*/
#ifndef	KERNEL    /* some include files need KERNEL to be defined */
#define	KERNEL
#endif

#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <machine/psl.h>
#include <sun/openprom.h>
#include <sun/vddrv.h>
#include <sys/map.h>
#include <sundev/mbvar.h>
#include <sys/time.h>


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

#include "pti.h"
#include "xyclkreg.h"
#include "xydreg.h"



/*<<<<<<<<<<<<<<<<<<<<<<<<  DEBUGGING SWITCHES  >>>>>>>>>>>>>>>>>>>>>>>>*/

/* #define PTI_DEBUG */ /* debugging level: PTI_TRACK, PTI_DEBUG, or none */

#define	PTI_ERROR_MSG		logpri = LOG_ERR;   log

#if defined(PTI_DEBUG) || defined(PTI_TRACK)
#define	PTI_DEBUG_MSG		logpri = LOG_INFO;  log
#else
#define PTI_DEBUG_MSG()
#endif

#ifdef	PTI_TRACK
#define	PTI_TRACK_MSG		logpri = LOG_DEBUG; log
#else
#define	PTI_TRACK_MSG()         
#endif



/*<<<<<<<<<<<<<<<<<<<<<<<<<  DATA  STRUCTURES  >>>>>>>>>>>>>>>>>>>>>>>>>*/

/* struct pti_reg is defined in pti.h
 * In short, it has the fields:
 *     sem[8]
 *     gpbyte0
 *     gpbyte1
 *     map1a, map1b
 *     map2a, map2b
 *     ...
 *     map7a, map7b
 *     rmwc
 *     s2vctrl
 *     vmewina, vmewinb
 *     svaddra, svaddrb
 *     v2sctrl
 *     leds
 *     virqstat
 *     ivec1, ivec2, ..., ivec7
 *     virqctrl
 *     sbusirq
 *     sbusctrl
 */

/* struct xyclkdevice is defined in xyclkreg.h
 * In short, it has the fields:
 *        cntrA_data,
 *	  cntrA_com,
 *	  cntrB_data,
 *	  cntrB_com,
 *	  uic_data,
 *	  uic_com,
 *	  csr,
 *	  quadselA,
 *        quadselB
 */

/* struct xyddevice is defined in xydreg.h
 * In short, it has the fields:
 *        moduleID[64],
 *	  intrInputs,
 *	  csr,
 *	  intrPending,
 *	  intrMask,
 *	  intrClear,
 *	  intrVector,
 *	  outFlags,
 *        portDirection,
 *        port[8]
 */


typedef struct clk_unit {
  struct xyclkdevice *clkregs;       /* VME clock card ctrl regs */
  u_char              noopen;        /* if true, attach failed */
  u_char              isopen;        /* if true, is open already, can't open */
  struct buf	      buf;           /* For physio() calls, should we do any */
  int                 enabled;       /* dev will run when open */
  int                 rate;          /* interrupt interval (msec) */
  int                 debug;         /* debug flag */
  unsigned short      hardcount;     /* last conter reading */
  unsigned long       icount;        /* count */
} CLK;


typedef struct xyd_unit {
  struct xyddevice   *xydregs;       /* VME240 parallel card ctrl regs */
  u_char              isopen;        /* if true, has been opened */
  u_char              noopen;        /* if true, attach failed */
  int                 debug;         /* debug flag */
} PAR;


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


/****************************************************/
/* Macros for sending commands to clock card. 
   Assumes 'clkregs' and 'pack' defined */

#define COMMAND_A(c)	(clkregs->cntrA_com = (c))
#define WRITE_A(d)	(pack.s = (d), 			\
			 clkregs->cntrA_data = pack.c[1], \
			 clkregs->cntrA_data = pack.c[0])
#define READ_A(d)	(pack.c[1] = clkregs->cntrA_data,	\
			 pack.c[0] = clkregs->cntrA_data,	\
			 (d) = pack.s)
#define COMMAND_B(c)	(clkregs->cntrB_com = (c))
#define WRITE_B(d)	(pack.s = (d), 			\
			 clkregs->cntrB_data = pack.c[1], \
			 clkregs->cntrB_data = pack.c[0])
#define READ_B(d)	(pack.c[1] = clkregs->cntrB_data,	\
			 pack.c[0] = clkregs->cntrB_data,	\
			 (d) = pack.s)
#define COMMAND_I(c)	(clkregs->uic_com = (c))
#define WRITE_I(d)	(clkregs->uic_data = (d))
#define READ_I(d)	((d) = clkregs->uic_data)



/****************************************************/

/* static data structures: */

struct dev_info       *pti_devinfo;
static struct pti_reg *ptiregs;
static CLK             clk;
static PAR             par;
static RCIINFO         rinfo;

u_int	              intpri;      /* Highest int pri (spl) */
int	              saved_spl;   /* Saved spl while in critical sections */
static	u_char logpri = 0;
static  u_char npti   = 0;

/* statistics */
u_long pollcount   = 0;
u_long ptierr      = 0;
u_long clkints     = 0;
u_long parints     = 0;
u_long sysfailints = 0;
u_long bogusints   = 0;

int xywallclk_debug =  0;
int max_xyclkintr   = 10;
int num_xyclkintr   =  0;
int xywallclk_retry =  1;
int xywallclk_hit   =  0;

static  struct xyddevice *traceDev   = NULL;
static  int               intInst    = 0;


struct timeval last_xyclkintr;
struct timeval RCI_wallclock();



/********************* procedure definitions **********************/

static int   pti_identify();
static int   pti_attach();
static int   pti_open();
static int   pti_close();
static int   pti_ioctl();
static int   pti_poll();
static void  clk_intr();
static void  par_intr();
static void  clk_setup();
static void  par_setup();
       int   pti_vdcmd();

static void START_CRITICAL();
static void END_CRITICAL();

static void   see_devinfo();
static void   pti_decom();
static struct timeval readWallClock();
static int setTrace();
static int clearTrace();


#define DO_VFAIL 0   /* 0 if we don't want VFAIL ints, VFAIL if we do */

#define RESET_VME() { ptiregs->virqctrl &= (~DO_VFAIL);      \
                      ptiregs->leds &= (~LED1);           \
		      ptiregs->s2vctrl = VSR | BR1;       \
		      while (ptiregs->s2vctrl & VSR);     \
		      ptiregs->leds |= LED1;              \
                      ptiregs->virqctrl |= DO_VFAIL; }



/********************* auto-config support **********************/

struct	dev_ops pti_ops = {
	1,				/* revision number		*/
	pti_identify,			/* confirm device ID		*/
	pti_attach,			/* attach routine of driver	*/
};



/**********************************************/
static int pti_identify(name)
char *name;
{
  PTI_TRACK_MSG(logpri, "Entering pti_identify, name: %s\n", name);

  if (strcmp(name, "PTI,sbs915") == 0) {
    npti++;
    PTI_TRACK_MSG(logpri, "Leaving pti_identify, ACCEPTED (#%d)\n", npti);
    return (1);
  } 

  PTI_TRACK_MSG(logpri, "Leaving pti_identify, (REJECTED)\n");
  return (0);
}



/**********************************************/
static int pti_attach(dinfo)
register struct dev_info *dinfo;
{
  struct dev_reg     *dreg;
  struct dev_intr    *dintr;
  u_int	              i;
  caddr_t             addr;

  PTI_TRACK_MSG(logpri, "Entering pti_attach: dinfo: 0x%x\n", dinfo);
  if (dinfo == NULL) {
    PTI_ERROR_MSG(logpri, "pti_attach: dinfo is NULL!\n");
    goto attach_failed;
  }

  /* zero out contents of structures */
  bzero((char *) &clk, sizeof(CLK));
  bzero((char *) &par, sizeof(PAR));
  ptiregs = NULL;

  saved_spl = (-1);
  pti_devinfo = dinfo;
  see_devinfo(dinfo);


  /***************************************************************/

  /* Map in PTI registers */

  dreg = dinfo->devi_reg;

  /* dreg may be NULL, but only if devi_nreg is zero. */
  for (i=1; i <= dinfo->devi_nreg; ++i, ++dreg) {
    if (dreg == NULL) {
      PTI_ERROR_MSG(logpri, "pti_attach: dreg is NULL!\n");
      goto attach_failed;
      /*NOTREACHED*/
      break;
    }

    switch (i) {
    case 1:				/* first register (Sbus->VME regs) */
      PTI_TRACK_MSG(logpri, "pti_attach: reg_addr = 0x%lx, size = 0x%x\n",
		    dreg->reg_addr, dreg->reg_size);

      /* kludge:  dreg->reg_size (on the PTI-SBS915) is off by 1 */
      if (sizeof (struct pti_reg) != (dreg->reg_size+1)) {
	PTI_ERROR_MSG(logpri, "Struct (%d bytes) != reg (%d bytes)!\n",
			sizeof (struct pti_reg), dreg->reg_size);
	goto attach_failed;
      }

      /* Map the structure into kernel virtual space. */
      ptiregs = (struct pti_reg *) map_regs(dreg->reg_addr, 
			     sizeof(struct pti_reg), dreg->reg_bustype);

      if (ptiregs == NULL) {
	PTI_ERROR_MSG(logpri, "pti_attach: map_regs (PTI) failed!\n");
	goto attach_failed;
      }
      break;


    default:
      PTI_ERROR_MSG(logpri, "pti_attach: too many registers (%d)\n", i);
      goto attach_failed;

      /*NOTREACHED*/
      break;
    }
  }


  
  /******************************/
  /* attempt to map in CLK regs */
  /******************************/

  dreg = dinfo->devi_reg;   /* point back to the PTI info... */

  /* Map the structure into kernel virtual space. */
  addr = ((caddr_t) dreg->reg_addr - PTI_REG_OFFSET) + PTI_MAP1 + ADDR_CLOCK;

  PTI_TRACK_MSG(logpri, "pti_attach: mapping clk regs.  addr=0x%x, size=%d\n",
		addr, sizeof(struct xyclkdevice));

  clk.clkregs = (struct xyclkdevice *)
    map_regs(addr, sizeof (struct xyclkdevice), dreg->reg_bustype);

  PTI_TRACK_MSG(logpri, "pti_attach: clkregs vaddr = 0x%x\n", clk.clkregs);

  if (clk.clkregs == NULL) {
    PTI_ERROR_MSG(logpri, "pti_attach: map_regs (CLK) failed!\n");
    goto attach_failed;
  }


  /******************************/
  /* attempt to map in PAR regs */
  /******************************/

  dreg = dinfo->devi_reg;
  addr = ((caddr_t) dreg->reg_addr - PTI_REG_OFFSET) + PTI_MAP1 + ADDR_VME240;

  PTI_TRACK_MSG(logpri, "pti_attach: mapping par regs.  addr=0x%x, size=%d\n",
		addr, sizeof(struct xyddevice));

  par.xydregs = (struct xyddevice *)
    map_regs(addr, sizeof (struct xyddevice), dreg->reg_bustype);

  PTI_TRACK_MSG(logpri, "pti_attach: parregs vaddr = 0x%x\n", par.xydregs);

  if (par.xydregs == NULL) {
    PTI_ERROR_MSG(logpri, "pti_attach: map_regs (PAR) failed!\n");
    goto attach_failed;
  }


  /**********************************/
  /* install PTI on interrupt chain */
  /**********************************/

  dintr = dinfo->devi_intr;
  for (i=dinfo->devi_nintr;  i>0;  --i, ++dintr) {
    if (dintr == NULL) {  /* A "can't happen" */
      PTI_ERROR_MSG(logpri, "pti_attach: dintr is NULL!\n");
      goto attach_failed;
      /*NOTREACHED*/
      break;
    }

    /* Convert the hardware int. priority level (ipl, 0-15) into an spl */

    if (ipltospl(dintr->int_pri) > intpri) {
      intpri = ipltospl(dintr->int_pri);
      PTI_DEBUG_MSG(logpri, "pti_attach: set intpri to 0x%x\n", intpri);
    }


    /* add device to interrupt chain */

    START_CRITICAL();
    addintr(dintr->int_pri, pti_poll, dinfo->devi_name, dinfo->devi_unit);
    intInst = 1;
    END_CRITICAL();

    PTI_DEBUG_MSG(logpri, "Installed pti_poll at SBus priority 0x%x\n",
		  dintr->int_pri);
  }

  report_dev(dinfo);



  /* everything's mapped and ready to go.  initialize the cards */

  /***********************/
  /* set up the PTI card */
  /***********************/

  ptiregs->s2vctrl = BR1;
  ptiregs->leds = 0x00;

  /* set up memory maps */
  PTI_SET_AMAP(0x00000000, VME16, ptiregs->map1a, ptiregs->map1b);
  PTI_SET_AMAP(0x00800000, VME24, ptiregs->map2a, ptiregs->map2b);
  PTI_SET_AMAP(0x00c00000, VME24, ptiregs->map3a, ptiregs->map3b);
  PTI_SET_AMAP(0x80000000, VME32, ptiregs->map4a, ptiregs->map4b);
  PTI_SET_AMAP(0x80400000, VME32, ptiregs->map5a, ptiregs->map5b);
  PTI_SET_AMAP(0x80800000, VME32, ptiregs->map6a, ptiregs->map6b);
  PTI_SET_AMAP(0x80c00000, VME32, ptiregs->map7a, ptiregs->map7b);

  /* set up interrupt stuff */
  ptiregs->virqctrl = VIRQ4 | VIRQ6;
  ptiregs->sbusirq = (pti_devinfo->devi_intr->int_pri) & 0x07;

  ptiregs->v2sctrl = 0x00;
  ptiregs->leds = LED0;
  RESET_VME();


  /***********************/
  /* set up the CLK card */
  /***********************/
  {
    struct xyclkdevice *clkregs = clk.clkregs;
    union pack          pack;

    clk.enabled = 1;
    clk.rate = DEFAULT_RATE;

    if (peekc(&(clkregs->cntrA_data)) == -1) {
      printf("pti_attach: no CLK device present at VME 0x%x\n", ADDR_CLOCK);
      clk.noopen = 1;
    }

    else {
      COMMAND_A (RESET_CNTRS);
      COMMAND_A (LOAD & 0x1f);
      COMMAND_B (RESET_CNTRS);
      COMMAND_B (LOAD & 0x1f);
      COMMAND_I (RESET_ICTRL);
    }
  }

  RCI_setwallclock(readWallClock);


  /***********************/
  /* set up the PAR card */
  /***********************/
  {
    int i;
    char *idstr = "VMEIDXYC240";

    /* make sure the thing is a VME240 */
    for (i=0; i<strlen(idstr); i++) {
      if (peekc(&(par.xydregs->moduleID[2*i+1])) != idstr[i]) break;
    }

    if (i != strlen(idstr)) {
      printf("pti_attach: no VME240 device found at VME 0x%x\n", ADDR_VME240);
      par.noopen = 1;
    }

    else {   /* it's okay, set it up */
      RCI_cleari(&rinfo);
      traceDev = par.xydregs;
      par_setup();
      RCI_setTraceFxns (setTrace, clearTrace);
      par.debug = 0;
    }
  }
    
  PTI_TRACK_MSG(logpri, "ATTACH SUCCEEDED.\n");
  return (0);

 attach_failed:
  PTI_TRACK_MSG(logpri, "ATTACH FAILED.\n");
  clk.noopen = 1;	/* prevent open */
  par.noopen = 1;	/* prevent open */
  return (-1);
}



/***********************************/
/*       DRIVER ENTRY POINTS       */
/***********************************/


/******************************************************/
static int pti_open(dev, flags)
dev_t	dev;
int	flags;
{
  u_char  unit;
  u_short retval = 0;	        /* return value */

  unit = minor(dev);

  PTI_TRACK_MSG(logpri,	"pti%d: Entering pti_open, flags %d.\n", unit, flags);

  if (unit == PTI_MINOR) {   /* opening PTI device */
    /* always succeeds */
  }

  else if (unit == CLK_MINOR) {   /* opening CLK device */
    union   pack pack;
    struct  xyclkdevice *clkregs = clk.clkregs;

    if (clk.noopen) {
      PTI_DEBUG_MSG(logpri, "pti_open: clk open inhibited\n");
      retval = ENXIO;
      goto out;
    }


    if ( !(clk.isopen) && clk.enabled) { 
      /* set up the device, and enable interrupts */

      START_CRITICAL();

      clk.enabled = 1;
      clk.rate = DEFAULT_RATE;

      /* initialize the clock hardware (do a reset) */
      COMMAND_A (RESET_CNTRS);
      COMMAND_A (LOAD & 0x1f);
      COMMAND_B (RESET_CNTRS);
      COMMAND_B (LOAD & 0x1f);
      COMMAND_I (RESET_ICTRL);
      RCI_setwallclock(readWallClock);

      RCI_use_hardclock(0);
      clk.isopen = 1;
      clk.icount = 0;
      clk.hardcount = 0;
      num_xyclkintr = 0;
      clk.debug = 0;
      clk_setup();
      COMMAND_I (SELR_ARM);
    }
    clk.isopen = 1;

    END_CRITICAL();
  }

  else if (unit == PAR_MINOR) {   /* opening PAR device */
    int s;
    struct xyddevice *xydregs = par.xydregs;

    if (par.noopen || par.isopen) {
      PTI_DEBUG_MSG(logpri, "pti_open: par open (noopen=%d, isopen=%d)\n",
		    par.noopen, par.isopen);
      retval = ENXIO;
      goto out;
    }

    RCI_init();

    if (!RCI_alive()) {
      printf ("RCI not alive\n");
      return(ENXIO);
    }

    s = splhigh();
    if (rinfo.flags & RCI_OPEN) { splx(s); return(EBUSY); }
    if (!RCI_openi(&rinfo)) { splx(s); return(ENFILE); }

    splx(s);
    par.isopen = 1;

    xydregs->csr |= XYD_RED_OFF;    /* turn red off */
    xydregs->csr |= XYD_GREEN_ON;   /* turn green on */
  }

  else {
    PTI_ERROR_MSG(logpri, "pti_open:  unrecognized minor device #%d\n",unit);
  }
  
    
out:
  PTI_TRACK_MSG(logpri, "pti_open:  returns %d.  unit=%d\n", retval, unit);
  return (retval);
}




/**********************************************/
static int pti_close(dev, flags)
dev_t dev;
int   flags;
{
  u_char  unit;
  u_short retval = 0;

  unit = minor(dev);
  PTI_TRACK_MSG(logpri,	"pti_close: unit %d.\n", unit);

  if (unit == PTI_MINOR) {
    /* always succeeds */
  }

  else if (unit == CLK_MINOR) {
    struct xyclkdevice *clkregs = clk.clkregs;
    union  pack pack;

    if (clk.isopen) {
      clk.isopen = 0;
      COMMAND_I (SELR_DISARM);
      RCI_use_hardclock(1);

#ifdef RESET_ON_CLOSE      
      if (par.isopen == 0) {
	printf("Resetting VME bus.  (clk close)\n");
	RESET_VME();
      }
#endif
    }
  }

  else if (unit == PAR_MINOR) {
    struct xyddevice *xydregs = par.xydregs;

    xydregs->csr &= ~XYD_RED_OFF;    /* turn red on */
    xydregs->csr &= ~XYD_GREEN_ON;   /* turn green off */

    if (par.isopen) {
      RCI_closei(&rinfo);
      par.isopen = 0;

#ifdef RESET_ON_CLOSE      
      if (clk.isopen == 0) {
	printf("Resetting VME bus.  (par close)\n");
	RESET_VME();
      }
#endif
    }
  }

  else {
    PTI_ERROR_MSG(logpri, "pti_close:  unrecognized minor device #%d\n",unit);
    retval = ENXIO;
  }

  PTI_TRACK_MSG(logpri,	"pti_close: returns %d.  unit=%d\n", retval, unit);
  return (retval);
}



/**********************************************/
static int pti_ioctl(dev, cmd, data, flag)
dev_t	dev;
int	cmd;
caddr_t	data;
int	flag;
{
  u_char  unit, i;
  u_short retval = 0;

  unit = minor(dev);
  PTI_TRACK_MSG(logpri, "pti_ioctl: unit=%d, cmd=%d\n", unit, cmd);

  if (unit == PTI_MINOR) {
    if (cmd == 0) {
      printf("Resetting VME bus (PTI ioctl 0)\n");
      RESET_VME();
    }
    else {
      PTI_DEBUG_MSG(logpri, "pti_ioctl: unit=%d (unknown ioctl %d)\n", 
		    unit,cmd);
    }
  }

  else if (unit == CLK_MINOR) {
    union   pack pack;
    struct  xyclkdevice *clkregs = clk.clkregs;

    if (!clk.isopen) {
      PTI_DEBUG_MSG(logpri, "pti_ioctl:  Error! (clk not open)\n");
      return(ENOTTY);
    }
  
    switch (cmd) {
    case RCIIOC_GETCLKRATE:  *(int*)data = clk.rate;     break;
    case RCIIOC_CLKSTATUS:   *(int*)data = clk.enabled;  break;

    case RCIIOC_SETCLKRATE:
      {	int interval = *(int*)data;

	if (interval <= 0 || (interval*500) > 0x10000) return(EINVAL);
	clk.rate = interval;
	COMMAND_A (LOAD_PTR | LOAD_REG | CNTR2);
	WRITE_A (interval*500);
	break;
      }

    case RCIIOC_CLKENABLE:
      if (!clk.enabled) { 
	clk.enabled = 1;
	clk.icount = 0;
	clk.hardcount = 0;
	clk_setup();
	COMMAND_I (SELR_ARM);
      }
      break;

    case RCIIOC_CLKDISABLE:
      clk.enabled = 0;
      COMMAND_I (SELR_DISARM);
      break;

    case RCIIOC_SETCLKDEBUG: clk.debug = *(int*)data;  break;

    default:
      PTI_DEBUG_MSG(logpri, "pti_ioctl: unit=%d (unknown ioctl %d)\n", 
		    unit,cmd);
      return(ENOTTY);
    }
  }


  /**************************************************/

  else if (unit == PAR_MINOR) {
    int s;
    int exclusive = 1;    /* hard-code exclusivosity */
    struct xyddevice *xydregs = par.xydregs;

    if (!par.isopen) {
      PTI_DEBUG_MSG(logpri, "pti_ioctl:  Error! (par not open)\n");
      return(ENOTTY);
    }
  
    switch (cmd) {

    default:
      retval = ENOTTY;
      break;

    case RCIIOC_CONFIRM:
      if (!exclusive) retval = ENOTTY;
      break;

    case RCIIOC_ATTACH:
      if (! exclusive) retval = ENOTTY;
      else {
	RCIINFO *rcii;
	struct RCI_fxn_arg *ap;

	rcii = &rinfo;
	ap = (struct RCI_fxn_arg *) data;
	s = splhigh();
	RCI_setup_switch(rcii);
	rcii->ifxn = ap->function;
	rcii->iarg = ap->argument;

	xydregs->intrClear = 0xff;
	xydregs->csr |= XYD_INTR_ENABLE;
	xydregs->intrMask = 0x1;
	splx(s);
      }
      break;

    case RCIIOC_EXECUTE:
      if (! exclusive) retval = ENOTTY;
      else {
	RCIINFO *rcii;
	struct RCI_fxn_arg *ap;
	
	rcii = &rinfo;
	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 (! exclusive) retval = 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 (! exclusive) retval = ENOTTY;
	else {
	  kut_callit (RCI_kutexec, (int)data);
	}
	break;
      }

  
    case RCIIOC_DETACH:  /* Detach from the device interrupt */
      if (! exclusive) retval = ENOTTY;
      else {
	s = splhigh();
	rinfo.ifxn = 0;

	xydregs->intrMask = 0;
	xydregs->csr &= ~XYD_INTR_ENABLE;
	xydregs->intrClear = 0xff;
	splx(s);
      }
      break;


    case RCIIOC_REQSIGS:  /* Request signals from the slave task */
      if (! exclusive) retval = ENOTTY;
      else {
	s = splhigh();
	rinfo.flags |= RCI_SIGS;
	splx(s);
      }
      break;

  
    case RCIIOC_HEARTBEAT:  /* Request a heartbeat */
      if (! exclusive) retval = ENOTTY;
      else {
	register RCIINFO *rcii;
	register struct RCI_heartbeat *ap;
	int s;
	int RCI_heartbeat();
	
	rcii = &rinfo;
	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;

  
    case RCIIOC_ONCRASH:  /* Set up crash signal */
      if (! exclusive) retval = ENOTTY;
      else {
	s = splhigh();
	rinfo.crashsig = * (int *) data;
	splx(s);
      }
      break;

  
    case RCIIOC_GETCRASH:  /* Get crash info */
      if (! exclusive) retval = ENOTTY;
      else {
	RCIINFO *rcii;
	rcii = &rinfo;
	if (copyout ((caddr_t)&rcii->lastcrash, 
		     (caddr_t)*(CRASHINFO**)data, 
		     sizeof(CRASHINFO)) == EFAULT) return (EFAULT);
	rcii->lastcrash.type = CI_NULL;
      }
      break;

  
    case RCIIOC_INTERFACE:  /* Get interface information */
      if (! exclusive) retval = ENOTTY;
      else RCI_get_interface((struct RCI_interface *)data);
      break;


    case RCIIOC_TIMEOUT:
      if (! exclusive) retval = ENOTTY;
      else RCI_setup_timeout(&rinfo,(struct RCI_timeout *) data);
      break;
    }
  }

  /**********************************************/

  else {
    PTI_ERROR_MSG(logpri, "pti_ioctl:  unrecognized minor device #%d\n",unit);
  }
  

  PTI_TRACK_MSG(logpri,	"pti_ioctl:  returns %d,  unit=%d\n", retval, unit);
  return (retval);
}






/**********************************************/
static int pti_poll()
{
  u_int	status;
  u_int	int_serviced = 0;
  int irq, ivec;

  pollcount++;

  irq = ptiregs->virqstat;

  if (irq) {  
    /* the PTI board is interrupting, at the very least... */

    if (irq & VIRQ4) {   /* IRQ4 = clock card interrupt */
      ivec = ptiregs->ivec4;
      if (ivec != IVEC_CLOCK) {
	PTI_ERROR_MSG(logpri, "pti_poll: IRQ4 ivec = 0x%x???\n", ivec);
      }

      int_serviced = 1;
      clk_intr();
      ptiregs->ivec4 = 0x00;   /* clear vector register to complete process */
    }


    else if (irq & VIRQ6) {   /* IRQ6 = VME240 card interrupt */
      ivec = ptiregs->ivec6;
      if (ivec != IVEC_VME240) {
	PTI_DEBUG_MSG(logpri, "pti_poll: IRQ6 ivec = 0x%x???\n", ivec);
      }

      int_serviced = 1;
      par_intr();
      ptiregs->ivec6 = 0x00;   /* clear vector register to complete process */
    }

    else if (irq & VFAIL) {   /* SYSFAIL interrupt */
      sysfailints++;
      PTI_ERROR_MSG(logpri, "pti_poll: SYSFAIL int #%ld\n", sysfailints);
      START_CRITICAL();
      RESET_VME();
      END_CRITICAL();
      int_serviced = 1;
    }
  }

  if (!int_serviced) bogusints++;

  return (int_serviced);
}





/**********************************************/
static void clk_intr()
{
  u_char status;
  register struct xyclkdevice	*clkregs;
  union pack pack;
  extern int inside_switch;

  /* when this is called we *know* it was a CLOCK interrupt */

  clkregs = clk.clkregs;
  clkints++;

  if ((clkints/100) & 1) ptiregs->leds &= (~LED2);    /* turn LED2 off */
                    else ptiregs->leds |= ( LED2);    /* turn LED2 on */

  last_xyclkintr = RCI_wallclock();

  START_CRITICAL();

  if (++num_xyclkintr > max_xyclkintr) {
    clkregs->csr = 0x10;	/* disable interrupts */
    printf("clk_intr interrupt saturation\n");
    END_CRITICAL();
    return;
  }

  if (inside_switch) {
    clkregs->csr = 0x10;	/* disable interrupts */
    printf ("clk_intr: inside RCI switch\n");
  }

  if (clk.isopen && clk.enabled) {
    unsigned short count3;	
    int inc;
    int ticks;

    COMMAND_A (LOAD_PTR | HOLD_REG | CNTR3);
    COMMAND_A (SAVE | 0x4);
    READ_A (count3);

    if ((inc = count3 - clk.hardcount) < 0) inc += 0x10000;

    ticks = (inc+1-(clk.hardcount%2))/2;
    clk.hardcount = count3;
    clk.icount += ticks;
    RCI_devclock(ticks);
  }

  else {    /* interrupt while not open or not enabled */
    PTI_ERROR_MSG(logpri,"clk_intr: while isopen=%d, enabled=%d\n",
		  clk.isopen, clk.enabled);
  }
	
  num_xyclkintr--;

  END_CRITICAL();
  
  if (clk.debug && (clk.icount % 200)==0) {
    printf ("clk_intr: icount=%d  ptierr=%d, intcount=%d, pcount=%d\n", 
	    clk.icount, ptierr, clkints, pollcount);
  }

  return;
}



/**********************************************/
static void par_intr()
{
  register RCIINFO              *p;
  struct xyddevice *xydregs = par.xydregs;
  int s;

  /* when this is called we *know* it was a VME240 interrupt */

  s = splhigh();
  parints++;
  xydregs->intrClear = 0xff;

  if ((parints/50) & 1) ptiregs->leds &= (~LED3);    /* turn LED3 off */
                   else ptiregs->leds |= ( LED3);    /* turn LED3 on */

  if (par.debug) printf("|");

  p = &rinfo;
  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);
}






/**************************************************/
static void clk_setup()
{
  register struct xyclkdevice *clkregs;
  union pack pack;

  PTI_TRACK_MSG(logpri, "pti: entering clk_setup\n");

  clkregs = clk.clkregs;

  /* do a general reset on the device */

  PTI_TRACK_MSG(logpri, "clk_setup: resetting clock\n");

  COMMAND_A (RESET_CNTRS);
  COMMAND_A (LOAD & 0x1f);
  COMMAND_B (RESET_CNTRS);
  COMMAND_B (LOAD & 0x1f);
  COMMAND_I (RESET_ICTRL);

  /* Use counter group A. Set up the counters to run
     at the initial default rate, and set them running */

  COMMAND_A (LOAD_PTR | MASTER_REG | CNTRL);
  WRITE_A (BCD_SCALER);

  /* Counter 1 is set to count down, divide by 4 */

  COMMAND_A (LOAD_PTR | MODE_REG | CNTR1);
  WRITE_A (SRC_F1 | REPEAT_CNT | OUT_TOGGLE);
  COMMAND_A (LOAD_PTR | LOAD_REG | CNTR1);
  WRITE_A (4);

  /* Counter 2 is set to count down, divide by rate*500
     (interrupts are generated at half the counting rate) */

  COMMAND_A (LOAD_PTR | MODE_REG | CNTR2);
  WRITE_A (SRC_TCNM1 | REPEAT_CNT | OUT_TOGGLE);
  COMMAND_A (LOAD_PTR | LOAD_REG | CNTR2);
  WRITE_A (clk.rate*500);

  /* Counter 3 counts up, and keeps track of the number of
     interrupts times 2 */

  COMMAND_A (LOAD_PTR | MODE_REG | CNTR3);
  WRITE_A (SRC_TCNM1 | REPEAT_CNT | UP_CNT | OUT_TOGGLE);
  COMMAND_A (LOAD_PTR | LOAD_REG | CNTR3);
  WRITE_A (0);

  COMMAND_A (LOAD_ARM | 0x7);

  /* now set up stuff for the interrupts */

  COMMAND_I (VECTOR | 0x1);
  WRITE_I (IVEC_CLOCK);
  COMMAND_I (MODE | HIGHEDGE_INTR);
  COMMAND_I (SELW_AC);
  WRITE_I (0xff);
  COMMAND_I (CLEAR_MR | 0x1);
  clkregs->csr = VME_IPL_4;	/* enable IPL 4 interrupts */

  PTI_TRACK_MSG(logpri, "pti: leaving clk_setup\n");
}




/**************************************************/
static void par_setup()
{
  register struct xyddevice *xydregs;

  PTI_TRACK_MSG(logpri, "Entering par_setup\n");

  xydregs = par.xydregs;

  xydregs->csr = 0;
  xydregs->csr = XYD_RESET;
  xydregs->csr = XYD_GREEN_ON | XYD_RED_OFF;
  xydregs->portDirection = 0xf2;	/* port directions used by RCI */
  xydregs->intrVector    = IVEC_VME240;
  xydregs->intrClear     = 0xff;

  PTI_TRACK_MSG(logpri, "Leaving par_setup\n");
}






/*<<<<<<<<<<<<<<<<<<  LOADABLE DEVICE DRIVER SUPPORT  >>>>>>>>>>>>>>>>>>*/

extern int nulldev();
extern int seltrue();

struct cdevsw pti_cdevsw = {
	pti_open,	pti_close,	nulldev,	nulldev,
	pti_ioctl,	nulldev,	seltrue,	0,		0
};

struct vdldrv clk_drv = {
	VDMAGIC_DRV,				/* Drv_magic		*/
	"sbs915-based RCI driver",		/* *Drv_name		*/
	&pti_ops,				/* *Drv_dev_ops		*/
	NULL,					/* *Drv_bdevsw		*/
	&pti_cdevsw,				/* *Drv_cdevsw		*/
	0,					/* Drv_blockmajor	*/
	0					/* Drv_charmajor	*/
};


/**********************************************/
int pti_vdcmd(command, vdp, vdi, vds)
     int			command;/* Identify why module called	*/
     struct vddrv		*vdp;	/* Pointer to kernel's structure*/
     struct vdioctl_load	*vdi;	/* pointer to struct vdioctl_*	*/
     struct vdstat		*vds;	/* ID and status information	*/
{
  struct vdconf		*vdconf_p;
  char			*string_p;
  int			status = 0;
  

  PTI_TRACK_MSG(logpri, "-----------------(%d)---------------\n", vdp->vdd_id);
  PTI_TRACK_MSG(logpri,	"Entering pti_vdcmd, vdp 0x%x, vdi 0x%x, vds 0x%x.\n",
		vdp, vdi, vds);

  switch (command) {
  case VDLOAD:
    PTI_TRACK_MSG(logpri, "Command is VDLOAD.\n");
    if (vdi != NULL) {
      PTI_TRACK_MSG(logpri, "vdi_id: %d\n",         vdi->vdi_id);
      PTI_TRACK_MSG(logpri, "vdi_status: %d\n",	    vdi->vdi_status);
      PTI_TRACK_MSG(logpri, "vdi_mmapaddr: 0x%x\n", vdi->vdi_mmapaddr);
      PTI_TRACK_MSG(logpri, "vdi_symtabsize: %d\n", vdi->vdi_symtabsize);
      PTI_TRACK_MSG(logpri, "vdi_userconf: 0x%x\n", vdi->vdi_userconf);

      if (vdi->vdi_userconf != NULL) {
	vdconf_p = vdi->vdi_userconf;
	if (vdconf_p->vdc_type == VDCEND) {
	  PTI_TRACK_MSG(logpri, "No config info available.\n");
	}

	while (vdconf_p->vdc_type != VDCEND) {
	  switch (vdconf_p->vdc_type) {
	  case VDCCONTROLLER: string_p = "(VDCCONTROLLER)"; break;
	  case VDCDEVICE:     string_p = "(VDCDEVICE)";     break;
	  case VDCBLOCKMAJOR: string_p = "(VDCBLOCKMAJOR)"; break;
	  case VDCCHARMAJOR:  string_p = "(VDCCHARMAJOR)";  break;
	  case VDCSYSCALLNUM: string_p = "(VDCSYSCALLNUM)"; break;

	  default: 
	    PTI_DEBUG_MSG(logpri, "vdconf switch error!\n");
	    string_p = "(SWITCH ERROR!)";
	    break;
	  }

	  PTI_TRACK_MSG(logpri, "vdc_type: %d %s vcd_data:  0x%x\n",
			vdconf_p->vdc_type, string_p, vdconf_p->vdc_data);

	  /* Move to next element of array */
	  vdconf_p++;
	}
      } else {
	PTI_DEBUG_MSG(logpri, "No config info, vdi_userconf is NULL\n");
      }

    } else {
      PTI_DEBUG_MSG(logpri, "No config info, vdi is NULL\n");
    }

    vdp->vdd_vdtab = (struct vdlinkage *) &clk_drv;

    PTI_TRACK_MSG(logpri, "Done VDLOAD.\n");
    break;



  case VDUNLOAD:
    PTI_TRACK_MSG(logpri, "Command is VDUNLOAD.\n");

    clk.noopen = 1;
    par.noopen = 1;

    if (clk.isopen || par.isopen) status = EBUSY;

    if (status == 0) pti_decom();
    else {
      clk.noopen = par.noopen = 0;
    }

    PTI_TRACK_MSG(logpri, "Done VDUNLOAD.\n");
    break;



  case VDSTAT:
    PTI_TRACK_MSG(logpri, "Command is VDSTAT.\n");

    /* DEVICE SPECIFIC */

    PTI_TRACK_MSG(logpri, "Done VDSTAT.\n");
    break;



  default:
    PTI_ERROR_MSG(logpri, "clk_vdcmd: unknown command 0x%x\n", command);
    status = EINVAL;
    break;
  }

  PTI_TRACK_MSG(logpri,	"Leaving clk_vdcmd, status (errno): %d.\n", status);
  return (status);
}




/*<<<<<<<<<<<<<<<<<<<<<  PRIVATE SUPPORT ROUTINES  >>>>>>>>>>>>>>>>>>>>>*/

static void START_CRITICAL()
{
  register int	temp_saved_spl;

  PTI_TRACK_MSG(logpri, "Entering START_CRITICAL\n");

  temp_saved_spl = splr(intpri);

#ifdef	PTI_DEBUG
  /* Do assertion checking. This is a critical section in itself. */
  if (saved_spl != (-1)) {
    PTI_DEBUG_MSG(logpri, "START_CRITICAL: ASSERT failed, saved_spl: %d\n",
		  saved_spl);
  }
#endif

  saved_spl = temp_saved_spl;

  PTI_TRACK_MSG(logpri, "Leaving START_CRITICAL\n");
  return;
}



static void END_CRITICAL()
{
  register int	temp_saved_spl;

  PTI_TRACK_MSG(logpri, "Entering END_CRITICAL\n");
  temp_saved_spl = saved_spl;

#ifdef	PTI_DEBUG
  /* Do assertion checking. */
  if (saved_spl == (-1)) {
    PTI_DEBUG_MSG(logpri, "END_CRITICAL: ASSERT failed, saved_spl: %d\n",
		  saved_spl);
  }

  saved_spl = (-1);
#endif


  splx(temp_saved_spl);   /* release critical section */

  PTI_TRACK_MSG(logpri, "Leaving END_CRITICAL\n");
  return;
}





static void see_devinfo(dinfo)
     register struct dev_info *dinfo;
{
  struct dev_reg  *dreg;
  struct dev_intr *dintr;
  u_int		   i;

  PTI_TRACK_MSG(logpri, "Entering see_devinfo: dinfo 0x%x\n", dinfo);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_name:    %s\n",   dinfo->devi_name);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_unit:    %d\n",   dinfo->devi_unit);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_nreg:    %d\n",   dinfo->devi_nreg);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_reg:     0x%x\n", dinfo->devi_reg);

  /* Show all the device registers. */
  dreg = dinfo->devi_reg;

  /* dev_reg_p may be NULL, but only if devi_nreg is zero. */
  for (i = 1; i <= dinfo->devi_nreg; i++, dreg++) {
    if (dreg == NULL) break;

    PTI_DEBUG_MSG(logpri, "REGISTER #%d: dreg->reg_bustype: 0x%x\n",
		  i, dreg->reg_bustype);
    PTI_DEBUG_MSG(logpri, "REGISTER #%d: dreg->reg_addr:    0x%x\n",
		  i, dreg->reg_addr);
    PTI_DEBUG_MSG(logpri, "REGISTER #%d: dreg->reg_size:    0x%x\n",
		  i, dreg->reg_size);
  }

  PTI_DEBUG_MSG(logpri, "dinfo->devi_nintr:   %d\n", dinfo->devi_nintr);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_intr:    0x%x\n", dinfo->devi_intr);

  /* Show all the interrupt data. */
  dintr = dinfo->devi_intr;

  /* dev_intr_p may be NULL, but only if devi_nintr is zero. */
  for (i = 1; i <= dinfo->devi_nintr; i++, dintr++) {
    if (dintr == NULL) break;

    PTI_DEBUG_MSG(logpri, "INTERRUPT #%d: dintr->int_pri:    0x%x\n",
		  i, dintr->int_pri);
    PTI_DEBUG_MSG(logpri, "INTERRUPT #%d: dintr->int_vec:    0x%x\n",
		  i, dintr->int_vec);
  }

  PTI_DEBUG_MSG(logpri, "dinfo->devi_data:    0x%x\n", dinfo->devi_data);
  PTI_DEBUG_MSG(logpri, "dinfo->devi_nodeid:  0x%x\n", dinfo->devi_nodeid);

  PTI_TRACK_MSG(logpri, "Leaving see_devinfo: dinfo 0x%x\n", dinfo);
  return;
}




/*********************************************************/
static void pti_decom()
{
  int		   i;
  struct dev_intr *dintr;

  PTI_TRACK_MSG(logpri, "Entering pti_decom\n");

  if (clk.clkregs) {
    clk.clkregs->csr = 0x10;      /* disable clock card interrupts */
  }

  if (par.xydregs) {
    par.xydregs->intrMask  = 0;   /* disable parallel card interrupts */
    par.xydregs->csr       &= ~XYD_INTR_ENABLE;
    par.xydregs->intrClear = 0xff;
  }

  if (ptiregs) {
    ptiregs->sbusirq = 0x00;  /* disable VME->sbus interrupts, period. */
    ptiregs->leds    = 0;     /* turn off LEDS */
  }

  START_CRITICAL();

  if (clk.clkregs) unmap_regs(clk.clkregs, sizeof (struct xyclkdevice));
  if (par.xydregs) unmap_regs(par.xydregs, sizeof (struct xyddevice));
  if (ptiregs)     unmap_regs(ptiregs,     sizeof (struct pti_reg));


  if (intInst) {   /* remove from interrupt chain (only if installed) */
    dintr = pti_devinfo->devi_intr;
    for (i = pti_devinfo->devi_nintr; i > 0;  --i, ++dintr) {
      if (dintr == NULL) {  /* A "can't happen". */
	PTI_ERROR_MSG(logpri, "pti_decom: ASSERT FAILED\n");
	break;
      }
      remintr(dintr->int_pri, pti_poll);
    }
  }

  END_CRITICAL();

  PTI_TRACK_MSG(logpri, "Leaving pti_decom\n");

  return;
}





/*********************************************************************/




/***************************************/
static struct timeval readWallClock()
{
  register struct xyclkdevice *clkregs;
  union pack pack;
  unsigned short hardcount, count2, count3;
  struct timeval tv;
  int icount, rate, inc, usec_500, extra_usec, s;

  clkregs = clk.clkregs;

  START_CRITICAL();
  hardcount = clk.hardcount;
  rate      = clk.rate;
  icount    = clk.icount;
  COMMAND_A (SAVE | 0x6);
  COMMAND_A (LOAD_PTR | HOLD_REG | CNTR2);
  READ_A (count2);

  /* If counter2 equals rate*500, it may have just wrapped
     and we should resave counter 3 */

  if (count2 == rate*500) {
    xywallclk_hit++;
    if (xywallclk_retry) { COMMAND_A (SAVE | 0x4); }
  }
  COMMAND_A (LOAD_PTR | HOLD_REG | CNTR3);
  READ_A (count3);

  END_CRITICAL();

  extra_usec = rate*500 - count2;
  if ((inc = count3 - hardcount) < 0) inc += 0x10000;
  /* total time, in units if 500 usec, is
     2 rate icount + rate inc + extra_usec / 500
     with remainder of extra_usec % 500. This time is
     then converted into seconds and useconds. */

  usec_500 = 2*rate*icount + rate*inc + extra_usec/500;
  tv.tv_sec = usec_500/2000;
  tv.tv_usec = extra_usec%500 + (usec_500%2000)*500;

  if (xywallclk_debug) {
    printf ("count2=%d count3=%d rate=%d icount=%d hardcount=%d\n",
	    count2, count3, rate, icount, hardcount);
    printf ("inc=%d extra_usec=%d usec_500=%d\n",
	    inc, extra_usec, usec_500);
    printf ("tv = %d + %d\n", tv.tv_sec, tv.tv_usec);
  }


  return (tv);	
}


/**********************************************************/
/*            RCI diagnostic trace routines               */
/**********************************************************/


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


