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

/*
      Driving setting:  ODT/DQS/DQ/CS/MAA/MAB/DCLK
    */

void DrivingODT(DRAM_SYS_ATTR * DramAttr);

void DrivingDQS(DRAM_SYS_ATTR * DramAttr);

void DrivingDQ(DRAM_SYS_ATTR * DramAttr);

void DrivingCS(DRAM_SYS_ATTR * DramAttr);

void DrivingMA(DRAM_SYS_ATTR * DramAttr);

void DrivingDCLK(DRAM_SYS_ATTR * DramAttr);

/* DRAM Driving Adjustment*/
void DRAMDriving(DRAM_SYS_ATTR * DramAttr)
{
	PRINT_DEBUG_MEM("set ODT!\r");
	DrivingODT(DramAttr);

	PRINT_DEBUG_MEM("set DQS!\r");
	DrivingDQS(DramAttr);

	PRINT_DEBUG_MEM(("set DQ!\r"));
	DrivingDQ(DramAttr);

	PRINT_DEBUG_MEM("set CS!\r");
	DrivingCS(DramAttr);

	PRINT_DEBUG_MEM("set MAA!\r");
	DrivingMA(DramAttr);

	PRINT_DEBUG_MEM("set DCLK!\r");
	DrivingDCLK(DramAttr);
}

/*
ODT	Control	for DQ/DQS/CKE/SCMD/DCLKO in ChA & ChB
which include driving enable/range and strong/weak selection

Processing: According to DRAM frequency to ODT control bits.
      		Because function enable bit must be the last one to be set.
      		So the register VIA_NB3DRAM_REGD4 and VIA_NB3DRAM_REGD3 should be
      		the last register	to be programmed.
*/
//-------------------------------------------------------------------------------
//                      ODT Lookup Table
//-------------------------------------------------------------------------------
#define Rank0_ODT				0
#define Rank1_ODT				1
#define Rank2_ODT				2
#define Rank3_ODT				3
#define NA_ODT					0
#define NB_ODT_75ohm			0
#define NB_ODT_150ohm			1

#define DDR2_ODT_75ohm			0x20
#define DDR2_ODT_150ohm			0x40

// Setting of ODT Lookup TBL
//                      RankMAP , Rank 3               Rank 2              Rank 1              Rank 0           , DRAM & NB ODT setting
//                  db  0000b   , Reserved
#define ODTLookup_Tbl_count 8
static const u8 ODTLookup_TBL[ODTLookup_Tbl_count][3] = {
	// 0001b
	{0x01,
	 (Rank3_ODT << 6) + (Rank2_ODT << 4) + (Rank1_ODT << 2) +
	 Rank0_ODT, DDR2_ODT_150ohm + NB_ODT_75ohm},
	// 0010b        , Reserved
	// 0011b
	{0x03,
	 (Rank3_ODT << 6) + (Rank2_ODT << 4) + (Rank0_ODT << 2) +
	 Rank1_ODT, DDR2_ODT_150ohm + NB_ODT_75ohm},
	// 0100b
	{0x04,
	 (Rank3_ODT << 6) + (Rank2_ODT << 4) + (Rank1_ODT << 2) +
	 Rank0_ODT, DDR2_ODT_150ohm + NB_ODT_75ohm},
	// 0101b
	{0x05,
	 (Rank3_ODT << 6) + (Rank0_ODT << 4) + (Rank1_ODT << 2) +
	 Rank2_ODT, DDR2_ODT_75ohm + NB_ODT_150ohm},
	// 0110b        , Reserved
	// 0111b
	{0x07,
	 (Rank3_ODT << 6) + (Rank0_ODT << 4) + (Rank2_ODT << 2) +
	 Rank2_ODT, DDR2_ODT_75ohm + NB_ODT_150ohm},
	// 1000b        , Reserved
	// 1001b        , Reserved
	// 1010b        , Reserved
	// 1011b        , Reserved
	// 1100b
	{0x0c,
	 (Rank2_ODT << 6) + (Rank3_ODT << 4) + (Rank1_ODT << 2) +
	 Rank0_ODT, DDR2_ODT_150ohm + NB_ODT_75ohm},
	// 1101b
	{0x0d,
	 (Rank0_ODT << 6) + (Rank0_ODT << 4) + (Rank1_ODT << 2) +
	 Rank2_ODT, DDR2_ODT_75ohm + NB_ODT_150ohm},
	// 1110b        , Reserved
	// 1111b
	{0x0f,
	 (Rank0_ODT << 6) + (Rank0_ODT << 4) + (Rank2_ODT << 2) +
	 Rank2_ODT, DDR2_ODT_75ohm + NB_ODT_150ohm}
};

#define ODT_Table_Width_DDR2		4
//                                                                                               RxD6   RxD3
static const u8 ODT_Control_DDR2[ODT_Table_Width_DDR2] = { 0xFC, 0x01 };

void DrivingODT(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u8 i;
	BOOLEAN bFound;

	pci_write_config8(MEMCTRL, 0xD0, 0x88);

	Data = ODT_Control_DDR2[0];
	pci_write_config8(MEMCTRL, 0xd6, Data);

	Data = ODT_Control_DDR2[1];
	pci_write_config8(MEMCTRL, 0xd3, Data);

	Data = pci_read_config8(MEMCTRL, 0x9e);
	//set MD turn_around wait state
	Data &= 0xCF;		/*clear bit4,5 */
	if (DIMMFREQ_400 == DramAttr->DramFreq)
		Data |= 0x0;
	else if (DIMMFREQ_533 == DramAttr->DramFreq)
		Data |= 0x10;
	else if (DIMMFREQ_667 == DramAttr->DramFreq)
		Data |= 0x20;
	else if (DIMMFREQ_800 == DramAttr->DramFreq)
		Data |= 0x20;
	else
		Data |= 0;
	pci_write_config8(MEMCTRL, 0x9e, Data);

	if (DIMMFREQ_400 == DramAttr->DramFreq)
		Data = 0x0;
	else if (DIMMFREQ_533 == DramAttr->DramFreq)
		Data = 0x11;
	else if (DIMMFREQ_667 == DramAttr->DramFreq)
		Data = 0x11;
	else if (DIMMFREQ_800 == DramAttr->DramFreq)
		Data = 0x11;
	else
		Data = 0;
	pci_write_config8(MEMCTRL, 0x9f, Data);

	/*channel A ODT select */
	if (DramAttr->DimmNumChA > 0) {
		Data = pci_read_config8(MEMCTRL, 0xd5);
		Data &= 0x5F;	/*clear bit7,5 */
		if (DramAttr->RankNumChA > 2)
			Data |= 0xA0;	/*if rank number > 2 (3or4), set bit7,5 */
		else
			Data |= 0x00;	/*if rank number is 1or2, clear bit5 */
		pci_write_config8(MEMCTRL, 0xd5, Data);

		Data = pci_read_config8(MEMCTRL, 0xd7);
		Data &= 0xEF;	/*clear bit7 */
		if (DramAttr->RankNumChA > 2)
			Data |= 0x80;	/*if rank number > 2 (3or4), set bit7 */
		else
			Data |= 0x00;	/*if rank number is 1or2,  clear bit7 */
		pci_write_config8(MEMCTRL, 0xd7, Data);

		/*channel A */
		Data = pci_read_config8(MEMCTRL, 0xd5);
		Data &= 0xF3;	//bit2,3
		if (DramAttr->DimmNumChA == 2)	/*2 Dimm, 3or4 Ranks */
			Data |= 0x00;
		else if (DramAttr->DimmNumChA == 1)
			Data |= 0x04;
		pci_write_config8(MEMCTRL, 0xd5, Data);

		if ((DramAttr->RankPresentMap & 0x0F) != 0) {	/*channel A */
			// MAA ODT Lookup Table
			bFound = FALSE;
			for (i = 0; i < ODTLookup_Tbl_count; i++) {
				if ((DramAttr->RankPresentMap & 0x0F) ==
				    ODTLookup_TBL[i][0]) {
					Data = ODTLookup_TBL[i][1];
					bFound = TRUE;
				}
			}
			if (!bFound) {	/*set default value */
				Data =
				    ODTLookup_TBL[ODTLookup_Tbl_count - 1][1];
			}
			pci_write_config8(MEMCTRL, 0x9c, Data);

			//set CHA MD ODT control State Dynamic-on
			Data = pci_read_config8(MEMCTRL, 0xD4);
			Data &= 0xC9;
			Data |= 0x30;
			pci_write_config8(MEMCTRL, 0xD4, Data);

			Data = pci_read_config8(MEMCTRL, 0x9e);
			Data |= 0x01;
			pci_write_config8(MEMCTRL, 0x9e, Data);
		}

	}
	/*channel B */
	if (1 == ENABLE_CHC) {
		//CHB has not auto compensation mode ,so must set it manual,or else CHB initialization will not successful

		Data = pci_read_config8(MEMCTRL, 0xd5);
		Data &= 0xAF;
		if (DramAttr->RankNumChB > 2)	/*rank number 3 or 4 */
			Data |= 0x50;
		else
			Data |= 0x00;
		pci_write_config8(MEMCTRL, 0xd5, Data);

		Data = pci_read_config8(MEMCTRL, 0xd7);
		Data &= 0xBF;	/*clear bit6 */
		if (DramAttr->RankNumChB > 2)
			Data |= 0x40;	/*if rank number > 2 (3or4), set bit7 */
		else
			Data |= 0x00;	/*if rank number is 1or2,  clear bit7 */
		pci_write_config8(MEMCTRL, 0xd7, Data);

		Data = pci_read_config8(MEMCTRL, 0xd5);
		Data &= 0xFC;
		if (DramAttr->DimmNumChB == 2)	/*2 Dimm, 3or4 Ranks */
			Data |= 0x00;	// 2 dimm RxD5[2,0]=0,0b
		else if (DramAttr->DimmNumChB == 1)
			Data |= 0x01;	// 1 dimm RxD5[2,0]=1,1b
		pci_write_config8(MEMCTRL, 0xd5, Data);

		//set CHB MD ODT control State Dynamic-on
		Data = pci_read_config8(MEMCTRL, 0xD4);
		Data &= 0xF6;
		Data |= 0x08;
		pci_write_config8(MEMCTRL, 0xD4, Data);

		//enable CHB differential DQS input
		Data = pci_read_config8(MEMCTRL, 0x9E);
		Data |= 0x02;
		pci_write_config8(MEMCTRL, 0x9E, Data);
	}
	//enable ODT Control
	Data = pci_read_config8(MEMCTRL, 0x9e);
	Data |= 0x80;
	pci_write_config8(MEMCTRL, 0x9e, Data);
}

void DrivingDQS(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;

	/*channel A */
	if (DramAttr->RankNumChA > 0) {
		Data = DDR2_DQSA_Driving_Table[DramAttr->RankNumChA - 1];
		pci_write_config8(MEMCTRL, 0xe0, Data);
	}

	/*channel B */
	if (1 == ENABLE_CHC) {
		Data = DDR2_DQSB_Driving_Table[DramAttr->RankNumChB - 1];
		pci_write_config8(MEMCTRL, 0xe1, Data);
	}

}

void DrivingDQ(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;

	/*channel A */
	if (DramAttr->RankNumChA > 0) {
		Data = DDR2_DQA_Driving_Table[DramAttr->RankNumChA - 1];
		pci_write_config8(MEMCTRL, 0xe2, Data);

	}
	/*channel B */
	if (1 == ENABLE_CHC) {
		Data = DDR2_DQB_Driving_Table[DramAttr->RankNumChB - 1];
		pci_write_config8(MEMCTRL, 0xe3, Data);
	}
}

void DrivingCS(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	/*Channel A */
	if (DramAttr->RankNumChA > 0) {
		Data = DDR2_CSA_Driving_Table_x8[DramAttr->RankNumChA - 1];
		pci_write_config8(MEMCTRL, 0xe4, Data);
	}
	/*channel B */
	if (1 == ENABLE_CHC) {
		Data = DDR2_CSB_Driving_Table_x8[DramAttr->RankNumChB - 1];
		pci_write_config8(MEMCTRL, 0xe5, Data);
	}
}

void DrivingMA(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u8 i, FreqId;

	if (DramAttr->RankNumChA > 0) {
		if (DIMMFREQ_400 == DramAttr->DramFreq)
			FreqId = 1;
		else if (DIMMFREQ_533 == DramAttr->DramFreq)
			FreqId = 2;
		else if (DIMMFREQ_667 == DramAttr->DramFreq)
			FreqId = 3;
		else if (DIMMFREQ_800 == DramAttr->DramFreq)
			FreqId = 4;
		else
			FreqId = 1;
		for (i = 0; i < MA_Table; i++) {
			if (DramAttr->LoadNumChA <=
			    DDR2_MAA_Driving_Table[i][0]) {
				Data = DDR2_MAA_Driving_Table[i][FreqId];
				break;
			}
		}
		pci_write_config8(MEMCTRL, 0xe8, Data);
	}
	if (1 == ENABLE_CHC) {
		for (i = 0; i < MA_Table; i++) {
			if (DramAttr->LoadNumChA <=
			    DDR2_MAB_Driving_Table[i][0]) {
				Data = DDR2_MAB_Driving_Table[i][1];
				break;
			}
		}
		pci_write_config8(MEMCTRL, 0xe9, Data);
	}
}

void DrivingDCLK(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data;
	u8 FreqId;

	if (DIMMFREQ_400 == DramAttr->DramFreq)
		FreqId = 0;
	else if (DIMMFREQ_533 == DramAttr->DramFreq)
		FreqId = 1;
	else if (DIMMFREQ_667 == DramAttr->DramFreq)
		FreqId = 2;
	else if (DIMMFREQ_800 == DramAttr->DramFreq)
		FreqId = 3;
	else
		FreqId = 0;

	/*channel A */
	if (DramAttr->RankNumChA > 0) {
		Data = DDR2_DCLKA_Driving_Table[FreqId];
		pci_write_config8(MEMCTRL, 0xe6, Data);
	}
	/*channel B */
	if (1 == ENABLE_CHC) {
		Data = DDR2_DCLKB_Driving_Table[FreqId];
		pci_write_config8(MEMCTRL, 0xe7, Data);
	}

}