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

void DRAMClearEndingAddress(DRAM_SYS_ATTR * DramAttr);

void DRAMSizingEachRank(DRAM_SYS_ATTR * DramAttr);

BOOLEAN DoDynamicSizing1XM(DRAM_SYS_ATTR * DramAttr,
			   u8 * nRA, u8 * nCA, u8 * nBS, u8 PhyRank);

void DRAMSetRankMAType(DRAM_SYS_ATTR * DramAttr);

void DRAMSetEndingAddress(DRAM_SYS_ATTR * DramAttr);

void DRAMPRToVRMapping(DRAM_SYS_ATTR * DramAttr);

/*===================================================================
Function   : DRAMBankInterleave()
Precondition :
Input        :
		   DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     :  Void
Purpose   :  STEP 13 Set Bank Interleave  VIANB3DRAMREG69[7:6] 00:No Interleave 01:2 Bank 10:4 Bank	11:8 Bank
                   Scan all DIMMs on board to find out the lowest Bank Interleave among these DIMMs and set register.
===================================================================*/
void DRAMBankInterleave(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data, SpdBAData;
	DIMM_INFO *CurrentDimminfo;
	u8 Bank = 3, Shift, RankNO, Count;
	Shift = 1;
	for (RankNO = 0; RankNO < 4; RankNO += 2)	//all_even  0 RankNO 4 6
	{
		if ((DramAttr->RankPresentMap & Shift) != 0) {
			CurrentDimminfo = &(DramAttr->DimmInfo[RankNO >> 1]);	//this Rank in a dimm
			SpdBAData =
			    (u8) (CurrentDimminfo->SPDDataBuf
				  [SPD_SDRAM_NO_OF_BANKS]);
			if (SpdBAData == 4)
				Count = 2;
			else if (SpdBAData == 8)
				Count = 3;
			else
				Count = 0;
			if (Count < Bank)
				Bank = Count;
		}
		Shift <<= 2;
	}

	Data = pci_read_config8(MEMCTRL, 0x69);
	Data &= ~0xc0;
	Data |= (Bank << 6);
	pci_write_config8(MEMCTRL, 0x69, Data);

	if (DramAttr->DimmNumChB > 0) {
		CurrentDimminfo = &(DramAttr->DimmInfo[3]);	//this Rank in a dimm
		SpdBAData =
		    (u8) (CurrentDimminfo->SPDDataBuf[SPD_SDRAM_NO_OF_BANKS]);
		if (SpdBAData == 4)
			Bank = 2;
		else if (SpdBAData == 2)
			Bank = 1;
		else
			Bank = 0;
		pci_write_config8(MEMCTRL, 0x87, Bank);
	}
}

/*===================================================================
Function   : DRAMSizingMATypeM()
Precondition :
Input        :
		   DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     :  Void
 Purpose  : STEP 14  1 DRAM Sizing 2  Fill MA type 3 Prank to vrankMapping
===================================================================*/
void DRAMSizingMATypeM(DRAM_SYS_ATTR * DramAttr)
{
	DRAMClearEndingAddress(DramAttr);
	DRAMSizingEachRank(DramAttr);
	DRAMSetRankMAType(DramAttr);
	DRAMSetEndingAddress(DramAttr);
	DRAMPRToVRMapping(DramAttr);
}

/*===================================================================
Function   : DRAMClearEndingAddress()
Precondition :
Input        :
		   DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     : Void
Purpose   : clear Ending and Start adress from 0x40-4f to zero
===================================================================*/
void DRAMClearEndingAddress(DRAM_SYS_ATTR * DramAttr)
{
	u8 Data, Reg;
	Data = 0;
	for (Reg = 0x40; Reg <= 0x4f; Reg++) {
		pci_write_config8(MEMCTRL, Reg, Data);
	}
}

/*===================================================================
Function   : DRAMSizingEachRank()
Precondition :
Input        :
		   DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     : Void
Purpose   : Sizing each Rank invidually, by number of rows column banks pins, be care about 128bit
===================================================================*/
void DRAMSizingEachRank(DRAM_SYS_ATTR * DramAttr)
{
	u8 Slot, RankIndex, Rows, Columns, Banks;
	u32 Size;
	BOOLEAN HasThreeBitBA;
	u8 Data;

	HasThreeBitBA = FALSE;
	for (Slot = 0; Slot < 2; Slot++) {
		if (!DramAttr->DimmInfo[Slot].bPresence)
			continue;
		Rows = DramAttr->DimmInfo[Slot].SPDDataBuf[SPD_SDRAM_ROW_ADDR];
		Columns =
		    DramAttr->DimmInfo[Slot].SPDDataBuf[SPD_SDRAM_COL_ADDR];
		Banks = DramAttr->DimmInfo[Slot].SPDDataBuf[SPD_SDRAM_NO_OF_BANKS];	//this is Bank number not Bank address bit
		if (Banks == 4)
			Banks = 2;
		else if (Banks == 8)
			Banks = 3;
		else
			Banks = 0;
		Size = (u32) (1 << (Rows + Columns + Banks + 3));
		RankIndex = 2 * Slot;
		DramAttr->RankSize[RankIndex] = Size;
		//if this module have two ranks
		if ((DramAttr->
		     DimmInfo[Slot].SPDDataBuf[SPD_SDRAM_DIMM_RANKS] & 0x07) ==
		    0x01) {
			RankIndex++;
			DramAttr->RankSize[RankIndex] = Size;
		}

		PRINT_DEBUG_MEM("rows: ");
		PRINT_DEBUG_MEM_HEX8(Rows);
		PRINT_DEBUG_MEM(", columns:");
		PRINT_DEBUG_MEM_HEX8(Columns);
		PRINT_DEBUG_MEM(", banks:");
		PRINT_DEBUG_MEM_HEX8(Banks);
		PRINT_DEBUG_MEM("\r");

		if (Banks == 3)
			HasThreeBitBA = TRUE;
	}

	//must set BA2 enable if any 8-bank device exists
	if (HasThreeBitBA) {
		Data = pci_read_config8(MEMCTRL, 0x53);
		Data |= 0x80;
		pci_write_config8(MEMCTRL, 0x53, Data);
	}
#if 1
	for (RankIndex = 0; DramAttr->RankSize[RankIndex] != 0; RankIndex++) {
		PRINT_DEBUG_MEM("Rank:");
		PRINT_DEBUG_MEM_HEX8(RankIndex);
		PRINT_DEBUG_MEM(", Size:");
		PRINT_DEBUG_MEM_HEX32(DramAttr->RankSize[RankIndex] >> 20);
		PRINT_DEBUG_MEM("\r");
	}
#endif
}

/*===================================================================
Function   : DRAMSetRankMAType()
Precondition :
Input       :
		  DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     : Void
Purpose   : set the matype Reg by MAMapTypeTbl, which the rule can be found in memoryinit
===================================================================*/
void DRAMSetRankMAType(DRAM_SYS_ATTR * DramAttr)
{
	u8 SlotNum, Data, j, Reg, or, and;
	u8 ShiftBits[] = { 5, 1, 5, 1 };	/* Rank 0/1 MA Map Type is 7:5, Rank 2/3 MA Map Type is 3:1. See  Fun3Rx50. */
	u8 MAMapTypeTbl[] = {	/* Table 12 of P4M800 Pro DataSheet. */
		2, 9, 0,	/* Bank Address Bits, Column Address Bits, Rank MA Map Type */
		2, 10, 1,
		2, 11, 2,
		2, 12, 3,
		3, 10, 5,
		3, 11, 6,
		3, 12, 7,
		0, 0, 0
	};
	Data = pci_read_config8(MEMCTRL, 0x50);
	Data &= 0x1;
	pci_write_config8(MEMCTRL, 0x50, Data);
	// disable MA32/16 MA33/17 swap   in memory init it has this Reg fill
	Data = pci_read_config8(MEMCTRL, 0x6b);
	Data &= ~0x08;
	pci_write_config8(MEMCTRL, 0x6b, Data);

	Data = 0x00;
	for (SlotNum = 0; SlotNum < MAX_DIMMS; SlotNum++) {
		if (DramAttr->DimmInfo[SlotNum].bPresence) {
			for (j = 0; MAMapTypeTbl[j] != 0; j += 3) {
				if ((1 << MAMapTypeTbl[j]) ==
				    DramAttr->
				    DimmInfo[SlotNum].SPDDataBuf
				    [SPD_SDRAM_NO_OF_BANKS]
				    && MAMapTypeTbl[j + 1] ==
				    DramAttr->
				    DimmInfo[SlotNum].SPDDataBuf
				    [SPD_SDRAM_COL_ADDR]) {
					break;
				}
			}
			if (0 == MAMapTypeTbl[j]) {
				PRINT_DEBUG_MEM
				    ("UNSUPPORTED Bank, Row and Column Addr Bits!\r");
				return;
			}
			or = MAMapTypeTbl[j + 2] << ShiftBits[SlotNum];
			if (DramAttr->CmdRate == 1)
				or |= 0x01 << (ShiftBits[SlotNum] - 1);

			Reg = SlotNum / 2;
			if ((SlotNum & 0x01) == 0x01) {
				and = 0xf1;	// BUGBUG: it should be 0xf0
			} else {
				and = 0x1f;	// BUGBUG: it should be 0x0f
			}
			Data = pci_read_config8(MEMCTRL, 0x50 + Reg);
			Data &= and;
			Data |= or;
			pci_write_config8(MEMCTRL, 0x50 + Reg, Data);
		}
	}
	//may have some Reg filling at add 3-52 11 and 3-53   in his function
}

/*===================================================================
Function   : DRAMSetEndingAddress()
Precondition :
Input      :
		  DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     : Void
Purpose   : realize the Vrank 40...Reg (Start and Ending Regs). Vrank have  same order with phy Rank, Size is actual Size
===================================================================*/
void DRAMSetEndingAddress(DRAM_SYS_ATTR * DramAttr)
{
	u8 Shift = 1, Data, RankNO, Size, Start = 0, End = 0, Vrank;
	for (RankNO = 0; RankNO < 4; RankNO++) {
		if ((DramAttr->RankPresentMap & Shift) != 0) {
			Size = (u8) (DramAttr->RankSize[RankNO] >> 26);	// current Size in the unit of 64M
			if (Size != 0) {

				End = End + Size;	// calculate current ending address,   add the current Size to ending
				Vrank = RankNO;	// get virtual Rank
				Data = End;	// set begin/End address register to correspondig virtual       Rank #
				pci_write_config8(MEMCTRL, 0x40 + Vrank, Data);
				Data = Start;
				pci_write_config8(MEMCTRL, 0x48 + Vrank, Data);
				PRINT_DEBUG_MEM("Rank: ");
				PRINT_DEBUG_MEM_HEX8(Vrank);
				PRINT_DEBUG_MEM(", Start:");
				PRINT_DEBUG_MEM_HEX8(Start);
				PRINT_DEBUG_MEM(", End:");
				PRINT_DEBUG_MEM_HEX8(End);
				PRINT_DEBUG_MEM("\r");

				Start = End;
			}
		}
		Shift <<= 1;
	}

	if (DramAttr->RankNumChB > 0) {
		//this is a bug,fixed is to 2,so the max LL size is 128M
		Data = 0x02;
		pci_write_config8(MEMCTRL, 0x44, Data);
	}
	Data = End * 4;
	pci_write_config8(PCI_DEV(0, 17, 7), 0x60, Data);
	// We should directly write to south Bridge, not in north bridge
	// program LOW TOP Address
	Data = pci_read_config8(MEMCTRL, 0x88);
	pci_write_config8(MEMCTRL, 0x85, Data);

	// also program vlink mirror
	// We should directly write to south Bridge, not in north bridge
	pci_write_config8(PCI_DEV(0, 17, 7), 0xe5, Data);
}

/*===================================================================
Function   : DRAMPRToVRMapping()
Precondition :
Input       :
		  DramAttr: pointer point to  DRAMSYSATTR  which consist the DDR and Dimm information in MotherBoard
Output     : Void
Purpose   : set the Vrank-prank map with the same order
===================================================================*/
void DRAMPRToVRMapping(DRAM_SYS_ATTR * DramAttr)
{
	u8 Shift, Data, and, or, DimmNO = 0, PhyRankNO, Reg;

	for (Reg = 0x54; Reg <= 0x57; Reg++)	//clear the map-reg
	{
		Data = 0;
		pci_write_config8(MEMCTRL, Reg, Data);
	}

	Shift = 1;
	for (PhyRankNO = 0; PhyRankNO < MAX_RANKS; PhyRankNO++) {
		if ((DramAttr->RankPresentMap & Shift) != 0) {
			or = PhyRankNO;	// get virtual Rank   ,same with PhyRank
			or |= 0x08;

			if ((PhyRankNO & 0x01) == 0x01)	// get mask for register
				and = 0xf0;
			else {
				and = 0x0f;
				or <<= 4;
			}
			DimmNO = (PhyRankNO >> 1);
			Data = pci_read_config8(MEMCTRL, 0x54 + DimmNO);
			Data &= and;
			Data |= or;
			pci_write_config8(MEMCTRL, 0x54 + DimmNO, Data);
		}
		Shift <<= 1;
	}
}