/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2010 Advanced Micro Devices, Inc.
 * Copyright (C) 2015 Timothy Pearson <tpearson@raptorengineeringinc.com>, Raptor Engineering
 *
 * 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.
 */

static uint8_t AgesaHwWlPhase1(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, u8 dct, u8 dimm, u8 pass);
static uint8_t AgesaHwWlPhase2(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t dimm, uint8_t pass);
static uint8_t AgesaHwWlPhase3(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, u8 dct, 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 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_DCT(pDCTstat->dev_dct, Channel, 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_DCT(pDCTstat->dev_dct, 0, 0x8C);
	val &= ~(1 << DisAutoRefresh);
	Set_NB32_DCT(pDCTstat->dev_dct, 0, 0x8C, val);

	val = Get_NB32_DCT(pDCTstat->dev_dct, 1, 0x8C);
	val &= ~(1 << DisAutoRefresh);
	Set_NB32_DCT(pDCTstat->dev_dct, 1, 0x8C, val);
}

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

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

	val = Get_NB32_DCT(pDCTstat->dev_dct, 1, 0x8C);
	val |= 1 << DisAutoRefresh;
	Set_NB32_DCT(pDCTstat->dev_dct, 1, 0x8C, val);
}


static uint8_t PhyWLPass1(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, u8 dct)
{
	u8 dimm;
	u16 DIMMValid;
	uint8_t status = 0;
	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))) {
				status |= AgesaHwWlPhase1(pMCTstat, pDCTstat, dct, dimm, FirstPass);
				status |= AgesaHwWlPhase2(pMCTstat, pDCTstat, dct, dimm, FirstPass);
				status |= AgesaHwWlPhase3(pMCTstat, pDCTstat, dct, dimm, FirstPass);
			}
		}
	}

	return status;
}

static uint8_t PhyWLPass2(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t final)
{
	u8 dimm;
	u16 DIMMValid;
	uint8_t status = 0;
	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);
		if (!is_fam15h()) {
			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);
		for (dimm = 0; dimm < MAX_DIMMS_SUPPORTED; dimm ++) {
			if (DIMMValid & (1 << (dimm << 1))) {
				status |= AgesaHwWlPhase1(pMCTstat, pDCTstat, dct, dimm, SecondPass);
				status |= AgesaHwWlPhase2(pMCTstat, pDCTstat, dct, dimm, SecondPass);
				status |= AgesaHwWlPhase3(pMCTstat, pDCTstat, dct, dimm, SecondPass);
			}
		}
	}

	return status;
}

static uint16_t fam15h_next_highest_memclk_freq(uint16_t memclk_freq)
{
	uint16_t fam15h_next_highest_freq_tab[] = {0, 0, 0, 0, 0x6, 0, 0xa, 0, 0, 0, 0xe, 0, 0, 0, 0x12, 0, 0, 0, 0x16, 0, 0, 0, 0x16};
	return fam15h_next_highest_freq_tab[memclk_freq];
}

/* Write Levelization Training
 * Algorithm detailed in the Fam10h BKDG Rev. 3.62 section 2.8.9.9.1
 */
static void WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstatA, uint8_t Node, uint8_t Pass)
{
	uint8_t status;
	uint8_t timeout;
	uint16_t final_target_freq;

	struct DCTStatStruc *pDCTstat;
	pDCTstat = pDCTstatA + Node;

	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];
	}

	if (Pass == FirstPass) {
		timeout = 0;
		do {
			status = 0;
			timeout++;
			status |= PhyWLPass1(pMCTstat, pDCTstat, 0);
			status |= PhyWLPass1(pMCTstat, pDCTstat, 1);
			if (status)
				printk(BIOS_INFO,
					"%s: Retrying write levelling due to invalid value(s) detected in first phase\n",
					__func__);
		} while (status && (timeout < 8));
		if (status)
			printk(BIOS_INFO,
				"%s: Uncorrectable invalid value(s) detected in first phase of write levelling\n",
				__func__);
	}

	if (Pass == SecondPass) {
		if (pDCTstat->TargetFreq > mhz_to_memclk_config(mctGet_NVbits(NV_MIN_MEMCLK))) {
			/* 8.Prepare the memory subsystem for the target MEMCLK frequency.
			 * NOTE: BIOS must program both DCTs to the same frequency.
			 * NOTE: Fam15h steps the frequency, Fam10h slams the frequency.
			 */
			uint8_t global_phy_training_status = 0;
			final_target_freq = pDCTstat->TargetFreq;

			while (pDCTstat->Speed != final_target_freq) {
				if (is_fam15h())
					pDCTstat->TargetFreq = fam15h_next_highest_memclk_freq(pDCTstat->Speed);
				else
					pDCTstat->TargetFreq = final_target_freq;
				SetTargetFreq(pMCTstat, pDCTstatA, Node);
				timeout = 0;
				do {
					status = 0;
					timeout++;
					status |= PhyWLPass2(pMCTstat, pDCTstat, 0, (pDCTstat->TargetFreq == final_target_freq));
					status |= PhyWLPass2(pMCTstat, pDCTstat, 1, (pDCTstat->TargetFreq == final_target_freq));
					if (status)
						printk(BIOS_INFO,
							"%s: Retrying write levelling due to invalid value(s) detected in last phase\n",
							__func__);
				} while (status && (timeout < 8));
				global_phy_training_status |= status;
			}

			pDCTstat->TargetFreq = final_target_freq;

			if (global_phy_training_status)
				printk(BIOS_WARNING,
					"%s: Uncorrectable invalid value(s) detected in second phase of write levelling; "
					"continuing but system may be unstable!\n",
					__func__);

			uint8_t dct;
			for (dct = 0; dct < 2; dct++) {
				sDCTStruct *pDCTData = pDCTstat->C_DCTPtr[dct];
				memcpy(pDCTData->WLGrossDelayFinalPass, pDCTData->WLGrossDelayPrevPass, sizeof(pDCTData->WLGrossDelayPrevPass));
				memcpy(pDCTData->WLFineDelayFinalPass, pDCTData->WLFineDelayPrevPass, sizeof(pDCTData->WLFineDelayPrevPass));
				pDCTData->WLCriticalGrossDelayFinalPass = pDCTData->WLCriticalGrossDelayPrevPass;
			}
		}
	}

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

void mct_WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
					struct DCTStatStruc *pDCTstatA, uint8_t Pass)
{
	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, pDCTstatA, Node, Pass);
			Restore_OnDimmMirror(pMCTstat, pDCTstat);
		}
	}
}
