From: els@icf.hrb.com (Eric L. Schott)
Newsgroups: comp.os.vxworks
Subject: Re: MVME167 usec timer?
Date: 12 Oct 92 12:04:02 EST
Organization: HRB Systems, Inc.

In article <9210072046.AA01692@coyote.draper.com>, lacy@draper.com (Carl Lacy) writes:
> 
>    I am in need of accurate timing on a MVME167.  The TOD clock, a MK48TO8,
> has a quanta of only 1 sec.  The MVME manual notes a "tick timer 1 and 2 
> counter" on the VMEchip2, and a "TIC timer 1 and 2 counter" on the PCCchip2, 
> so I was wondering if anyone might have packaged routines for managing these?

Below is a package which should meet your needs.  This uses
unix standard gettimeofday, settimeofday, and adjtime.  Accurate
to 1 microseconds.  Calls to gettimeofday without a timezone
take about 5.5 microseconds.  This package works with the NTP
packages available for VxWorks (see archives).  
-- 

Eric L. Schott,    HRB Systems, Inc.    814/238-4311     els@icf.hrb.com
  "As we acquire more knowledge, things do not become more comprehensible
   but more mysterious."                  Albert Schweitzer, "Paris Notes"

---------------------------------------------------------------------------
/*
 *      (U) The Timer_Library provides gettimeofday and
 *      settimeofday functionality for the MVME167 card.  This library
 *      has been extended to support NTP.  Therefore, this 167 can be
 *      closely synchronized with other CPUs on the network.  See the
 *      VxWorks NTP documentation for further details.
 *
 *      (U) The functions within the Timer_Library software are
 *      called as needed based on their prototypes as found in
 *      Prototypes.h.
 *
 *	(U) The TOD_Init routine MUST be called AFTER the clock
 *	has been set or reset (via sysClkRateSet).
 *
 */

#include <vxWorks.h>
#include <sysLib.h>
#include <intLib.h>
#include <68k/iv.h>
#include <stdioLib.h>
#include <string.h>
#include <limits.h>
#include <wdLib.h>

#define J68KTIM_USEC_PER_SEC 1000000
/*
 *.		(U) Number of microseconds per second
 *.
 *.
 */
struct timeval {
	long	tv_sec;		/* seconds */
	long	tv_usec;	/* and microseconds */
};
struct timezone {
	int	tz_minuteswest;	/* minutes west of Greenwich */
	int	tz_dsttime;	/* type of dst correction */
};
#define	DST_NONE	0	/* not on dst */
#define	DST_USA		1	/* USA style dst */
#define	DST_AUST	2	/* Australian style dst */
#define	DST_WET		3	/* Western European dst */
#define	DST_MET		4	/* Middle European dst */
#define	DST_EET		5	/* Eastern European dst */
#define	DST_CAN		6	/* Canada */
#define	DST_GB		7	/* Great Britain and Eire */
#define	DST_RUM		8	/* Rumania */
#define	DST_TUR		9	/* Turkey */
#define	DST_AUSTALT	10	/* Australian style with shift in 1986 */
#define	ITIMER_REAL	0
#define	ITIMER_VIRTUAL	1
#define	ITIMER_PROF	2
struct	itimerval {
	struct	timeval it_interval;	/* timer interval */
	struct	timeval it_value;	/* current value */
};


/*
 *	Local Commons and Data Items
 */
#define VMECHIP2_BASE_ADRS 0xfff40000
#include <drv/vmechip2.h>
/*
 *.             (U) Define the VMEchip2 registers
 */

#define TIMEOUTCR_PA_MASK 0xff
/*
 *.             (U) Mask for Prescaler Ajdust in Prescaler Control
 *.             Register of VMEchip2.  
 */

#define TIMEOUTCR_PA_25_MHZ 0xe7
/*
 *.             (U) Prescaler Ajdust for our 68040 speed.
 */

#define	TIMERCR_TT1_OVF_SHIFT (4)
/*
 *.             (U) Tick Timer Control Register overflow counter.
 */

#define	TIMERCR_TT1_OVF (0xf << TIMERCR_TT1_OVF_SHIFT)
/*
 *.             (U) Tick Timer Control Register overflow counter.
 */

#define IOCR_VBR0_SHIFT (28 - 4)
/*
 *.             (U) Mask for VMEchip2 interrupt vector selection.
 */

#define IOCR_VBR1_SHIFT (24 - 4)
/*
 *.             (U) Mask for VMEchip2 interrupt vector selection.
 */

#define IOCR_VBR0_MASK 0xf0000000
/*
 *.             (U) Mask for VMEchip2 interrupt vector selection.
 */

#define IOCR_VBR1_MASK 0x0f000000
/*
 *.             (U) Mask for VMEchip2 interrupt vector selection.
 */

#define ADJTIME_MAX	((INT_MAX / J68KTIM_USEC_PER_SEC) - 1)
/*
 *.             (U) Maximum time to adjust clock.
 */

unsigned int hz;
/*
 *.             (U) sysclock interrupt rate per seconds.  This is
 *.             global for NTP.
 *.
 *.             Units:           interrupts per seconds
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         TRUE or FALSE
 */

unsigned int tick;
/*
 *.             (U) Number of microseconds in clock tick.  This is
 *.             global for NTP.
 *.
 *.             Units:           microseconds
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         N/A
 */

volatile int tickadj = 500;
/*
 *.             (U) Number of microseconds to adjust time of day clock
 *.             every adjustment interval (one second).  This is
 *.             global for NTP.
 *.
 *.             Units:           microseconds
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           1 - 250,000 (1/4 s)
 *.             Default:         500
 */

LOCAL int adj_time = 0;
/*
 *.             (U) How much time to adjust timeofday clock
 *.
 *.             Units:           microseconds
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           +/- ADJTIME_MAX
 *.             Default:         Zero
 */

LOCAL int timerInitCalled=FALSE;
/*
 *.             (U) Indicates if timer initialized.
 *.
 *.             Units:           Boolean
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         TRUE or FALSE
 */

LOCAL long currentSecond=0;
/*
 *.             (U) This is the current second past Epoch.  However,
 *.             the concept of Epoch is muddied here since we have no
 *.             guarantee settimeofday has been called to set the
 *.             seconds based upon Jan 1, 1970.  Most likey, Epoch is
 *.             time since the board last booted. 
 *.
 *.             Units:           seconds
 *.             Accuracy:        one second
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         N/A
 */

LOCAL struct timezone currentZone = {5*60,DST_USA};
/*
 *.             (U) Current time zone.  Must be set by settimeofday
 *.             for the value to be valid.
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         Eastern timezone, USA DST
 */

LOCAL WDOG_ID adjust_wd_timer;
/*
 *.             (U) When doing an adjustment of the time of day clock,
 *.             it is simplest if the adjustment is made in the middle
 *.             of a second.  This means if we either add or subtract
 *.             from the microseconds in a second, we do not have to
 *.             worry about the time rolling over into another second.
 *.             We use this timer to create an interrupt at near the
 *.             one-half second interval.
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         N/A
 */

LOCAL unsigned int adjust_wd_ticks;
/*
 *.             (U) Number of ticks to wait to call adjustment routine.
 *.
 *.             Units:           VxWorks ticks
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             Default:         N/A
 */


/********************* Adjust_Clock ***********************
 *
 *      (U) Adjust_Clock adjusts the time of day clock.
 *
 *      (U) Adjust_Clock adjusts the time of day clock.  This
 *	routine should be called at the 1/2 second interval if there
 *	is an adjustment to be made.  It adds/subtracts the adjustment
 *	interval from the VMECHIP2 timer.
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
LOCAL void  Adjust_Clock 
  ( 
   int parameter
/*
 *.             (U) Unused VxWorks parameter.
 *.
 *.             Type:            Input
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */
  )
{
   int cur_adj;
/*
 *.             (U) Amount to adjust clock in current second.
 *.
 *.             Units:           Microseconds
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           0 -> +/-  tickadj
 *.             
 */

  volatile int *ireg;
/*
 *.             (U)  points to hardware register
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */


   cur_adj = max(min(adj_time,tickadj),-tickadj); /* Calc adjustment */
   if (cur_adj != 0)			 /* if adjustment required */
     {
     ireg = VMECHIP2_TTCOUNT1;
     *ireg += cur_adj;
     adj_time -=  cur_adj;
     }
   return;
}

/********************* TOD_Isr ***********************
 *
 *      (U) TOD_Isr is the Time of Day interrupt service routine.
 *
 *      (U) TOD_Isr resets the VMEchip2 microseconds timer and
 *      maintains the count of seconds past the "Epoch."
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
LOCAL void TOD_Isr
  ( 
   int param
/*
 *.             (U) Unused intConnect parameter
 *.
 *.             Type:            Input
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */
  )
{
  volatile int *ireg;
/*
 *.             (U)  points to hardware register
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */


  ireg = VMECHIP2_ICLR;			 /* Interrupt clear register */
  *ireg |= ICLR_CTIC1;			 /* Turn off Timer 1 interrupt */
  ireg = VMECHIP2_TIMERCR;
  currentSecond += (*ireg & TIMERCR_TT1_OVF) >> TIMERCR_TT1_OVF_SHIFT;
					 /* Increment current second */
  *ireg |= TIMERCR_TT1_COVF;		 /* Clear overflow bit */
  if (adj_time != 0)
    wdStart (adjust_wd_timer,adjust_wd_ticks,Adjust_Clock,0);
					 /* Ajdust clock if required */
  return;				 /* Isr done */
}



/********************* TOD_Init ***********************
 *
 *      (U) TOD_Init initializes the Time of Day hardware.
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
int TOD_Init
  ( 
/*
 *.             (U) None
 *.
 */
  )
{
  volatile int *ireg;
/*
 *.             (U) points to hardware registers
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  int lockKey;
/*
 *.             (U) Interrupt lock key
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  hz = sysClkRateGet();			 /* Get current clock rate */
  tick = J68KTIM_USEC_PER_SEC / hz;	 /* uS per clock interrupt */
  lockKey = intLock();
  adjust_wd_ticks = hz/2;		 /* 1/2 second */
  if (!timerInitCalled)
    {
    adjust_wd_timer = wdCreate ();	 /* Get timer */
    ireg   = VMECHIP2_IOCR;
    intConnect
      ((void *) INUM_TO_IVEC(((*ireg & IOCR_VBR0_MASK) >> 
			      IOCR_VBR0_SHIFT) + LBIV_TT1),
					 /*   Vector from chip */
       TOD_Isr,			 /*   ISR (above) */
       0);				 /*   ISR parameter (unused) */

    ireg = VMECHIP2_TIMEOUTCR;
    *ireg = (*ireg & ~TIMEOUTCR_PA_MASK) |
      TIMEOUTCR_PA_25_MHZ;			 /* set prescaler adjust */
    ireg = VMECHIP2_TTCOMP1;
    *ireg = J68KTIM_USEC_PER_SEC;	 /* Interrupt at second bound */
    ireg = VMECHIP2_TTCOUNT1;
    *ireg = 0;				 /* Zero counter */
    ireg   = VMECHIP2_IOCR;
    *ireg |= IOCR_MEIN;			 /* Enable VMECHIP2 interrupts */
    ireg   = VMECHIP2_ILR1;
    *ireg |= ILR1_TICK1_LEVEL1;		 /* Timer Int Lvl 1 */
    ireg   = VMECHIP2_ICLR;
    *ireg |= ICLR_CTIC1;		 /* Clear pending timer interrupt */
    ireg   = VMECHIP2_LBIER;
    *ireg |= LBIER_ETIC1;		 /* Enable timer interrupt */
    ireg = VMECHIP2_TIMERCR;
    *ireg |= (
	      TIMERCR_TT1_EN |		 /* Enable timer */
	      TIMERCR_TT1_COC |		 /* Reset counter when 1sec */
	      TIMERCR_TT1_COVF );	 /* Clear overflow */
    timerInitCalled = TRUE;		 /* Flag timer init called */

    }
  intUnlock(lockKey);

  return(OK);
}


/********************* gettimeofday ***********************
 *
 *      (U) gettimeofday returns the current time of day.
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
int gettimeofday
  ( 
   struct timeval *tp,
/*
 *.             (U) pointer to timeval structure
 *.
 *.             Type:            Input
 *.             Units:           seconds and microseconds
 *.             Accuracy:        microseconds!
 *.             Precision:       N/A
 *.             Range:           valid address or NULL
 *.             
 */

   struct timezone *tzp
/*
 *.             (U) Pointer to timezone structure
 *.
 *.             Type:            Input
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           valid address or NULL
 *.             
 */

  )
{
  volatile int *ireg;
/*
 *.             (U) points to hardware registers
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  int lockKey;
/*
 *.             (U) Interrupt lock key
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  int status=ERROR;
/*
 *.             (U) Return status; assume not ok
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  if (tp != NULL)
    {
    /* 
     * The following loop ensures we correctly read the time.  There
     * is a race condition where between when we read the fractional
     * seconds from the chip and the software maintained seconds from
     * memory that the second rolls over and the time reported would
     * be one second too large.  The following code handles that
     * condition by disabling interrupts and reading the fractional
     * and whole second as a "pair."  If the timer reports it rolled
     * over to a new second, then repeat the read.
     */
    while (status != OK)
      {
      lockKey = intLock();		 /* Keep ISR from increment sec */
      tp->tv_sec = currentSecond;	 /* S/W maintained sec */
      ireg = VMECHIP2_TTCOUNT1;
      tp->tv_usec = *ireg;		 /* read us */
      ireg = VMECHIP2_TIMERCR;
      if ((*ireg & TIMERCR_TT1_OVF) == 0) /* Chip overflow? */
	status = OK;			 /*   No, time ok */
      intUnlock(lockKey);		 /* Let ISR run */
      }
    }
  /*
   * Read the timezone information.  There is a slight race condition
   * in that a high priority task could interrupt this process in the
   * middle of the copy of the timezone information and change the
   * timezone information.  We will ignore this reace condition.
   */
  if (tzp != NULL)
    {
    lockKey = intLock();		 /* Keep other task away */
    *tzp = currentZone;			 /* Copy timezone info */
    intUnlock(lockKey);			 /* Done */
    }

  return(OK);
}
 

/********************* settimeofday ***********************
 *
 *      (U) settimeofday sets a new time value and/or time zone.
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
int settimeofday
  ( 
   struct timeval *tp,
/*
 *.             (U) Pointer to timeval structure.
 *.
 *.             Type:            Input
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           valid memory address or NULL
 *.             
 */

   struct timezone *tzp
/*
 *.             (U) Pointer to timezone structure
 *.
 *.             Type:            Input
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           valid memory address or NULL
 *.             
 */

  )
{
  volatile int *ireg;
/*
 *.             (U) points to hardware registers
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */

  int lockKey;
/*
 *.             (U) Interrupt lock key
 *.
 *.             Units:           N/A
 *.             Accuracy:        N/A
 *.             Precision:       N/A
 *.             Range:           N/A
 *.             
 */


  lockKey = intLock();
  if (tp != NULL)
    {
    currentSecond = tp->tv_sec;		 /* set second */
    ireg = VMECHIP2_TTCOUNT1;
    *ireg = tp->tv_usec;		 /* Set counter */
    ireg = VMECHIP2_TIMERCR;
    *ireg |= (
	      TIMERCR_TT1_EN |		 /* Enable timer */
	      TIMERCR_TT1_COC |		 /* Reset counter when 1sec */
	      TIMERCR_TT1_COVF );	 /* Clear overflow */
    adj_time = 0;			 /* No more time adjustment */
    }
  if (tzp != NULL)
    currentZone = *tzp;
  intUnlock(lockKey);
  return(OK);
}

/********************* adjtime ***********************
 *
 *      (U) adjtime sets the time of day adjustment factor.
 *
 *      (U) adjtime sets the ammount of time to adjust the time of day
 *      clock.  This adjustment is done in small steps so as to not
 *      cause a sudden jump in the time.
 *
 *      (U) The calling parameters for this routine are:
 *          
 */
int adjtime
  ( 
   struct timeval *delta,
/*
 *.             (U) New adjustment factor.
 *.
 *.             Type:            timeval structure 
 *.             Units:           microseconds, seconds
 *.             Accuracy:        1 microseconds
 *.             Precision:       N/A
 *.             Range:           Huge
 *.             
 */
   struct timeval *olddelta
/*
 *.             (U) Remaining adjustment factor
 *.
 *.             Type:            timeval structure 
 *.             Units:           microseconds, seconds
 *.             Accuracy:        1 microseconds
 *.             Precision:       N/A
 *.             Range:           Huge
 *.             
 */
  )
{

  if (olddelta != NULL) 
    {
    olddelta->tv_sec = (adj_time / J68KTIM_USEC_PER_SEC);
    olddelta->tv_usec = (adj_time % J68KTIM_USEC_PER_SEC);
    }

  if (delta != NULL)
    {
    if ( (delta->tv_sec > ADJTIME_MAX) || (delta->tv_sec < -ADJTIME_MAX) )
      return( -1 );

    adj_time = (delta->tv_sec * J68KTIM_USEC_PER_SEC) + delta->tv_usec;
    }

  return( 0 );

}