/*	Cyclone-timer: 
 *		This code implements timer_ops for the cyclone counter found
 *		on IBM x440, x360, and other Summit based systems.
 *
 *	Copyright (C) 2002 IBM, John Stultz (johnstul@us.ibm.com)
 */


#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/timex.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/jiffies.h>

#include <asm/timer.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/fixmap.h>

extern spinlock_t i8253_lock;

/* Number of usecs that the last interrupt was delayed */
static int delay_at_last_interrupt;

#define CYCLONE_CBAR_ADDR 0xFEB00CD0
#define CYCLONE_PMCC_OFFSET 0x51A0
#define CYCLONE_MPMC_OFFSET 0x51D0
#define CYCLONE_MPCS_OFFSET 0x51A8
#define CYCLONE_TIMER_FREQ 100000000
#define CYCLONE_TIMER_MASK (((u64)1<<40)-1) /* 40 bit mask */
int use_cyclone = 0;

static u32* volatile cyclone_timer;	/* Cyclone MPMC0 register */
static u32 last_cyclone_low;
static u32 last_cyclone_high;
static unsigned long long monotonic_base;
static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED;

/* helper macro to atomically read both cyclone counter registers */
#define read_cyclone_counter(low,high) \
	do{ \
		high = cyclone_timer[1]; low = cyclone_timer[0]; \
	} while (high != cyclone_timer[1]);


static void mark_offset_cyclone(void)
{
	unsigned long lost, delay;
	unsigned long delta = last_cyclone_low;
	int count;
	unsigned long long this_offset, last_offset;

	write_seqlock(&monotonic_lock);
	last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
	
	spin_lock(&i8253_lock);
	read_cyclone_counter(last_cyclone_low,last_cyclone_high);

	/* read values for delay_at_last_interrupt */
	outb_p(0x00, 0x43);     /* latch the count ASAP */

	count = inb_p(0x40);    /* read the latched count */
	count |= inb(0x40) << 8;
	spin_unlock(&i8253_lock);

	/* lost tick compensation */
	delta = last_cyclone_low - delta;	
	delta /= (CYCLONE_TIMER_FREQ/1000000);
	delta += delay_at_last_interrupt;
	lost = delta/(1000000/HZ);
	delay = delta%(1000000/HZ);
	if (lost >= 2)
		jiffies_64 += lost-1;
	
	/* update the monotonic base value */
	this_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
	monotonic_base += (this_offset - last_offset) & CYCLONE_TIMER_MASK;
	write_sequnlock(&monotonic_lock);

	/* calculate delay_at_last_interrupt */
	count = ((LATCH-1) - count) * TICK_SIZE;
	delay_at_last_interrupt = (count + LATCH/2) / LATCH;


	/* catch corner case where tick rollover occured 
	 * between cyclone and pit reads (as noted when 
	 * usec delta is > 90% # of usecs/tick)
	 */
	if (lost && abs(delay - delay_at_last_interrupt) > (900000/HZ))
		jiffies_64++;
}

static unsigned long get_offset_cyclone(void)
{
	u32 offset;

	if(!cyclone_timer)
		return delay_at_last_interrupt;

	/* Read the cyclone timer */
	offset = cyclone_timer[0];

	/* .. relative to previous jiffy */
	offset = offset - last_cyclone_low;

	/* convert cyclone ticks to microseconds */	
	/* XXX slow, can we speed this up? */
	offset = offset/(CYCLONE_TIMER_FREQ/1000000);

	/* our adjusted time offset in microseconds */
	return delay_at_last_interrupt + offset;
}

static unsigned long long monotonic_clock_cyclone(void)
{
	u32 now_low, now_high;
	unsigned long long last_offset, this_offset, base;
	unsigned long long ret;
	unsigned seq;

	/* atomically read monotonic base & last_offset */
	do {
		seq = read_seqbegin(&monotonic_lock);
		last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
		base = monotonic_base;
	} while (read_seqretry(&monotonic_lock, seq));


	/* Read the cyclone counter */
	read_cyclone_counter(now_low,now_high);
	this_offset = ((unsigned long long)now_high<<32)|now_low;

	/* convert to nanoseconds */
	ret = base + ((this_offset - last_offset)&CYCLONE_TIMER_MASK);
	return ret * (1000000000 / CYCLONE_TIMER_FREQ);
}

static int __init init_cyclone(char* override)
{
	u32* reg;	
	u32 base;		/* saved cyclone base address */
	u32 pageaddr;	/* page that contains cyclone_timer register */
	u32 offset;		/* offset from pageaddr to cyclone_timer register */
	int i;
	
	/* check clock override */
	if (override[0] && strncmp(override,"cyclone",7))
			return -ENODEV;

	/*make sure we're on a summit box*/
	if(!use_cyclone) return -ENODEV; 
	
	printk(KERN_INFO "Summit chipset: Starting Cyclone Counter.\n");

	/* find base address */
	pageaddr = (CYCLONE_CBAR_ADDR)&PAGE_MASK;
	offset = (CYCLONE_CBAR_ADDR)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid CBAR register.\n");
		return -ENODEV;
	}
	base = *reg;	
	if(!base){
		printk(KERN_ERR "Summit chipset: Could not find valid CBAR value.\n");
		return -ENODEV;
	}
	
	/* setup PMCC */
	pageaddr = (base + CYCLONE_PMCC_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_PMCC_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid PMCC register.\n");
		return -ENODEV;
	}
	reg[0] = 0x00000001;

	/* setup MPCS */
	pageaddr = (base + CYCLONE_MPCS_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_MPCS_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid MPCS register.\n");
		return -ENODEV;
	}
	reg[0] = 0x00000001;

	/* map in cyclone_timer */
	pageaddr = (base + CYCLONE_MPMC_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_MPMC_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	cyclone_timer = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!cyclone_timer){
		printk(KERN_ERR "Summit chipset: Could not find valid MPMC register.\n");
		return -ENODEV;
	}

	/*quick test to make sure its ticking*/
	for(i=0; i<3; i++){
		u32 old = cyclone_timer[0];
		int stall = 100;
		while(stall--) barrier();
		if(cyclone_timer[0] == old){
			printk(KERN_ERR "Summit chipset: Counter not counting! DISABLED\n");
			cyclone_timer = 0;
			return -ENODEV;
		}
	}

	init_cpu_khz();

	/* Everything looks good! */
	return 0;
}


static void delay_cyclone(unsigned long loops)
{
	unsigned long bclock, now;
	if(!cyclone_timer)
		return;
	bclock = cyclone_timer[0];
	do {
		rep_nop();
		now = cyclone_timer[0];
	} while ((now-bclock) < loops);
}
/************************************************************/

/* cyclone timer_opts struct */
struct timer_opts timer_cyclone = {
	.name = "cyclone",
	.init = init_cyclone, 
	.mark_offset = mark_offset_cyclone, 
	.get_offset = get_offset_cyclone,
	.monotonic_clock =	monotonic_clock_cyclone,
	.delay = delay_cyclone,
};
