/*
 * rtl_time.c
 *
 * architecture-dependent clock support
 *
 * Written by Michael Barabanov
 * Copyright  Finite State Machine Labs Inc. 1998-1999
 * Released under the terms of the GPL Version 2
 *
 * StandAlone RTLinux integration 
 * written by Vicente Esteve LLoret <viesllo@inf.upv.es> 
 * Copyright (C) May, 2003 OCERA Consortium.
 *
 *
 * 
 */

#include <deblin/vhal.h>
#include <rtl_conf.h>
#include <arch/hw_irq.h>
#include <arch/timer.h>
#include <arch/rtl_io.h>
#include <arch/mprot.h>
#include <rtl_core.h>
#include <rtl_debug.h>
#include <rtl_sync.h>
#include <rtl_time.h>

#ifdef CONFIG_ARM_SA1100
#include <asm/arch/SA-1100.h>
#endif

#ifdef CONFIG_ARM_PXA
#include <asm/arch/hardware.h>
#include <asm/arch/pxa-regs.h>
#endif


//#include <asm/system.h>
//#include <asm/io.h>
//#include <asm/arch/hardware.h>
//#include <asm/arch/irqs.h>
 
static hrtime_t hrtime_resolution;

/* 
** The sa1100 uses a 3.6864 Mhz clock 
** The OS Timer Count register is a 32 bit counter that 
** increments on every rising edge of the 3.6864 Mhz clock.
*/
#define TICKS_PER_SEC	3686400

/* One second contains HZ number ofjiffies*/
#define TICKS_PER_JIFFY	(TICKS_PER_SEC / HZ)

/* Nanoseconds per jiffy */
#define NSECS_PER_JIFFY (NSECS_PER_SEC / HZ)

/* Nanoseconds per tick */
#define NSECS_PER_TICK (NSECS_PER_JIFFY / TICKS_PER_JIFFY)

/* 
** ONE microsecond is in fact 3.6864 ticks
** Lets make one microsecond == 4 ticks
*/
#define ONE_MICRO_SECOND 4

/*
** This should allow to stop the kernel from
** programming the Timer Match Register and let RTL
** handle it (& vice versa)
*/
#define ACTIVATED	1
#define DEACTIVATED	0

static void _sa1100_uninit (clockid_t clock);
static int _sa1100_init (clockid_t clock);
static hrtime_t _sa1100_gettime (struct rtl_clock *);
static int _sa1100_settimer (struct rtl_clock *, hrtime_t interval);
static int _sa1100_settimermode (struct rtl_clock *, int mode);
unsigned int sa1100_intercept(struct pt_regs *);
void default_handler( struct pt_regs *regs) {};

static unsigned long sa1100_tick_count = 0;
static unsigned long linux_decrs = 0; /* number of ticks that passed since last jiffy increase */
static unsigned long wakeup_counter_value = 0; /* tick counter value of last wakeup */
static unsigned long previous_match;


struct rtl_clock sa1100_clock = {
	_sa1100_init, 
	_sa1100_uninit,
	_sa1100_gettime,
	NULL, 			/* sethrtime */
	_sa1100_settimer,
	_sa1100_settimermode,
        default_handler,
	RTL_CLOCK_MODE_ONESHOT, /* mode */
};


static int _sa1100_settimer (struct rtl_clock *clock, hrtime_t interval) {
  __u32 val;

  
  if ( (ulong)(interval>>32) ) {
    val = 0xffffffff;
  }
  else {
    /* Convert the time (ns) to ticks*/
    val = (long)interval / NSECS_PER_TICK + 1;
    if (sa1100_clock.mode == RTL_CLOCK_MODE_PERIODIC)
      clock->resolution = interval;
    else
      clock->resolution = 1;
  }
	
  if ( val > TICKS_PER_JIFFY ) {
		/* time was too high, set timer to hit when Linux wants its next tick */
		if ( TICKS_PER_JIFFY > linux_decrs )
			val = TICKS_PER_JIFFY - linux_decrs;
		else
			val = TICKS_PER_JIFFY;
  }
  else if ( val < ONE_MICRO_SECOND ) {
    val = ONE_MICRO_SECOND;
  }
  

  /* 
  ** We will reprogram the timer, while the previous timer setting did not 
  ** generate an interrupt yet.
  */	
  if (sa1100_clock.arch.istimerset==1) {
    /* We should wake up before the next programmed interrupt */
    if(OSMR0 >= (OSCR+val+ONE_MICRO_SECOND)) {
      sa1100_tick_count = (OSCR-previous_match)+val;
      OSMR0 = OSCR + val;
    }
    return 0;
  }
  
  previous_match=OSMR0;
  sa1100_tick_count = val;
  if ( (OSMR0+=val) <= (OSCR + ONE_MICRO_SECOND)) {
     sa1100_tick_count = (OSCR + ONE_MICRO_SECOND)-previous_match;
     OSMR0=(OSCR + ONE_MICRO_SECOND);
  }
  sa1100_clock.arch.istimerset = 1;
  return 0;
}

/*
** Returns number of microseconds that have passed since last match
*/
static unsigned long gettimeoffset(void) {
  unsigned long ticks_time_diff;
  unsigned long us_time;
  /* number of ticks that have passed since our last match */
  ticks_time_diff = OSCR - wakeup_counter_value;
  
  /* converting to us */
  us_time = ((ticks_time_diff * NSECS_PER_TICK) / 1000);
  
  return us_time;
}

/*
** Returns the time in ns
*/
hrtime_t _sa1100_gettime (struct rtl_clock *clock) {
  hrtime_t ret;
  ret = (sa1100_clock.arch.time * NSECS_PER_TICK) + (gettimeoffset()*1000);
  return ret;
}


static int _sa1100_settimermode(struct rtl_clock *clock, int mode) {
  clock->mode = mode;
  return 0;
}

/*
** Called from scheduler 'init_module'
*/
clockid_t rtl_getbestclock (unsigned int cpu) {
  return &sa1100_clock;
}
		 	 	
/*
** time in ns                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             s since bootup
*/
hrtime_t gethrtime(void) {
  return _sa1100_gettime(&sa1100_clock);
}

/*
** number of nanoseconds per tick
*/ 
hrtime_t gethrtimeres(void) {
  return NSECS_PER_TICK;
}

/*
** busy waiting for deadline
*/
inline void rtl_delay(long nanoseconds) {
  unsigned long match = OSCR + (nanoseconds/NSECS_PER_TICK);
  while(OSCR < match) { }
}

/*
** Interrupt Handler Routine
*/
static unsigned int _sa1100_timer_intercept( unsigned int irq, struct pt_regs *regs ) {
  void (*handler)( struct pt_regs *regs) = sa1100_clock.handler;
  // clear match on timer 0 
  OSSR = OSSR_M0;
  // update the time in the clock
  sa1100_clock.arch.time += sa1100_tick_count;

  // record the time we last woke up
  wakeup_counter_value = OSCR;

  if ( (sa1100_clock.mode == RTL_CLOCK_MODE_ONESHOT) && sa1100_clock.arch.istimerset ) {
    sa1100_clock.arch.istimerset = 0;
  }
	
  /* give the interrupt to Linux */
  linux_decrs += sa1100_tick_count;
  
//  rtl_irq_controller_enable(IRQ_OST0);
  
  OSSR = 0x1;
  
  if ( handler != default_handler ) {
    handler(regs);
  }
  
  sa1100_clock.arch.istimerset = 1;
  return 0;
}


/*
** Called when the scheduler module is inserted.
** (init_module of scheduler)
*/

 static int _sa1100_init (clockid_t clock) {
  /* on next tick, how many have gone by */
  sa1100_tick_count = TICKS_PER_JIFFY;	
  rtl_request_global_irq(IRQ_OST0, _sa1100_timer_intercept );
//  _sa1100_settimer(&sa1100_clock, (hrtime_t) 0x4000000); 
  OSSR = 0xf;
  OIER |= OIER_E0;
  OSCR = 0;
  ICMR = 1<<26;// IC_OST0;
  
  return 0;
}

/*
** Called upon removal of scheduler module.
*/
static void _sa1100_uninit (clockid_t clock) {
//  clock->handler = RTL_CLOCK_DEFAULTS.handler;
//  rtl_free_global_irq(IRQ_OST0);
}

int init_clocks (void)
{
  rtl_init_standard_clocks();
  return 0;
}


