/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2009 One Laptop per Child, Association, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

 /*
    Set Dram Timing functions
  */

void SetCL(DRAM_SYS_ATTR * DramAttr);

void SetTrp(DRAM_SYS_ATTR * DramAttr);

void SetTrcd(DRAM_SYS_ATTR * DramAttr);

void SetTras(DRAM_SYS_ATTR * DramAttr);

void SetTrfc(DRAM_SYS_ATTR * DramAttr);

void SetTrrd(DRAM_SYS_ATTR * DramAttr);

void SetTwr(DRAM_SYS_ATTR * DramAttr);

void SetTwtr(DRAM_SYS_ATTR * DramAttr);

void SetTrtp(DRAM_SYS_ATTR * DramAttr);

/* Set DRAM	Timing*/
void DRAMTimingSetting(DRAM_SYS_ATTR * DramAttr)
{
	PRINT_DEBUG_MEM("Set CAS latency value!");
	SetCL(DramAttr);

	PRINT_DEBUG_MEM("Set tRP value!");
	SetTrp(DramAttr);

	PRINT_DEBUG_MEM("Set tRCD value!");
	SetTrcd(DramAttr);

	PRINT_DEBUG_MEM("Set tRAS value!");
	SetTras(DramAttr);

	PRINT_DEBUG_MEM("Set tRFC value!");
	SetTrfc(DramAttr);

	PRINT_DEBUG_MEM("Set tRRD value!");
	SetTrrd(DramAttr);

	PRINT_DEBUG_MEM("Set tWR value!");
	SetTwr(DramAttr);

	PRINT_DEBUG_MEM("Set tWTR value!");
	SetTwtr(DramAttr);

	PRINT_DEBUG_MEM("Set tRTP value!");
	SetTrtp(DramAttr);
}

/*
Set DRAM Timing: CAS Latency for DDR1
D0F3RX62 bit[0:2] for CAS Latency;
*/
void SetCL(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u8 CL;

	/*DDR2 CL Value: 20, 30, 40, 50 -> 2, 3, 4, 5 */
	CL = (u8) ((DramAttr->CL - 20) / 10);	//000,001,010,011

	PRINT_DEBUG_MEM("CAS = ");
	PRINT_DEBUG_MEM_HEX8(CL);
	PRINT_DEBUG_MEM("\n");
	Data = pci_read_config8(MEMCTRL, 0x62);
	Data = (u8) ((Data & 0xf8) | CL);
	pci_write_config8(MEMCTRL, 0x62, Data);
}

/*
 Minimum row precharge time, Trp for DDR1/DDR2
 D0F3Rx64[3:2] for Trp 2T~5T
*/
#define MAX_TRP 6
#define MIN_TRP 2

void SetTrp(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trp value from SPD data
	   SPD Byte27, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRP]);
			if (Tmp > Max)
				Max = Tmp;
		}
		/*Calculate clock,this value should be 2T,3T,4T,5T */
	}
	Tmp =
	    (u16) CEIL_DIV(Max * 100, (DramAttr->DramCyc) << 2);
	PRINT_DEBUG_MEM("Trp = ");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TRP)
		Tmp = MAX_TRP;
	else if (Tmp < MIN_TRP)
		Tmp = MIN_TRP;

	Tmp -= 2;		//00->2T, 01->3T, 10->4T, 11->5T
	Tmp <<= 1;		//bit1,2,3

	Data = pci_read_config8(MEMCTRL, 0x64);
	Data = (u8) ((Data & 0xf1) | (u8) Tmp);
	pci_write_config8(MEMCTRL, 0x64, Data);

	//enable DDR2 8-Bank Device Timing Constraint
	Data = pci_read_config8(MEMCTRL, 0x62);
	Data = (u8) ((Data & 0xf7) | 0x08);
	pci_write_config8(MEMCTRL, 0x62, Data);
}

/*
Minimum RAS to CAS dely,Trcd for DDR1/DDR2
D0F3Rx64[7:6] for Trcd
*/
#define MAX_TRCD 6
#define MIN_TRCD 2

void SetTrcd(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trcd value from SPD data
	   SPD Byte29, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRCD]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}
	/*Calculate clock,this value should be 2T,3T,4T,5T */
	Tmp =
	    (u16) CEIL_DIV(Max * 100, (DramAttr->DramCyc) << 2);
	PRINT_DEBUG_MEM("Trcd =");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TRCD)
		Tmp = MAX_TRCD;
	else if (Tmp < MIN_TRCD)
		Tmp = MIN_TRCD;
	Tmp -= 2;		//00->2T, 01->3T, 10->4T, 11->5T
	Tmp <<= 5;		//bit5,6,7

	Data = pci_read_config8(MEMCTRL, 0x64);
	Data = (u8) ((Data & 0x1f) | (u8) Tmp);
	pci_write_config8(MEMCTRL, 0x64, Data);

}

/*
 minimum active to precharge time,Tras for DDR1/DDR2
 D0F3Rx62[7:4] Tras
*/
#define MAX_TRAS 20		//20T
#define MIN_TRAS 5		//5T
void SetTras(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Tras value from SPD data
	   SPD byte30: bit0:7 1ns~255ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRAS]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}

	/*Calculate clock,value range 5T-20T */
	Tmp = (u16) CEIL_DIV((Max * 100), DramAttr->DramCyc);
	PRINT_DEBUG_MEM("Tras =");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TRAS)
		Tmp = MAX_TRAS;
	else if (Tmp < MIN_TRAS)
		Tmp = MIN_TRAS;
	Tmp -= 5;		//0->5T  ... 1111->20T
	Tmp <<= 4;		//bit4:7

	Data = pci_read_config8(MEMCTRL, 0x62);
	Data = (u8) ((Data & 0x0f) | (u8) Tmp);
	pci_write_config8(MEMCTRL, 0x62, Data);
}

/*
Minimum refresh to activate/refresh command period Trfc for DDR1/DDR2
D0F3Rx61[5:0] for Trfc
*/

#define MAX_TRFC 71		// Max supported,71T
#define MIN_TRFC 8		// Min supported,8T

void SetTrfc(DRAM_SYS_ATTR * DramAttr)
{

	u8 Data;
	u32 Max, Tmp;
	u8 Byte40;
	u8 Socket;

	/*get the max Trfc value from SPD data */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u32) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRFC])
			    * 100;
			/*only DDR2 need to add byte 40 bit[7:4] */
			Byte40 =
			    (DramAttr->
			     DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRFC2]);
			/*if bit0 = 1, byte42(RFC)+256ns, SPD spec JEDEC standard No.21.c */
			if (Byte40 & 0x01)
				Tmp += (256 * 100);
			/*bit1,2,3 000->0ns+byte42; 001->0.25ns+byte42; 010->0.33ns+byte42; 011->0.5ns+byte42;100-> 0.75ns+byte42 */
			switch ((Byte40 >> 1) & 0x07) {	/*bit1,2,3 */
			case 1:
				Tmp += 25;
				break;
			case 2:
				Tmp += 33;
				break;
			case 3:
				Tmp += 50;
				break;
			case 4:
				Tmp += 66;
				break;
			case 5:
				Tmp += 75;
				break;
			case 6:	//what is FRU???
			default:
				break;
			}
			if (Tmp > Max)
				Max = Tmp;
		}
	}

	/*Calculate clock,value range 8T-71T */
	Tmp = (u16) CEIL_DIV(Max, DramAttr->DramCyc);
	PRINT_DEBUG_MEM("Trfc = ");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");
	if (Tmp > MAX_TRFC)
		Tmp = MAX_TRFC;
	else if (Tmp < MIN_TRFC) {
		// return;
		Tmp = 0x40;
	}
	/*D0F3Rx61 bit[0:5] 0->8T ... 63->71T */
	Tmp -= 8;

	Data = pci_read_config8(MEMCTRL, 0x61);
	Data = (u8) ((Data & 0xc0) | ((u8) Tmp & 0x3f));
	pci_write_config8(MEMCTRL, 0x61, Data);
}

/*
Minimum row active to row active delay: Trrd for DDR1/DDR2
D0F3Rx61[7:6]:Trrd  00->2T, 01->3T, 10->4T, 11->5T
*/
#define MAX_TRRD 5
#define MIN_TRRD 2

void SetTrrd(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trrd value from SPD data
	   SPD Byte28, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRRD]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}

	/*Calculate clock,this value should be 2T,3T,4T,5T */
	Tmp =
	    (u16) CEIL_DIV(Max * 100, (DramAttr->DramCyc) << 2);
	PRINT_DEBUG_MEM("Trrd =");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TRRD)
		Tmp = MAX_TRRD;
	else if (Tmp < MIN_TRRD)
		Tmp = MIN_TRRD;
	Tmp -= 2;		//00->2T, 01->3T, 10->4T, 11->5T
	Tmp <<= 6;

	Data = pci_read_config8(MEMCTRL, 0x61);
	Data = (u8) ((Data & 0x3f) | (u8) Tmp);
	pci_write_config8(MEMCTRL, 0x61, Data);
}

/*
Write recovery time: Twr for DDR1/DDR2
Device 0 Function 3:REG63[7:5]:Twr 00->2T 01->3T 10->4T 11->5T
*/
#define MAX_TWR 6
#define MIN_TWR 2

void SetTwr(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trtp value from SPD data
	   SPD Byte36, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TWR]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}
	/*Calculate clock */
	Tmp = (u16) CEIL_DIV((Max * 100), ((DramAttr->DramCyc) << 2));	//this value should be 2T,3T,4T,5T
	PRINT_DEBUG_MEM("Twr = ");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TWR)
		Tmp = MAX_TWR;
	else if (Tmp < MIN_TWR)
		Tmp = MIN_TWR;
	Tmp -= 2;		//00->2T, 01->3T, 10->4T, 11->5T
	Tmp <<= 5;

	Data = pci_read_config8(MEMCTRL, 0x63);
	Data = (u8) ((Data & 0x1f) | (u8) Tmp);
	pci_write_config8(MEMCTRL, 0x63, Data);
}

/*
Internal write to read command delay: Twtr for DDR1/DDR2
Device 0 Function 3:REG63[1,0]:Twtr   DDR: 1T or 2T; DDR2 2T or 3T
*/
#define MAX_TWTR 5		//5T
#define MIN_TWTR 2		//2T

void SetTwtr(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trtp value from SPD data
	   SPD Byte37, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TWTR]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}
	/*Calculate clock */
	Tmp = (u16) CEIL_DIV((Max * 100), ((DramAttr->DramCyc) << 2));	//this value should be 2T or 3T

	PRINT_DEBUG_MEM("Twtr =");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	if (Tmp > MAX_TWR)
		Tmp = MAX_TWTR;
	else if (Tmp < MIN_TWR)
		Tmp = MIN_TWTR;
	Tmp -= 2;		//00->2T, 01->3T, 10->4T, 11->5T
	Data = pci_read_config8(MEMCTRL, 0x63);
	Data = (u8) ((Data & 0xFC) | Tmp);
	pci_write_config8(MEMCTRL, 0x63, Data);
}

/*
Internal read to precharge command delay, Trtp for DDR1/DDR2
Device 0 Function 3:REG63[3]:Trtp  2T or 3T
*/
#define MAX_TRTP 3		//3T
#define MIN_TRTP 2		//2T

void SetTrtp(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u16 Max, Tmp;
	u8 Socket;

	/*get the max Trtp value from SPD data
	   SPD Byte38, Bit7:2->1ns~63ns, Bit1:0->0ns, 0.25ns, 0.50ns, 0.75ns */
	Max = 0;
	for (Socket = 0; Socket < MAX_SOCKETS; Socket++) {
		if (DramAttr->DimmInfo[Socket].bPresence) {
			Tmp =
			    (u16) (DramAttr->
				   DimmInfo[Socket].SPDDataBuf[SPD_SDRAM_TRTP]);
			if (Tmp > Max)
				Max = Tmp;
		}
	}
	/*Calculate clock */
	Tmp = (u16) CEIL_DIV((Max * 100), ((DramAttr->DramCyc) << 2));	//this value should be 2T or 3T

	PRINT_DEBUG_MEM("Trtp =");
	PRINT_DEBUG_MEM_HEX16(Tmp);
	PRINT_DEBUG_MEM("\r");

	Data = pci_read_config8(MEMCTRL, 0x63);
	if (Tmp > MIN_TRTP)
		Data = (u8) (Data | 0x08);	/*set bit3, set 3T */
	else
		Data = (u8) (Data & 0xf7);	/*clear bit3, set 2T */

	pci_write_config8(MEMCTRL, 0x63, Data);
}
