/*
 *      Copyright (C) 1993 Bas Laarhoven.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

 $Source: /usr/src/distr/ftape-0.9.7/RCS/calibr.c,v $
 $Author: bas $
 *
 $Revision: 1.3 $
 $Date: 1993/12/04 11:52:41 $
 $State: Alpha $
 *
 *      GP calibration routine for processor speed dependent
 *      functions.
 */

static char RCSid[] = "$Id: calibr.c,v 1.3 1993/12/04 11:52:41 bas Alpha $";

#include <linux/errno.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/io.h>

#include "calibr.h"

#include <linux/timex.h>
extern struct timeval xtime;

#undef DEBUG

/*
 *      Following code fragment was copied from the kernel file `time.c'
 *      It allows us to experiment with these routines without having to
 *      compile and reboot the kernel every time.
 */

/* This function must be called with interrupts disabled 
 * It was inspired by Steve McCanne's microtime-i386 for BSD.  -- jrs
 * 
 * However, the pc-audio speaker driver changes the divisor so that
 * it gets interrupted rather more often - it loads 64 into the
 * counter rather than 11932! This has an adverse impact on
 * do_gettimeoffset() -- it stops working! What is also not
 * good is that the interval that our timer function gets called
 * is no longer 10.0002 msecs, but 9.9767 msec. To get around this
 * would require using a different timing source. Maybe someone
 * could use the RTC - I know that this can interrupt at frequencies
 * ranging from 8192Hz to 2Hz. If I had the energy, I'd somehow fix
 * it so that at startup, the timer code in sched.c would select
 * using either the RTC or the 8253 timer. The decision would be
 * based on whether there was any other device around that needed
 * to trample on the 8253. I'd set up the RTC to interrupt at 1024Hz,
 * and then do some jiggery to have a version of do_timer that 
 * advanced the clock by 1/1024 sec. Every time that reached over 1/100
 * of a second, then do all the old code. If the time was kept correct
 * then do_gettimeoffset could just return 0 - there is no low order
 * divider that can be accessed.
 *
 * Ideally, you would be able to use the RTC for the speaker driver,
 * but it appears that the speaker driver really needs interrupt more
 * often than every 120us or so.
 *
 * Anyway, this needs more thought....		pjsg (28 Aug 93)
 * 
 * If you are really that interested, you should be reading
 * comp.protocols.time.ntp!
 */

#define TICK_SIZE tick

static inline unsigned long do_gettimeoffset(void)
{
	int count;
	unsigned long offset = 0;

	/* timer count may underflow right here */
	outb_p(0x00, 0x43);	/* latch the count ASAP */
	count = inb_p(0x40);	/* read the latched count */
	count |= inb(0x40) << 8;
	/* we know probability of underflow is always MUCH less than 1% */
	if (count > (LATCH - LATCH/100)) {
		/* check for pending timer interrupt */
		outb_p(0x0a, 0x20);
		if (inb(0x20) & 1)
			offset = TICK_SIZE;
	}
	count = ((LATCH-1) - count) * TICK_SIZE;
	count = (count + LATCH/2) / LATCH;
	return offset + count;
}

/*
 * This version of gettimeofday has near microsecond resolution.
 */
void do_gettimeofday(struct timeval *tv)
{
        unsigned long flags;

        save_flags( flags);
	cli();
	*tv = xtime;
#ifdef __i386__
	tv->tv_usec += do_gettimeoffset();
#endif
        restore_flags( flags);
	if (tv->tv_usec >= 1000000) {
		tv->tv_usec -= 1000000;
		tv->tv_sec++;
	}
}

/*
 *===========================================================
 */

/*
 *      Input:  function taking int count as parameter.
 *              pointers to calculated calibration variables.
 */
int
calibrate( char* name, void(*fun)(int), int* calibr_count, int* calibr_time)
{
  struct timeval t1 = { 0, 0 }, t2 = { 0, 0 }, t3 = { 0, 0};
  int overhead, delta;

  /*    value of timeout must be set so that on very slow systems
   *    it will give a time less than one jiffy, and on
   *    very fast systems it'll give reasonable precision.
   */

  *calibr_count = 50;

  printk( "calibr.c (calibrate) - starting calibration of function: `%s'.\n", name);
  for (;;) {
    *calibr_time = *calibr_count; /* set correction factor to 1 */

    do_gettimeofday( &t1);
    fun( *calibr_count);
    do_gettimeofday( &t2);
    fun( 0);
    do_gettimeofday( &t3);

#ifdef DEBUG
    printk( "T: %6d & %6d => %8u, %8d, %8d\n",
           *calibr_count, *calibr_time,
           t1.tv_sec * 1000000 + t1.tv_usec,
           t2.tv_sec * 1000000 + t2.tv_usec - (t1.tv_sec * 1000000 + t1.tv_usec),
           t3.tv_sec * 1000000 + t3.tv_usec - (t2.tv_sec * 1000000 + t2.tv_usec) );
#endif

    overhead = (t3.tv_sec - t2.tv_sec) * 1000000 + (t3.tv_usec - t2.tv_usec);
    delta = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec);
    if (delta <= overhead) {
      printk( "calibr.c (calibrate) - unlikely result, retry.\n");
      continue;
    } else {
      *calibr_time = delta - overhead;
    }
    /*
     * increase the count until the resulting time nears 10/HZ
     */
    if (2 * *calibr_time > 10 * 1000000 / HZ) {
      break;
    }
    *calibr_count *= 2;
  }
  printk( "calibr.c (calibrate) - time = %d usec for count = %d.\n",
         *calibr_time, *calibr_count);
  /*  Verify the timing
   */
  do_gettimeofday( &t1);
  fun( 1000);
  do_gettimeofday( &t2);
  fun( 0);
  do_gettimeofday( &t3);

#ifdef DEBUG
    printk( "V: %6d & %6d => %8u, %8d, %8d\n",
           *calibr_count, *calibr_time,
           t1.tv_sec * 1000000 + t1.tv_usec,
           t2.tv_sec * 1000000 + t2.tv_usec - (t1.tv_sec * 1000000 + t1.tv_usec),
           t3.tv_sec * 1000000 + t3.tv_usec - (t2.tv_sec * 1000000 + t2.tv_usec) );
#endif

  overhead = (t3.tv_sec - t2.tv_sec) * 1000000 + (t3.tv_usec - t2.tv_usec);
  delta = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec);
  if (delta - overhead < 990) {
    printk( "calibr.c (calibrate) - `%s' timer function might be not accurate enough (%d) !\n",
           name, delta - overhead - 1000 );
  }
  return 0;
}
