/* Driver for the DRV11 dressed up as a clock to serve as a real time
   clock for RCI */

#include "drvclk.h"
#if NDRVCLK > 0

#include "../h/param.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/errno.h"
#include "../rciext/crashinfo.h"
#include "../rciext/kacomm.h"
#include "../rciext/rci.h"
#include "../rciext/rciioctl.h"

#include "../machine/pte.h"
#include "map.h"
#include "buf.h"
#include "../vaxuba/ubareg.h"
#include "../vaxuba/ubavar.h"

#include "../rciext/drvclkreg.h"
#include "../rciext/kareg.h"

struct drvclk_softc {
	int open;		/* device is open */
	int enabled;		/* device will run when open */
	int rate;		/* interrupt interval (msec) */
	int debug;		/* debug flag */
	unsigned short hardcount; /* last counter reading */
	unsigned long icount;	/* interrupt count */
} drvclk_softc[NDRVCLK];

#define DEFAULT_RATE 	10

int     drvclkprobe(), drvclkattach(), drvclkintr(), drvclk_timeout();
struct  uba_device *drvclkdinfo[NDRVCLK];
u_short drvclkstd[] = { 0 };
struct  uba_driver drvclkdriver = 
    { drvclkprobe, 0, drvclkattach, 0, 
      drvclkstd, "drvclk", drvclkdinfo };

static deviceopen = 0;
extern int RCIclockDevice; 

drvclkprobe(reg, ui)
caddr_t reg;
struct uba_device *ui;
{
        register int br, cvec;
        register int i;

	if (ui->ui_flags == 0)
	 { printf("Missing vector configured on drvclk%d\n",ui->ui_unit);
	   return(0);
	 }
        br = 0x17;			/* Set IPL of the device */
	cvec = ui->ui_flags;

	if (RCIclockDevice)
	 { printf ("warning: RCI clock device already attached\n");
	 }
	else
	 { printf ("RCI clock device attached\n");
	 }
	RCIclockDevice = 1;
	return (sizeof (struct drvclkdevice));	
}

drvclkattach(ui)
struct uba_device *ui;
{
	struct drvclkdevice *drvclk;
	struct drvclk_softc *drvsoft;

	drvclk = (struct drvclkdevice*)(ui->ui_addr);
	drvsoft = &drvclk_softc[ui->ui_unit];

	/* initialize the soft control structure */

	drvsoft->open = 0;
	drvsoft->enabled = 1;
	drvsoft->rate = DEFAULT_RATE;
	drvsoft->debug = 0;
	drvsoft->icount = 0;
	drvsoft->hardcount = 0;

	drvclk->obuf = DRVCLK_CLEAR;

	return(0);
}

drvclkopen(dev,flag)
dev_t dev;
int flag;
{
	int unit = minor(dev);
	struct drvclk_softc *drvsoft = &drvclk_softc[unit];
	struct drvclkdevice *drvclk;
	struct uba_device *ui = drvclkdinfo[unit];

	if (unit < 0 || unit >= NDRVCLK || !ui || !ui->ui_alive)
	 { return (ENXIO);
	 }
	if (!drvsoft->open && drvsoft->enabled)
	 { 
	   /* enable interrupts and the device */

	   drvsoft->icount = 0;
	   drvsoft->hardcount = 0;

	   drvclk = (struct drvclkdevice *)drvclkdinfo[unit]->ui_addr;
	   drvclk->obuf = DRVCLK_CLEAR;
	   drvclk->csr |= DRVCLK_INTEN;
	   drvclk->obuf = DRVCLK_ENABLE;
	 }
	drvsoft->open = 1;
	return (0);
}

drvclkclose(dev,flag)
dev_t dev;
int flag;
{
	int unit = minor(dev);
	struct drvclk_softc *drvsoft = &drvclk_softc[unit];
	struct drvclkdevice *drvclk;
	struct uba_device *ui = drvclkdinfo[unit];

	if (unit >= NDRVCLK || !ui || ui->ui_alive == 0)
	 { return (ENXIO);
	 }
	drvclk = (struct drvclkdevice *)drvclkdinfo[unit]->ui_addr;
	drvclk->obuf = DRVCLK_CLEAR;
	drvsoft->open = 0;
	return (0);
}

drvclkioctl(dev,cmd,data,flag)
dev_t dev;
int cmd;
caddr_t data;
int flag;
{
	register struct drvclkdevice *drvclk;
	struct drvclk_softc *drvsoft;
	struct uba_device *ui;
	int unit;

	unit = minor(dev);
	ui = drvclkdinfo[unit];
	if (unit < 0 || unit >= NDRVCLK || !ui || !ui->ui_alive)
	 { return (ENXIO);
	 }
	drvsoft = &drvclk_softc[unit];
	drvclk = (struct drvclkdevice *)ui->ui_addr;

	switch (cmd)
	 { default:
	    { return (ENOTTY);
	      break;
	    }
	   case RCIIOC_GETCLKRATE:
	    { *(int*)data = drvsoft->rate;
	      break;
	    }
	   case RCIIOC_CLKSTATUS:
	    { *(int*)data = drvsoft->enabled;
	      break;
	    }
	   case RCIIOC_SETCLKRATE:
	    { return (ENOTTY);
	      break;
	    }
	   case RCIIOC_CLKENABLE:
	    { drvsoft->enabled = 1;
	      RCIclockDevice = 1;
	      break;
	    }
	   case RCIIOC_CLKDISABLE:
	    { drvsoft->enabled = 0;
	      RCIclockDevice = 0;
	      break;
	    }
	   case RCIIOC_SETCLKDEBUG:
	    { drvsoft->debug = *(int*)data;
	      break;
	    }
	 }
	return (0);
}

drvclkintr(unit)
int unit;
{
	register struct drvclk_softc *drvsoft = &drvclk_softc[unit];
	struct drvclkdevice *drvclk;
	int count;
	int s;

	extern struct kabbblock *kabboards[];
	extern int ka_rci_clocks[];
	extern int ka_wanttick[];

	s = splhigh();

	if  (drvsoft->open && drvsoft->enabled)
	 {
	   drvclk = (struct drvclkdevice *)drvclkdinfo[unit]->ui_addr;

	   drvclk->obuf |= 0x4;	/* for debugging */
	   count = (drvclk->ibuf & DRVCLK_COUNTMASK) - drvsoft->hardcount;
	   if (count > 0)
	    { drvsoft->icount += count;
	    }
	   else 
	    { drvsoft->icount += DRVCLK_COUNTMASK + count + 1;
	    }
	   drvsoft->hardcount = (drvclk->ibuf & DRVCLK_COUNTMASK);

	   /* create an RCI clock tick on the arbiter CPU */

	   kabboards[0]->common.time_rt[0] = drvsoft->icount;
	   ka_rci_clocks[0] = drvsoft->icount;
	   ka_wanttick[0] = 1;
	   
	   ka_rttick();
	   drvclk->obuf &= ~0x4;	/* for debugging */
	 }
	if (drvsoft->debug && ((drvsoft->icount % drvsoft->debug) == 0))
	 { printf ("drvclk: %d\n", drvsoft->icount);
	 }

	splx(s);
}

#endif
