/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2010 Advanced Micro Devices, 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
 */

static void SetTargetFreq(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat);
static void AgesaHwWlPhase1(sMCTStruct *pMCTData,
					sDCTStruct *pDCTData, u8 dimm, u8 pass);
static void EnableZQcalibration(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
static void DisableZQcalibration(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
static void PrepareC_MCT(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
static void PrepareC_DCT(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, u8 dct);
static void MultiplyDelay(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, u8 dct);
static void Restore_OnDimmMirror(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
static void Clear_OnDimmMirror(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);

static void SetEccWrDQS_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat)
{
	u8 ByteLane, DimmNum, OddByte, Addl_Index, Channel;
	u8 EccRef1, EccRef2, EccDQSScale;
	u32 val;
	u16 word;

	for (Channel = 0; Channel < 2; Channel ++) {
		for (DimmNum = 0; DimmNum < C_MAX_DIMMS; DimmNum ++) { /* we use DimmNum instead of DimmNumx3 */
			for (ByteLane = 0; ByteLane < 9; ByteLane ++) {
				/* Get RxEn initial value from WrDqs */
				if (ByteLane & 1)
					OddByte = 1;
				else
					OddByte = 0;
				if (ByteLane < 2)
					Addl_Index = 0x30;
				else if (ByteLane < 4)
					Addl_Index = 0x31;
				else if (ByteLane < 6)
					Addl_Index = 0x40;
				else if (ByteLane < 8)
					Addl_Index = 0x41;
				else
					Addl_Index = 0x32;
				Addl_Index += DimmNum * 3;

				val = Get_NB32_index_wait(pDCTstat->dev_dct, Channel * 0x100 + 0x98, Addl_Index);
				if (OddByte)
					val >>= 16;
				/* Save WrDqs to stack for later usage */
				pDCTstat->CH_D_B_TxDqs[Channel][DimmNum][ByteLane] = val & 0xFF;
				EccDQSScale = pDCTstat->CH_EccDQSScale[Channel];
				word = pDCTstat->CH_EccDQSLike[Channel];
				if ((word & 0xFF) == ByteLane) EccRef1 = val & 0xFF;
				if (((word >> 8) & 0xFF) == ByteLane) EccRef2 = val & 0xFF;
			}
		}
	}
}

static void EnableAutoRefresh_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat)
{
	u32 val;

	val = Get_NB32(pDCTstat->dev_dct, 0x8C);
	val &= ~(1 << DisAutoRefresh);
	Set_NB32(pDCTstat->dev_dct, 0x8C, val);

	val = Get_NB32(pDCTstat->dev_dct, 0x8C + 0x100);
	val &= ~(1 << DisAutoRefresh);
	Set_NB32(pDCTstat->dev_dct, 0x8C + 0x100, val);
}

static void DisableAutoRefresh_D(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat)
{
	u32 val;

	val = Get_NB32(pDCTstat->dev_dct, 0x8C);
	val |= 1 << DisAutoRefresh;
	Set_NB32(pDCTstat->dev_dct, 0x8C, val);

	val = Get_NB32(pDCTstat->dev_dct, 0x8C + 0x100);
	val |= 1 << DisAutoRefresh;
	Set_NB32(pDCTstat->dev_dct, 0x8C + 0x100, val);
}


static void PhyWLPass1(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, u8 dct)
{
	u8 dimm;
	u16 DIMMValid;
	void *DCTPtr;

	dct &= 1;

	DCTPtr = (void *)(pDCTstat->C_DCTPtr[dct]);
	pDCTstat->DIMMValid = pDCTstat->DIMMValidDCT[dct];
	pDCTstat->CSPresent = pDCTstat->CSPresent_DCT[dct];

	if (pDCTstat->GangedMode & 1)
		pDCTstat->CSPresent = pDCTstat->CSPresent_DCT[0];

	if (pDCTstat->DIMMValid) {
		DIMMValid = pDCTstat->DIMMValid;
		PrepareC_DCT(pMCTstat, pDCTstat, dct);
		for (dimm = 0; dimm < MAX_DIMMS_SUPPORTED; dimm ++) {
			if (DIMMValid & (1 << (dimm << 1)))
				AgesaHwWlPhase1(pDCTstat->C_MCTPtr, DCTPtr, dimm, FirstPass);
		}
	}
}

static void PhyWLPass2(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, u8 dct)
{
	u8 dimm;
	u16 DIMMValid;
	void *DCTPtr;

	dct &= 1;

	DCTPtr = (void *)&(pDCTstat->C_DCTPtr[dct]); /* todo: */
	pDCTstat->DIMMValid = pDCTstat->DIMMValidDCT[dct];
	pDCTstat->CSPresent = pDCTstat->CSPresent_DCT[dct];

	if (pDCTstat->GangedMode & 1)
		pDCTstat->CSPresent = pDCTstat->CSPresent_DCT[0];

	if (pDCTstat->DIMMValid) {
		DIMMValid = pDCTstat->DIMMValid;
		PrepareC_DCT(pMCTstat, pDCTstat, dct);
		pDCTstat->Speed = pDCTstat->DIMMAutoSpeed = pDCTstat->TargetFreq;
		pDCTstat->CASL = pDCTstat->DIMMCASL = pDCTstat->TargetCASL;
		SPD2ndTiming(pMCTstat, pDCTstat, dct);
		ProgDramMRSReg_D(pMCTstat, pDCTstat, dct);
		PlatformSpec_D(pMCTstat, pDCTstat, dct);
		fenceDynTraining_D(pMCTstat, pDCTstat, dct);
		Restore_OnDimmMirror(pMCTstat, pDCTstat);
		StartupDCT_D(pMCTstat, pDCTstat, dct);
		Clear_OnDimmMirror(pMCTstat, pDCTstat);
		SetDllSpeedUp_D(pMCTstat, pDCTstat, dct);
		DisableAutoRefresh_D(pMCTstat, pDCTstat);
		MultiplyDelay(pMCTstat, pDCTstat, dct);
		for (dimm = 0; dimm < MAX_DIMMS_SUPPORTED; dimm ++) {
			if (DIMMValid & (1 << (dimm << 1)))
				AgesaHwWlPhase1(pDCTstat->C_MCTPtr, pDCTstat->C_DCTPtr[dct], dimm, SecondPass);
		}
	}
}

static void WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat)
{
	pDCTstat->C_MCTPtr  = &(pDCTstat->s_C_MCTPtr);
	pDCTstat->C_DCTPtr[0] = &(pDCTstat->s_C_DCTPtr[0]);
	pDCTstat->C_DCTPtr[1] = &(pDCTstat->s_C_DCTPtr[1]);

	/* Disable auto refresh by configuring F2x[1, 0]8C[DisAutoRefresh] = 1 */
	DisableAutoRefresh_D(pMCTstat, pDCTstat);

	/* Disable ZQ calibration short command by F2x[1,0]94[ZqcsInterval]=00b */
	DisableZQcalibration(pMCTstat, pDCTstat);
	PrepareC_MCT(pMCTstat, pDCTstat);

	if (pDCTstat->GangedMode & (1 << 0)) {
		pDCTstat->DIMMValidDCT[1] = pDCTstat->DIMMValidDCT[0];
	}

	PhyWLPass1(pMCTstat, pDCTstat, 0);
	PhyWLPass1(pMCTstat, pDCTstat, 1);

	if (pDCTstat->TargetFreq > 4) {
		/* 8.Prepare the memory subsystem for the target MEMCLK frequency.
		 * Note: BIOS must program both DCTs to the same frequency.
		 */
		SetTargetFreq(pMCTstat, pDCTstat);
		PhyWLPass2(pMCTstat, pDCTstat, 0);
		PhyWLPass2(pMCTstat, pDCTstat, 1);

	}

	SetEccWrDQS_D(pMCTstat, pDCTstat);
	EnableAutoRefresh_D(pMCTstat, pDCTstat);
	EnableZQcalibration(pMCTstat, pDCTstat);
}

void mct_WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstatA)
{
	u8 Node;

	for (Node = 0; Node < MAX_NODES_SUPPORTED; Node++) {
		struct DCTStatStruc *pDCTstat;
		pDCTstat = pDCTstatA + Node;

		if (pDCTstat->NodePresent) {
			mctSMBhub_Init(Node);
			Clear_OnDimmMirror(pMCTstat, pDCTstat);
			WriteLevelization_HW(pMCTstat, pDCTstat);
			Restore_OnDimmMirror(pMCTstat, pDCTstat);
		}
	}
}
