// $Id: afar_pfc.c,v 1.4 2011/07/08 21:50:30 lpoulsen Exp $
//
// Copyright (c) 2011 by Afar Communications Inc, Santa Barbara, CA 93117 USA
//
// Utility functions for low-overhead high precision short interval timers.
//
// $Log: afar_pfc.c,v $
// Revision 1.4  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.3  2011/04/13 23:28:46  lpoulsen
// Support fast machines where the performance counter ticks at the
//    primary CPU speed (thus requiring pre-scaling).
//
// Revision 1.2  2011/04/13 17:40:10  lpoulsen
// Accept computers with slower performance clock.
// Give better developer information when rejecting performance clock.
//
// Revision 1.1  2011/02/04 22:29:06  lpoulsen
// Many little fixes.
//
//

#include <windows.h>
#include <stdio.h>
#include "afartype.h"
#include "dbflags.h"

unsigned int afar_pfc_freq = 0;
unsigned int afar_pfc_scale = 0;
unsigned int afar_pfc_shift = 0;

// Example values: 25,000,000 on my AMD64 desktop PC
//                  1,19?,??? on an old Windows 98 on Scott's bench

void
afar_pfc_setup(void)
{
    BOOL          good;
    LARGE_INTEGER pfc_freq;

    good = QueryPerformanceFrequency(&pfc_freq);
    if ( (!good)
      || (pfc_freq.u.HighPart != 0)
      || (pfc_freq.u.LowPart == 0)
      || (pfc_freq.u.LowPart < 500*1000))
    {
        int pfc_error = GetLastError();
        printf("Bad return from QueryPerformanceFrequency()\n");
        printf("    GetLastError() returned %d\n", pfc_error);
        printf("    pfc_freq value (hex) high: %08X  low: %08X\n",
               pfc_freq.u.HighPart, pfc_freq.u.LowPart);
        exit(EXIT_FAILURE);
    }
    if (pfc_freq.u.LowPart < 16*1024*1024) {
        afar_pfc_freq  = pfc_freq.u.LowPart;
        afar_pfc_scale = 0;
        afar_pfc_shift = 0;
    } else {
        afar_pfc_freq  = pfc_freq.u.LowPart;
        afar_pfc_scale = pfc_freq.u.LowPart >> 20;
        if (afar_pfc_scale > 1024) {
            afar_pfc_shift = 10;
            afar_pfc_freq = afar_pfc_freq >> afar_pfc_shift;
        } else if (afar_pfc_scale > 64) {
            afar_pfc_shift = 6;
            afar_pfc_freq = afar_pfc_freq >> afar_pfc_shift;
        } else if (afar_pfc_scale > 16) {
            afar_pfc_shift = 4;
            afar_pfc_freq = afar_pfc_freq >> afar_pfc_shift;
        }
        if (debug_flags & DBFLG_DATE_TIME) {
            printf("Hi-res timer: %12.6fMHz (%08X) scale=%d shift=%d\n",
                    pfc_freq.u.LowPart/(1000.0*1000.0),
                    pfc_freq.u.LowPart, afar_pfc_scale, afar_pfc_shift);
            printf("   (Virtual frequency: %d)\n", afar_pfc_freq);
        }
    }
}

uint32
afar_pfc_read(void)
{
    BOOL           good;
    LARGE_INTEGER  pfc_count;

    good = QueryPerformanceCounter(&pfc_count);

    if (!good)
    {
        int pfc_error = GetLastError();
        printf("Bad return from QueryPerformanceCounter()\n");
        printf("    GetLastError() returned %d\n", pfc_error);
        exit(EXIT_FAILURE);
    }
    if (afar_pfc_shift == 0)
        return pfc_count.u.LowPart;
    else {
        ULONGLONG      pfc_value;
        pfc_value = pfc_count.QuadPart;
        pfc_value = pfc_value >> afar_pfc_shift;
        return (uint32)pfc_value;
    }
}

int
afar_usec_delta(uint32 start, uint32 end)
{
    int32   delta = (end - start);
    double  MHz = afar_pfc_freq / 1000000.0;
    int     microsec;

    microsec = (int)(delta/MHz + 0.5);

    return  microsec;
}
