#include <cpu/x86/tsc.h>

#define CLOCK_TICK_RATE	1193180U /* Underlying HZ */
#define CALIBRATE_INTERVAL ((20*CLOCK_TICK_RATE)/1000) /* 20ms */
#define CALIBRATE_DIVISOR  (20*1000) /* 20ms / 20000 == 1usec */

static unsigned int calibrate_tsc(void)
{
	/* Set the Gate high, disable speaker */
	outb((inb(0x61) & ~0x02) | 0x01, 0x61);

	/*
	 * Now let's take care of CTC channel 2
	 *
	 * Set the Gate high, program CTC channel 2 for mode 0,
	 * (interrupt on terminal count mode), binary count,
	 * load 5 * LATCH count, (LSB and MSB) to begin countdown.
	 */
	outb(0xb0, 0x43);			/* binary, mode 0, LSB/MSB, Ch 2 */
	outb(CALIBRATE_INTERVAL	& 0xff, 0x42);	/* LSB of count */
	outb(CALIBRATE_INTERVAL	>> 8, 0x42);	/* MSB of count */

	{
		tsc_t start;
		tsc_t end;
		unsigned long count;

		start = rdtsc();
		count = 0;
		do {
			count++;
		} while ((inb(0x61) & 0x20) == 0);
		end = rdtsc();

		/* Error: ECTCNEVERSET */
		if (count <= 1)
			goto bad_ctc;

		/* 64-bit subtract - gcc just messes up with long longs */
		__asm__("subl %2,%0\n\t"
			"sbbl %3,%1"
			:"=a" (end.lo), "=d" (end.hi)
			:"g" (start.lo), "g" (start.hi),
			 "0" (end.lo), "1" (end.hi));

		/* Error: ECPUTOOFAST */
		if (end.hi)
			goto bad_ctc;


		/* Error: ECPUTOOSLOW */
		if (end.lo <= CALIBRATE_DIVISOR)
			goto bad_ctc;

		return (end.lo + CALIBRATE_DIVISOR -1)/CALIBRATE_DIVISOR;
	}

	/*
	 * The CTC wasn't reliable: we got a hit on the very first read,
	 * or the CPU was so fast/slow that the quotient wouldn't fit in
	 * 32 bits..
	 */
bad_ctc:
	print_err("bad_ctc\n");
	return 0;
}

/* spll_raw_clk = SYSREF * FbDIV,
 * GLIU Clock   = spll_raw_clk / MDIV
 * CPU Clock    = sppl_raw_clk / VDIV
 */

/* table for Feedback divisor to FbDiv register value */
static const unsigned char plldiv2fbdiv[] = {
	 0,  0,  0,  0,  0,  0, 15,  7,  3,  1,  0, 32, 16, 40, 20, 42, /* pll div  0 - 15 */
	21, 10, 37, 50, 25, 12, 38, 19,  9,  4, 34, 17,  8, 36, 18, 41, /* pll div 16 - 31 */
	52, 26, 45, 54, 27, 13,  6, 35, 49, 56, 28, 46, 23, 11, 05, 02, /* pll div 32 - 47 */
	33, 48, 24, 44, 22, 43, 53, 58, 29, 14, 39, 51, 57, 60, 30, 47, /* pll div 48 - 63 */
};

/* table for FbDiv register value to Feedback divisor */
static const unsigned char fbdiv2plldiv[] = {
	10,  9, 47,  8, 25, 46, 38,  7, 28, 24, 17, 45, 21, 37, 57,  6,
	12, 27, 30, 23, 14, 16, 52, 44, 50, 20, 33, 36, 42, 56,  0,  0,
	11, 48, 26, 39, 29, 18, 22, 58, 13, 31, 15, 53, 51, 34, 43,  0,
	49, 40, 19, 59, 32, 54, 35,  0, 41, 60, 55,  0, 61,  0,  0,  0
};

static const unsigned char pci33_ddr_crt [] = {
	/* FbDIV, VDIV, MDIV		   CPU/GeodeLink */
	     12,    2,    3,		// 200/133
	     16,    2,    3,		// 266/177
             18,    2,    3,		// 300/200
             20,    2,    3,		// 333/222
             22,    2,    3,		// 366/244
             24,    2,    3,		// 400/266
             26,    2,    3             // 433/289
};

#if 0
static unsigned int get_memory_speed(void)
{
	unsigned char val, hi, lo;

	val = spd_read_byte(0xA0, 9);
	hi = (val >> 4) & 0x0f;
	lo = val & 0x0f;

	return 20000/(hi*10 + lo);
}
#endif

#define USE_GOODRICH_VERSION 1

#if USE_GOODRICH_VERSION
///////////////////////////////////////////////////////////////////////////////
// Goodrich Version of pll_reset
#define POST_CODE(x) outb(x, 0x80)

// PLLCHECK_COMPLETED is the "we've already done this" flag
#define PLLCHECK_COMPLETED (1 << RSTPLL_LOWER_SWFLAGS_SHIFT)

#ifndef RSTPPL_LOWER_BYPASS_SET
#define RSTPPL_LOWER_BYPASS_SET (1 << GLCP_SYS_RSTPLL_BYPASS)
#endif // RSTPPL_LOWER_BYPASS_SET

#define DEFAULT_MDIV	3
#define DEFAULT_VDIV	2
#define DEFAULT_FBDIV	22	// 366/244 ; 24 400/266 018 ;300/200 

static void pll_reset(void)
{
	msr_t msrGlcpSysRstpll;
	unsigned MDIV_VDIV_FBDIV;
	unsigned SyncBits;			// store the sync bits in up ebx

	// clear the Bypass bit
	
	// If the straps say we are in bypass and the syspll is not AND there are no software
	// bits set then FS2 or something set up the PLL and we should not change it.
	
	msrGlcpSysRstpll = rdmsr(GLCP_SYS_RSTPLL);
	msrGlcpSysRstpll.lo &= ~RSTPPL_LOWER_BYPASS_SET;
	wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);

	// If the "we've already been here" flag is set, don't reconfigure the pll
	if ( !(msrGlcpSysRstpll.lo & PLLCHECK_COMPLETED ) )
	{ // we haven't configured the PLL; do it now
	
		// Store PCI33(0)/66(1), SDR(0)/DDR(1), and CRT(0)/TFT(1) in upper esi to get to the
		// correct Strap Table.
		POST_CODE(POST_PLL_INIT);
		
		// configure for DDR
		msrGlcpSysRstpll.lo &= ~(1 << RSTPPL_LOWER_SDRMODE_SHIFT);
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);
		
		// Use Manual settings
		//	UseManual:
		POST_CODE(POST_PLL_MANUAL);

		// DIV settings manually entered.
		// ax = VDIV, upper eax = MDIV, upper ecx = FbDIV
		// use gs and fs since we don't need them.
		
		//	ProgramClocks:
		// ax = VDIV, upper eax = MDIV, upper ecx = FbDIV
		// move everything into ebx
		// VDIV
		MDIV_VDIV_FBDIV = ((DEFAULT_VDIV - 2) << RSTPLL_UPPER_VDIV_SHIFT);

		// MDIV
		MDIV_VDIV_FBDIV |= ((DEFAULT_MDIV - 2) << RSTPLL_UPPER_MDIV_SHIFT);

		// FbDIV
		MDIV_VDIV_FBDIV |= (plldiv2fbdiv[DEFAULT_FBDIV] << RSTPLL_UPPER_FBDIV_SHIFT);

		// write GLCP_SYS_RSTPPL (GLCP reg 0x14) with clock values  
		msrGlcpSysRstpll.lo &= ~(1 << RSTPPL_LOWER_SDRMODE_SHIFT);
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);

		msrGlcpSysRstpll.hi = MDIV_VDIV_FBDIV;
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);

		// Set Reset, LockWait, and SW flag
		//	DoReset:

		// CheckSemiSync proc
		// Check for Semi-Sync in GeodeLink and CPU.
		// We need to do this here since the strap settings don't account for these bits.
		SyncBits = 0;			// store the sync bits in up ebx

		// Check for Bypass mode.
		if (msrGlcpSysRstpll.lo & RSTPPL_LOWER_BYPASS_SET)
		{
			// If we are in BYPASS PCI may or may not be sync'd but CPU and GeodeLink will.	
			SyncBits |= RSTPPL_LOWER_CPU_SEMI_SYNC_SET;
		}
		else
		{
			//	CheckPCIsync:	
			// If FBdiv/Mdiv is evenly divisible then set the PCI semi-sync. FB is always greater
			// look up the real divider... if we get a 0 we have serious problems
			if ( !(fbdiv2plldiv[((msrGlcpSysRstpll.hi >> RSTPLL_UPPER_FBDIV_SHIFT) & 0x3f)] % 
				(((msrGlcpSysRstpll.hi >> RSTPLL_UPPER_MDIV_SHIFT) & 0x0F) + 2)) )
			{
				SyncBits |= RSTPPL_LOWER_PCI_SEMI_SYNC_SET;
			}

			//	CheckCPUSync:
			// If Vdiv/Mdiv is evenly divisible then set the CPU semi-sync.
			// CPU is always greater or equal.
			if (!((((msrGlcpSysRstpll.hi >> RSTPLL_UPPER_MDIV_SHIFT) & 0x07) + 2) %
				(((msrGlcpSysRstpll.hi >> RSTPLL_UPPER_VDIV_SHIFT) & 0x0F) + 2)))
			{
				SyncBits |= RSTPPL_LOWER_CPU_SEMI_SYNC_SET;
			}
		}


		//	SetSync:
		msrGlcpSysRstpll.lo &= ~(RSTPPL_LOWER_PCI_SEMI_SYNC_SET | RSTPPL_LOWER_CPU_SEMI_SYNC_SET);
		msrGlcpSysRstpll.lo |= SyncBits;
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);
		// CheckSemiSync endp

		// now we do the reset
		// Set hold count to 99 (063h)
		msrGlcpSysRstpll.lo &= ~(0x0FF << RSTPPL_LOWER_HOLD_COUNT_SHIFT);
		msrGlcpSysRstpll.lo |=  (0x0DE << RSTPPL_LOWER_HOLD_COUNT_SHIFT);
		msrGlcpSysRstpll.lo |=  PLLCHECK_COMPLETED;		// Say we are done
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);

		// Don't want to use LOCKWAIT
		msrGlcpSysRstpll.lo |= (RSTPPL_LOWER_PLL_RESET_SET + RSTPPL_LOWER_PD_SET);
		msrGlcpSysRstpll.lo |= RSTPPL_LOWER_CHIP_RESET_SET;
		wrmsr(GLCP_SYS_RSTPLL, msrGlcpSysRstpll);
		
		// You should never get here..... The chip has reset.
		POST_CODE(POST_PLL_RESET_FAIL);
		while (1);

	} // we haven't configured the PLL; do it now
	
}
// End of Goodrich version of pll_reset
///////////////////////////////////////////////////////////////////////////////

#else // #if USE_GOODRICH_VERSION

static void pll_reset(void)
{
	msr_t msr;
	unsigned int sysref, spll_raw, cpu_core, gliu;
	unsigned mdiv, vdiv, fbdiv;

	/* get CPU core clock in MHZ */
	cpu_core = calibrate_tsc();
	print_debug("Cpu core is ");
	print_debug_hex32(cpu_core);
	print_debug("\n");

	msr = rdmsr(GLCP_SYS_RSTPLL);
	if (msr.lo & (1 << GLCP_SYS_RSTPLL_BYPASS)) {
#if 0
		print_debug("MSR ");
		print_debug_hex32(GLCP_SYS_RSTPLL);
		print_debug("is ");
		print_debug_hex32(msr.hi);
		print_debug(":");
		print_debug_hex32(msr.lo);

		msr.hi = PLLMSRhi;
		msr.lo = PLLMSRlo;
		wrmsr(GLCP_SYS_RSTPLL, msr);
		msr.lo |= PLLMSRlo1;
		wrmsr(GLCP_SYS_RSTPLL, msr);

		print_debug("Reset PLL\n\r");

		msr.lo |= PLLMSRlo2;
		wrmsr(GLCP_SYS_RSTPLL,msr);
		print_debug("should not be here\n\r");
#endif
		print_err("shit");
		while (1)
			;
	}

	if (msr.lo & GLCP_SYS_RSTPLL_SWFLAGS_MASK) {
		/* PLL is already set and we are reboot from PLL reset */
		print_debug("reboot from BIOS reset\n\r");
		return;
	}

	/* get the sysref clock rate */
	vdiv  = (msr.hi >> GLCP_SYS_RSTPLL_VDIV_SHIFT) & 0x07;
	vdiv += 2;
	fbdiv = (msr.hi >> GLCP_SYS_RSTPLL_FBDIV_SHIFT) & 0x3f;
	fbdiv = fbdiv2plldiv[fbdiv];
	spll_raw = cpu_core * vdiv;
	sysref   = spll_raw / fbdiv;

	/* get target memory rate by SPD */
	//gliu = get_memory_speed();

	msr.hi = 0x00000019;
	msr.lo = 0x06de0378;
	wrmsr(0x4c000014, msr);
	msr.lo |= ((0xde << 16) | (1 << 26) | (1 << 24));
	wrmsr(0x4c000014, msr);

	print_debug("Reset PLL\n\r");

	msr.lo |= ((1<<14) |(1<<13) | (1<<0));
	wrmsr(0x4c000014,msr);

	print_debug("should not be here\n\r");
}
#endif // #if USE_GOODRICH_VERSION
