/* $Id: time.c,v 1.3 2005/10/19 05:38:20 cgoh Exp $
 *
 * time.c  MAP specific time handling
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Equator Technologies
 */
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/timex.h>
#include <linux/kernel_stat.h>
#include <asm/assert.h>

#include <libdev/etiContext.h>
#include <libdev/etiProcessor.h>
#include <libdev/etiTimers.h>

#include "context.h"

#include <linux/mc146818rtc.h>
#include <linux/timex.h>

#include <kernelLib/etiDriverOsal.h>

extern volatile unsigned long lost_ticks;

/* This is for machines which generate the exact clock. */
#define USECS_PER_JIFFY (1000000/HZ)




void do_gettimeofday(struct timeval *tv)
{
	unsigned long flags;

	save_and_cli(flags);
	*tv = xtime;
#if 0
	/* FIXME */
	tv->tv_usec += do_gettimeoffset();
#endif
	/*
	 * xtime is atomically updated in timer_bh. lost_ticks is
	 * nonzero if the timer bottom half hasnt executed yet.
	 */
	if (lost_ticks)
		tv->tv_usec += USECS_PER_JIFFY;

	restore_flags(flags);

	if (tv->tv_usec >= 1000000) {
		tv->tv_usec -= 1000000;
		tv->tv_sec++;
	}
}

void do_settimeofday(struct timeval *tv)
{
	ETISTUB("do_settimeofday()");
}


/*
 * In order to set the CMOS clock precisely, set_rtc_mmss has to be
 * called 500 ms after the second nowtime has started, because when
 * nowtime is written into the registers of the CMOS clock, it will
 * jump to the next second precisely 500 ms later. Check the Motorola
 * MC146818A or Dallas DS12887 data sheet for details.
 *
 * BUG: This routine does not handle hour overflow properly; it just
 *      sets the minutes. Usually you won't notice until after reboot!
 */
static int set_rtc_mmss(unsigned long nowtime)
{
	int retval = 0;
	int real_seconds, real_minutes, cmos_minutes;
	unsigned char save_control, save_freq_select;

	save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */
	CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);

	save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */
	CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);

	cmos_minutes = CMOS_READ(RTC_MINUTES);
	if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
		BCD_TO_BIN(cmos_minutes);

	/*
	 * since we're only adjusting minutes and seconds,
	 * don't interfere with hour overflow. This avoids
	 * messing with unknown time zones but requires your
	 * RTC not to be off by more than 15 minutes
	 */
	real_seconds = nowtime % 60;
	real_minutes = nowtime / 60;
	if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1)
		real_minutes += 30;		/* correct for half hour time zone */
	real_minutes %= 60;

	if (abs(real_minutes - cmos_minutes) < 30) {
		if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
			BIN_TO_BCD(real_seconds);
			BIN_TO_BCD(real_minutes);
		}
		CMOS_WRITE(real_seconds,RTC_SECONDS);
		CMOS_WRITE(real_minutes,RTC_MINUTES);
	} else {
		printk(KERN_WARNING
		       "set_rtc_mmss: can't update from %d to %d\n",
		       cmos_minutes, real_minutes);
 		retval = -1;
	}

	/* The following flags have to be released exactly in this order,
	 * otherwise the DS12887 (popular MC146818A clone with integrated
	 * battery and quartz) will not reset the oscillator and will not
	 * update precisely 500 ms later. You won't find this mentioned in
	 * the Dallas Semiconductor data sheets, but who believes data
	 * sheets anyway ...                           -- Markus Kuhn
	 */
	CMOS_WRITE(save_control, RTC_CONTROL);
	CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);

	return retval;
}



/*--------------------------------- Definitions -------------------------------*/

#define MAX_DELAY             0x100000 /* in cycles */
#define GRANULARITY             0x8000 /* in cycles */
#define MAX_VIOLATORS               16

#define MAX_BUCKET   (MAX_DELAY / GRANULARITY)

static  timTimer_t    timer;
static  UInt          histogram[ MAX_BUCKET + 1 ];
static  Register      violators[ MAX_VIOLATORS  ];
static  UInt          vptr;
static  UInt          highestLatency;
static  UInt          highestLatencyTime;
static  Register      highestLatencyAddress;
static  UInt          missedTicks;
static  Int32         timerModulus;
static  UInt32	      TotalInterrupts;
static  UInt32	      TotalLatency;



static void
timer_handler( ctxExceptionContext_t ctx, timTimer_t timer, Pointer data ) 
{
	timSetup_t    setup;
	int           cpu = smp_processor_id();
	UInt          latency;
	static UInt   count;
	static UInt   next_fcnt;
        UInt          nrofActivations= 1;

	timGetSetup(timer, &setup);
	latency= setup.value;

	TotalLatency += latency/100;
	TotalInterrupts ++;

	
	/*
	 * Compensate for missed timer ticks:
	 */
	if (next_fcnt) {
	    while ( ((Int)((UInt)hmpv_read_fcnt() - next_fcnt)) > timerModulus ) {
	        next_fcnt += timerModulus; 
	        latency   += timerModulus;
	        missedTicks++;
	        nrofActivations++;
	    }
	    next_fcnt += timerModulus;
	} else {
	    next_fcnt  = hmpv_read_fcnt() + timerModulus;
	}


	/* 
	 * perform some aging on the latency information:
	 */
	if (count++ == 5000) {
	    UInt i;

	    count           = 0;
	    highestLatency /= 2;
	    TotalLatency /= 2;
	    TotalInterrupts = (TotalInterrupts + 1)/2;

	    for (i= 0; i < (MAX_BUCKET+1);    i++) { histogram[i] /= 2;                      }
	    for (i= 0; i < (MAX_VIOLATORS/4); i++) { violators[ vptr++ % MAX_VIOLATORS ]= 0; }
	}

	if (highestLatency <= latency) {
	    highestLatency = latency;
	    highestLatencyAddress= ctx->epcr0;
	}

	/*
	 * Log experienced exception latency:
	 */
	if (latency >= MAX_DELAY) {
	    vptr %= MAX_VIOLATORS;
	    violators[ vptr++     ]= ctx->epcr0;
	    histogram[ MAX_BUCKET ]++;
	} else {
	    histogram[ latency/GRANULARITY ]++;
	}


	CHECK_STACK("T");

	if ( (ctx->cl[0].cpse | ctx->cl[1].cpse) & 1 ) {
	    printk("DTLB problem (time.c): cpse0= 0x%08x cpse1= 0x%08x \n",
	          ctx->cl[0].cpse, ctx->cl[1].cpse );
	}

        while (nrofActivations--) {
	    hardirq_enter(cpu);
	    {
	        do_timer( (struct pt_regs*) ctx );

	        /*
	         * Kernel profiling: turn on by specifying
	         * profile=<num> on the boot command line.
	         * <num> is the right shift value of the addresses,
	         * i.e. the granularity of the profiler.
	         */
	        if (prof_buffer && !user_mode(ctx) && current->pid) {
		    extern int _stext;
		    unsigned long pc = ctx->epcr0;

		    pc -= (unsigned long) &_stext;
		    pc >>= prof_shift;
		    /*
		     * Dont ignore out-of-bounds pc values silently,
		     * put them into the last histogram slot, so if
		     * present, they will show up as a sharp peak.
		     */
		    if (pc > prof_len-1)
		        pc = prof_len-1;
		    atomic_inc((atomic_t *)&prof_buffer[pc]);
	        }
	    }
	    hardirq_exit(cpu); 
	}

	CHECK_STACK("t");

	RUN_BOTTOM;
}



#include <linux/proc_fs.h>
#include <stdarg.h>


static int read_latency_procmem(char *buf, char **start, off_t offset, int count, int unused)
{
    UInt bucket;
    UInt len   = 0;    
    UInt limit = count - 250;    
    UInt AverageLatencyTime = 0;

    if (offset > 0) { return 0; }

    len = sprintf( buf, "- Recent interrupt latency histogram:\n");

    for (bucket= 0; bucket < MAX_BUCKET; bucket++) {
	 UInt32 startTime= timCyclesToNanoSeconds( bucket    * GRANULARITY) / 1000;
	 UInt32 endTime  = timCyclesToNanoSeconds((bucket+1) * GRANULARITY) / 1000;
	 UInt   amount   = histogram[bucket];

	 if (amount) {
	     len += sprintf( buf+len, " %9d - %9d us : %10d hits\n", startTime, endTime, amount );
	     if (len > limit) { return len; }
	 }
    }

    if (histogram[MAX_BUCKET]) {
	 UInt maxDelayTime       = timCyclesToNanoSeconds(MAX_DELAY)      / 1000;
	 UInt amount             = histogram[MAX_BUCKET];
	 UInt nextWrap= 0;
	 UInt i;

	 len += sprintf( buf+len, "\n- Overflow bucket (>%d us): %10d hits\n", maxDelayTime, amount);
	 len += sprintf( buf+len, "     These latencies have been experienced near the following addresses:\n");
	 
	 if (len > limit) { return len; }

	 for (i=0; i<MAX_VIOLATORS; i++) {
	     Register vaddr= violators[i];
	     if (vaddr) { 
	         if (nextWrap < len) { len += sprintf( buf+len, "\n\t"); nextWrap= len+80; }
	         len += sprintf( buf+len, "0x%08x ", vaddr ); 
	         if (len > limit) { return len; }
	     }
	 }
    }
    highestLatencyTime = timCyclesToNanoSeconds(highestLatency) / 1000;
    len += sprintf( buf+len, "\n\n- Highest latency: %d us near address 0x%08x\n", 
	                      highestLatencyTime,
	                      highestLatencyAddress );
    AverageLatencyTime = TotalLatency/TotalInterrupts;
    len += sprintf( buf+len, "- Average latency: %d us \n", 
				AverageLatencyTime );

    if (missedTicks) {
	 len += sprintf( buf+len, "- In total, %d timer %s been missed\n", missedTicks, missedTicks==1?"tick has":"ticks have");
    }

    return len;
}


static struct proc_dir_entry latency_proc_entry = {
	 0,
	 16, "interruptlatency",
	 S_IFREG | S_IRUGO,
	 1,0,0,
	 0,
	 NULL,
	 &read_latency_procmem,
    };

static UInt proc_latency_register()
{
    proc_register( &proc_root, &latency_proc_entry);
    return 0;
}


/*
 * Add it to etiDriverOsal's autoinit list:
 */
drosDefineDriverInit(proc_latency_register)





/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
 * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
 *
 * [For the Julian calendar (which was used in Russia before 1917,
 * Britain & colonies before 1752, anywhere else before 1582,
 * and is still in use by some communities) leave out the
 * -year/100+year/400 terms, and add 10.]
 *
 * This algorithm was first published by Gauss (I think).
 *
 * WARNING: this function will overflow on 2106-02-07 06:28:16 on
 * machines were long is 32-bit! (However, as time_t is signed, we
 * will already get problems at other places on 2038-01-19 03:14:08)
 */
static inline unsigned long mktime(unsigned int year, unsigned int mon,
	unsigned int day, unsigned int hour,
	unsigned int min, unsigned int sec)
{
	if (0 >= (int) (mon -= 2)) {	/* 1..12 -> 11,12,1..10 */
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}
	return (((
	    (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) +
	      year*365 - 719499
	    )*24 + hour /* now have hours */
	   )*60 + min /* now have minutes */
	  )*60 + sec; /* finally seconds */
}

__initfunc(void time_init(void))
{
	unsigned int i, epoch, year, mon, day, hour, min, sec, tmp;

	 procCapabilities_t   caps;
	 timTimer_t           timer;
	 timSetup_t           setup;

	 procGetCapabilities( &caps );
	 timerModulus= (unsigned long)(((double)caps.cpuFrequency) / HZ);

	 setup.value   = 0;
	 setup.modulus = timerModulus;
	 setup.handler = timer_handler;
	 setup.data    = Null;
	 setup.nmi     = False;

	 timOpen ( &timer );
	 timSetup( timer, &setup );

	

#ifdef CONFIG_ETI_PCI_MASTER

#if 1
	/* set real-time clock */
	//year = 2001;  //ABH 050722   What the hell orignal sorce mean?! anyway i change initial year to 2005.
	year = 0x105;
	mon = 0x12;
	day = 0x25;
	hour = 0;
	min = 0;
	sec = 0;
	//CMOS_WRITE(year - 1974, RTC_YEAR);  //ABH 050722
	CMOS_WRITE(year, RTC_YEAR);
	CMOS_WRITE(mon, RTC_MONTH);
	CMOS_WRITE(day, RTC_DAY_OF_MONTH);
	CMOS_WRITE(hour, RTC_HOURS);
	CMOS_WRITE(min, RTC_MINUTES);
	CMOS_WRITE(sec, RTC_SECONDS);
#else
	/* Read real-time clock */

   	/* read RTC exactly on falling edge of update flag */
	for (i = 0 ; i < 1000000 ; i++)	/* may take up to 1 second... */
		if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)
			break;
	for (i = 0 ; i < 1000000 ; i++)	/* must try at least 2.228 ms */
		if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))
			break;
#endif
	do { /* Isn't this overkill ? UIP above should guarantee consistency */
		sec = CMOS_READ(RTC_SECONDS);
		min = CMOS_READ(RTC_MINUTES);
		hour = CMOS_READ(RTC_HOURS);
		day = CMOS_READ(RTC_DAY_OF_MONTH);
		mon = CMOS_READ(RTC_MONTH);
		year = CMOS_READ(RTC_YEAR);
	} while (sec != CMOS_READ(RTC_SECONDS));

	if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
	  {
	    BCD_TO_BIN(sec);
	    BCD_TO_BIN(min);
	    BCD_TO_BIN(hour);
	    BCD_TO_BIN(day);
	    BCD_TO_BIN(mon);
	    BCD_TO_BIN(year);
	  }

	if ((year += 1900) < 1970)
		year += 100;

	xtime.tv_sec = mktime(year, mon, day, hour, min, sec);
	xtime.tv_usec = 0;
#endif /* CONFIG_ETI_PCI_MASTER */
}
