/*
 *      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/driver/RCS/calibr.c,v $
 $Author: bas $
 *
 $Revision: 1.2 $
 $Date: 1993/08/29 15:24:41 $
 $State: Alpha $
 *
 *      GP calibration routine for processor speed dependent
 *      functions.
 */

static char RCSid[] = "$Id: calibr.c,v 1.2 1993/08/29 15:24:41 bas Alpha $";

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

#include "calibr.h"

/*
 *      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; /* disable correction in fdc_wait */

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

    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);
    *calibr_time = delta - overhead;
    if (2 * *calibr_time > 1000000 / HZ) {
      break;
    }
    *calibr_count *= 2;
  }
#if 0
    printk( "calibr.c (calibrate) - t1.tv_sec = %d, t1.tv_usec = %d\n",
           t1.tv_sec, t1.tv_usec);
    printk( "calibr.c (calibrate) - t2.tv_sec = %d, t2.tv_usec = %d\n",
           t2.tv_sec, t2.tv_usec);
    printk( "calibr.c (calibrate) - t3.tv_sec = %d, t3.tv_usec = %d\n",
           t3.tv_sec, t3.tv_usec);
#endif
  printk( "calibr.c (calibrate) - time = %d usec for count = %d.\n",
         *calibr_time, *calibr_count);
  /*  Verify the timing
   */
  cli();
  do_gettimeofday( &t1);
  fun( 1000);
  do_gettimeofday( &t2);
  fun( 0);
  do_gettimeofday( &t3);
  sti();
  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);
  printk( "calibr.c (calibrate) - verification: error = %d promille.\n",
         delta - overhead - 1000);

  return 0;
}


/*
 *      Next code fragment was copied from the kernel
 *      so we can find out why bad values are returned
 *      without recompiling the kernel.
 */

#define LATCH ((1193180 + HZ/2)/HZ)

#define PIC_ICW1        (0x20)
#define PIC_ICW24       (0x21)

#define PIT_C0          (0x40)
#define PIT_C1          (0x41)
#define PIT_C2          (0x42)
#define PIT_CTL         (0x43)

/*
 * This version of gettimeofday has near microsecond resolution.
 * It was inspired by Steve McCanne's microtime-i386 for BSD.  -- jrs
 */
void do_gettimeofday( struct timeval *tv)
{
  unsigned long nowtime;
  long count;
  long flags;

  save_flags( flags);
  cli();

  outb_p( 0x0a, PIC_ICW1);
  nowtime = inb( PIC_ICW1) & 1; /* use nowtime for scratch */
  /* timer count may underflow right here */

  outb_p( 0x00, PIT_CTL);	/* latch the count ASAP */
  count = inb_p( PIT_C0);	/* read the latched count */
  count |= inb_p( PIT_C0) << 8;

  outb_p( 0x0a, PIC_ICW1);
  if ((inb( PIC_ICW1) & 1) && (count > LATCH / 2 || (nowtime && count != 1))) {
    nowtime = jiffies + 1;
  } else { 
    nowtime = jiffies;
  }
  restore_flags( flags);
  
  nowtime += jiffies_offset;
  tv->tv_sec = startup_time + CT_TO_SECS(nowtime);
  /* the correction term is always in the range [0, 1) clocktick */
  tv->tv_usec = CT_TO_USECS(nowtime) + ((LATCH - 1) - count)*(1000000/HZ)/LATCH;
}
