/*  SVGATextMode -- An SVGA textmode manipulation/enhancement tool
 *
 *  Copyright (C) 1995  Koen Gadeyne
 *
 *  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 of the License, 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; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/***
 *** probe.c
 *** Copyright (c) 1995 by Koen Gadeyne (kmg@barco.be)
 ***
 *** All kinds of probing fuctions (currently just the clock probe)
 ***
 *** Try to measure CURRENT pixel clock.
 *** Should work on ANY VGA card, since it only uses standard VGA registers
 ***
 *** - No need to disable interrupts to be able to measure!
 *** - Can give slightly inaccurate results on heavily loaded machines (but normally not VERY wrong)
 *** - Due to unexplained "glitches" in the vertical sync register, some timing attempts go wrong.
 ***   this is detected in the program, and it tries again.
 ***   Does anyone know WHY those glitches are there, and how to circumvent them?
 ***
 *** There should even be a possibility to measure H-frequencies using input status 1 bit 0 (0x3DA, bit 0).
 ***
 ***
 ***/

#undef DBG_MEASURE 

#include <stdio.h>
#include <string.h>
#include <values.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#ifndef DOS
#  define UCLOCKS_PER_SEC 1000000
#endif
#define URATIO (1000000.0/(float)UCLOCKS_PER_SEC)

#ifndef DOS
#  include <asm/io.h>
#endif

#include "misc.h"
#include "vga_prg.h"
#include "configfile.h"
#include "wait_vsync.h"
#include "messages.h"


/*
 * The Clock probe. Given the hor. and vert. REAL TOTAL screen size, it returns the pixel clock in MHz
 * 
 * 'REAL' means you have to input the total screen width (which is read from VGA regs in CHARS)
 * multiplied by the font size (8 or 9).
 *
 * NOTE: this function ASSUMES it has the rights to read VGA register STATUS1 !
 *
 * Try to measure CURRENT pixel clock.
 * Should work on ANY VGA card, since it only uses standard VGA registers
 *
 * - No need to disable interrupts to be able to measure! (the probe in the X-server does, because it doesn't use timers). 
 * - Can give slightly inaccurate results on heavily loaded machines (but normally not VERY wrong)
 * - Due to unexplained "glitches" in the vertical sync register, some timing attempts go wrong.
 *   this is detected in the program, and it tries again.
 *   Does anyone know WHY those glitches are there, and how to circumvent them?
 * - has the tendency to over-estimate the clock rate by about 0.1 MHz. No real clue why... (or is it just on MY machine?)
 *
 * There should even be a possibility to measure H-frequencies using input status 1 bit 0 (STATUS1, bit 0).
 *
 */
 
#define REALLY_SLOW_IO

/* number of frames to count for timing measurement */
#define NFRAMES    200


/* number sorting function for sort() */
static int compar(long* a, long* b)
{
  return(*a - *b);
}

                   
#define M_RETRY      3            /* maximum number of retries */
#define TIME_BAND    5            /* in usec, defines the time-band size used for building a histogram */ 
#define VALID_MEASR  NFRAMES*2/3  /* this number of measurements must be valid, or measurement will be bad */


float pixclock(int hsize, int vsize)
{
#ifndef DOS
  struct timeval tv;
#endif
  int i;
  long measure[NFRAMES+1];
  long center, num;
  double scanrate;
  int retries=0;
  double av;
  long current, centernum;

  int (*compfunc) ();
  compfunc=compar;

  if (!safe_wait_vsync()) return(0.0);

  /* measure */
  do
  {
    /*** Try to do a measurement ***/
    PDEBUG(("Measurement attempt #%d",retries));
    waitframe;    /* synchronize */

    /* this short measurement loop should be optimized heavily, or even written in assembler */
    for (i=0; i<NFRAMES+1; i++)
    {
      waitframe;    /* measure */
#ifndef DOS
      gettimeofday(&tv, NULL); 
      measure[i] = tv.tv_usec;  /* this will go wrong if we are task-switched out for > 1 sec ... */
#else
      /* DJGPP also has gettimeofday(), but resolution is only 1/18 sec. So we use uclock() */ 
      measure[i] = uclock();
#endif
    }
    /* end of measurement loop */ 

    av = 0;
    /*** convert absolute timer intervals to relative intervals ***/
    for (i=0; i<NFRAMES; i++)
    {
#ifdef DBG_MEASURE
      printf(" %ld", measure[i]);
#endif
      measure[i] = measure[i+1] - measure[i];
#ifndef DOS
      /* UNIX: usec counter wraps around at 1 sec (1000000 usec)
       *
       * DJGPP/DOS: the usec timer does not wrap around at 1 sec.
       *            It is a 64-bit counter, so it NEVER produces a negative difference result
       */
      if (measure[i]<0) measure[i] += 1000000;
#endif
    }
    
#ifdef DBG_MEASURE
      printf("\n");
#endif
    
    /*** sort measurements ***/
    qsort(measure, NFRAMES, sizeof(long), compfunc);

    /*** find value at peak of histogram ***/
    /*** Use that to filter out values out of bounds ***/
    PDEBUG(("URATIO = %1.3f", URATIO));
    current = measure[0];
    center = current + TIME_BAND/2; centernum = 0;
    i = 0;
    do
    {
      num = 0;
      while ((abs(measure[i]-current) < TIME_BAND) && (i<NFRAMES))
      {
        num++; i++;
      }
      if (num > centernum)
      {
        centernum = num;
        center = current + TIME_BAND/2;
      }
      if (num>0) PDEBUG(("Time slot: %1.0f..%1.0f usec, number: %d", ((float)current)*URATIO, ((float)current)*URATIO+TIME_BAND, num));
      current += TIME_BAND;
    } while (i<NFRAMES);
    PDEBUG(("Center = %1.0f usec", ((float)center)*URATIO));

    /*** Use histogram peak as center value, filter out values out of bounds ***/
    av = 0; num = 0;
    for (i=0; i<NFRAMES; i++)
    {
      if (abs(measure[i]-center) < (TIME_BAND*3))
      {
        av+=measure[i];
        num++;
      }
    }
    av /= num;
    
    retries++;
    PDEBUG(("Measurement: valid measurements: %ld/%d", num, NFRAMES));
  }
  while ( (retries<M_RETRY) && (num<VALID_MEASR) );

  scanrate = UCLOCKS_PER_SEC/av;

  PDEBUG(("average framerate (from valid measurements only) = %5.1f usec", av * URATIO));
  if (num < VALID_MEASR)
  {
    PWARNING(("Only %ld%% of measurements were considered 'valid',", num*100/NFRAMES));
#ifdef DOS
    PWARNING(("\n  If this were UNIX, then your system would have to be under severe load.\n\
  But... Since it isn't, MS-Winslows (DOS box) could be the reason."));
#else
    PWARNING(("Your system is probably under heavy load."));
#endif
    PWARNING(("The measurement you get can be inaccurate!"));
  }

  PDEBUG(("Real total H = %d , total V = %d", hsize, vsize));

  return((scanrate * hsize * vsize) / 1000000);
}
