/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2007 Advanced Micro Devices, Inc.
 * Copyright (C) 2010 Nils Jacobs
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

#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 */

/* spll_raw_clk = SYSREF * FbDIV,
 * GLIU Clock   = spll_raw_clk / MDIV
 * CPU Clock    = spll_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
};

/*	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 */

/* 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

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

	if (CONFIG_GX2_PROCESSOR_MHZ == 400) {
		DEFAULT_FBDIV = 24;
	} else if (CONFIG_GX2_PROCESSOR_MHZ == 366) {
		DEFAULT_FBDIV = 22;
	} else if (CONFIG_GX2_PROCESSOR_MHZ == 300) {
		DEFAULT_FBDIV = 18;
	} else {
		post_code(POST_PLL_CPU_VER_FAIL);
		die("Unsupported GX2_PROCESSOR_MHZ setting!\n");
	}

	/* 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);
		die("CONFIGURING PLL FAILURE\n");

	} /* we haven't configured the PLL; do it now */

}

static unsigned int GeodeLinkSpeed(void)
{
	unsigned geodelinkspeed;
	geodelinkspeed = ((CONFIG_GX2_PROCESSOR_MHZ * DEFAULT_VDIV) / DEFAULT_MDIV);
	return (geodelinkspeed);
}
