| /* |
| * 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); |
| } |
| } |
| } |