/* IBM_PROLOG_BEGIN_TAG */ /* This is an automatically generated prolog. */ /* */ /* $Source: chips/p9/procedures/hwp/memory/lib/phy/ddr_phy.C $ */ /* */ /* IBM CONFIDENTIAL */ /* */ /* EKB Project */ /* */ /* COPYRIGHT 2015,2016 */ /* [+] International Business Machines Corp. */ /* */ /* */ /* The source code for this program is not published or otherwise */ /* divested of its trade secrets, irrespective of what has been */ /* deposited with the U.S. Copyright Office. */ /* */ /* IBM_PROLOG_END_TAG */ /// /// @file ddr_phy.C /// @brief Subroutines to manipulate the phy, or used during phy procedures /// // *HWP HWP Owner: Brian Silver // *HWP HWP Backup: Andre Marin // *HWP Team: Memory // *HWP Level: 2 // *HWP Consumed by: FSP:HB #include #include #include #include #include #include #include #include #include #include #include using fapi2::TARGET_TYPE_MCBIST; using fapi2::TARGET_TYPE_PROC_CHIP; using fapi2::TARGET_TYPE_SYSTEM; using fapi2::TARGET_TYPE_MCA; using fapi2::TARGET_TYPE_MCS; using fapi2::TARGET_TYPE_DIMM; namespace mss { /// /// @brief change resetn to the given state /// @param[in] i_target the mcbist /// @param[in] i_state the desired state /// @return FAPI2_RC_SUCCESS iff ok /// fapi2::ReturnCode change_resetn( const fapi2::Target& i_target, states i_state ) { fapi2::buffer l_data; for (const auto& p : mss::find_targets(i_target)) { FAPI_DBG("Change reset to %s PHY: %s", (i_state == HIGH ? "high" : "low"), mss::c_str(p)); FAPI_TRY( mss::getScom(p, MCA_MBA_CAL0Q, l_data) ); i_state == HIGH ? l_data.setBit() : l_data.clearBit(); FAPI_TRY( mss::putScom(p, MCA_MBA_CAL0Q, l_data) ); } fapi_try_exit: return fapi2::current_err; } /// /// @brief perform the zctl toggle process /// @param[in] i_target the mcbist for the reset recover /// @return FAPI2_RC_SUCCESS iff ok /// fapi2::ReturnCode toggle_zctl( const fapi2::Target& i_target ) { // With model 31 (Drop X) this became unecessary. Not removing it as it's unclear what // the final algorithm(s) will be. BRS #if 0 fapi2::buffer l_data; const auto l_ports = mss::find_targets(i_target); // // 4. Write 0x0010 to PC IO PVT N/P FET driver control registers to assert ZCTL reset and enable the internal impedance controller. // (SCOM Addr: 0x8000C0140301143F, 0x8000C0140301183F, 0x8001C0140301143F, 0x8001C0140301183F) FAPI_DBG("Write 0x0010 to PC IO PVT N/P FET driver control registers to assert ZCTL reset"); l_data.setBit<59>(); FAPI_TRY( mss::scom_blastah(l_ports, MCA_DDRPHY_PC_IO_PVT_FET_CONTROL_P0, l_data) ); // // 5. Write 0x0018 to PC IO PVT N/P FET driver control registers to deassert ZCTL reset while impedance controller is still enabled. // (SCOM Addr: 0x8000C0140301143F, 0x8000C0140301183F, 0x8001C0140301143F, 0x8001C0140301183F) FAPI_DBG("Write 0x0018 to PC IO PVT N/P FET driver control registers to deassert ZCTL reset."); l_data.setBit<59>().setBit<60>(); FAPI_TRY( mss::scom_blastah(l_ports, MCA_DDRPHY_PC_IO_PVT_FET_CONTROL_P0, l_data) ); // // 6. Write 0x0008 to PC IO PVT N/P FET driver control registers to deassert the impedance controller. // (SCOM Addr: 0x8000C0140301143F, 0x8000C0140301183F, 0x8001C0140301143F, 0x8001C0140301183F) FAPI_DBG("Write 0x0008 to PC IO PVT N/P FET driver control registers to deassert the impedance controller"); l_data.clearBit<59>().setBit<60>(); FAPI_TRY( mss::scom_blastah(l_ports, MCA_DDRPHY_PC_IO_PVT_FET_CONTROL_P0, l_data) ); fapi_try_exit: #endif return fapi2::current_err; } /// @brief Change mclk low /// @param[in] i_target mcbist target /// @param[in] i_state mss::HIGH or mss::LOW - desired state. /// @return FAPI2_RC_SUCCESS iff ok /// fapi2::ReturnCode change_force_mclk_low (const fapi2::Target& i_target, const mss::states i_state) { // Working with Goldade, we learned this: // From John Bailas: 9:49:46 AM: you need to use that force_mclk_low signal, it needs to be asserted which // forces RESETN on, and CK/CKN to 0, and all other address/cmd to Z. Then the MC has to establish valid // values for all signals after PHY reset before force_mclk_low gets de-asserted // And to celar up the ambiguity of "on" // John S. Bialas Jr: Hi Brian, force_mclk_low should force RESETN, CK, and CK# to 0, and all // other address/command signals to Z // So, this should be enough to get us going. BRS // Additionally: from John Bailas // The PHY should be reset and initialized such that it can synchronously control the RESETN, CK, and CKN // to the DIMM, then force_mclk_low can be de-asserted and the control of those signals will be synchronously // maintained. Beyond that the remainder of the DIMM init sequence can be performed, ie. start CK/CKN, // de-assert RESETN, etc fapi2::buffer l_data; FAPI_DBG("force mclk %s for all ports", (i_state == mss::LOW ? "low" : "high") ); // Might as well do this for all the ports while we're here. for (const auto& p : mss::find_targets(i_target)) { FAPI_TRY( mss::getScom(p, MCA_MBA_FARB5Q, l_data) ); // TK: use writeBit? if (i_state == mss::HIGH) { l_data.setBit(); } else { l_data.clearBit(); } FAPI_TRY(mss::putScom( p, MCA_MBA_FARB5Q, l_data)); } fapi_try_exit: return fapi2::current_err; } /// /// @brief Unset the PLL and check to see that the PLL's have started /// @param[in] i_target the mcbist target /// @return FAPI2_RC_SUCCES iff ok /// fapi2::ReturnCode deassert_pll_reset( const fapi2::Target& i_target ) { fapi2::buffer l_data; #ifdef KNOW_ADR_DLL_PROCESS static const uint64_t dp16_lock_mask = 0x000000000000FFFE; static const uint64_t ad_lock_mask = fapi2::buffer().setBit<48>().setBit<49>(); #endif // // Write 0x4000 into the PC Resets Registers. This deasserts the PLL_RESET and leaves the SYSCLK_RESET bit active // (SCOM Addr: 0x8000C00E0301143F, 0x8001C00E0301143F, 0x8000C00E0301183F, 0x8001C00E0301183F) FAPI_DBG("Write 0x4000 into the PC Resets Regs. This deasserts the PLL_RESET and leaves the SYSCLK_RESET bit active"); l_data.setBit(); FAPI_TRY( mss::scom_blastah(mss::find_targets(i_target), MCA_DDRPHY_PC_RESETS_P0, l_data) ); // // Wait at least 1 millisecond to allow the PLLs to lock. Otherwise, poll the PC DP18 PLL Lock Status // and the PC AD32S PLL Lock Status to determine if all PLLs have locked. // PC DP18 PLL Lock Status should be 0xF800: (SCOM Addr: 0x8000C0000301143F, 0x8001C0000301143F, 0x8000C0000301183F, 0x8001C0000301183F) // PC AD32S PLL Lock Status should be 0xC000: (SCOM Addr: 0x8000C0010301143F, 0x8001C0010301143F, 0x8000C0010301183F, 0x8001C0010301183F) #ifdef KNOW_ADR_DLL_PROCESS // Poll for lock bits FAPI_DBG("Poll until DP18 and AD32S PLLs have locked"); do { // Set in the CHECK_PLL macro done_polling = true; fapi2::delay(DELAY_1US, cycles_to_simcycles(us_to_cycles(i_target, 1))); // Note: in the latest scomdef this is DP16 BRS // Note: Not sure what the proper registers here are. I took the following old addresses from Ed's // version of the code and mapped the addresses to the latests mc_scom_addresses.H. This needs to // be fixed up when the extended addressing is fixed up. BRS // CONST_UINT64_T(DDRPHY_PC_DP18_PLL_LOCK_STATUS_P0_0x8000C0000701143F, ULL(0x8000C0000701143F) ); CHECK_PLL( l_target_proc, MCA_0_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P0, l_data, dp16_lock_mask ); // CONST_UINT64_T(DDRPHY_PC_DP18_PLL_LOCK_STATUS_P1_0x8001C0000701143F, ULL(0x8001C0000701143F) ); CHECK_PLL( l_target_proc, MCA_1_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P1, l_data, dp16_lock_mask ); // CONST_UINT64_T(DDRPHY_PC_DP18_PLL_LOCK_STATUS_P2_0x8000C0000701183F, ULL(0x8000C0000701183F) ); CHECK_PLL( l_target_proc, MCA_2_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P2, l_data, dp16_lock_mask ); // CONST_UINT64_T(DDRPHY_PC_DP18_PLL_LOCK_STATUS_P3_0x8001C0000701183F, ULL(0x8001C0000701183F) ); CHECK_PLL( l_target_proc, MCA_3_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P3, l_data, dp16_lock_mask ); // CONST_UINT64_T( DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P0_0x8000C0010701143F, ULL(0x8000C0010701143F) ); CHECK_PLL( l_target_proc, MCA_0_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P0, l_data, ad_lock_mask ); // CONST_UINT64_T( DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P1_0x8001C0010701143F, ULL(0x8001C0010701143F) ); CHECK_PLL( l_target_proc, MCA_1_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P1, l_data, ad_lock_mask ); // CONST_UINT64_T( DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P2_0x8000C0010701183F, ULL(0x8000C0010701183F) ); CHECK_PLL( l_target_proc, MCA_2_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P2, l_data, ad_lock_mask ); // CONST_UINT64_T( DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P3_0x8001C0010701183F, ULL(0x8001C0010701183F) ); CHECK_PLL( l_target_proc, MCA_3_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P3, l_data, ad_lock_mask ); } while (!done_polling && --max_poll_loops); // If we ran out of iterations, report we have a pll lock failure. if (max_poll_loops == 0) { FAPI_ERR("DDR PHY PLL failed to lock for %s", mss::c_str(i_target)); FFDC_PLL( l_target_proc, MCA_0_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P0, l_data, dp16_lock_mask, fapi2::MSS_DP16_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_1_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P1, l_data, dp16_lock_mask, fapi2::MSS_DP16_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_2_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P2, l_data, dp16_lock_mask, fapi2::MSS_DP16_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_3_DDRPHY_PC_DP18_PLL_LOCK_STATUS_P3, l_data, dp16_lock_mask, fapi2::MSS_DP16_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_0_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P0, l_data, ad_lock_mask, fapi2::MSS_AD32S_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_1_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P1, l_data, ad_lock_mask, fapi2::MSS_AD32S_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_2_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P2, l_data, ad_lock_mask, fapi2::MSS_AD32S_PLL_FAILED_TO_LOCK() ); FFDC_PLL( l_target_proc, MCA_3_DDRPHY_PC_AD32S_PLL_LOCK_STATUS_P3, l_data, ad_lock_mask, fapi2::MSS_AD32S_PLL_FAILED_TO_LOCK() ); } #endif fapi_try_exit: return fapi2::current_err; } /// /// @brief Change the continuous update mode of the PR CNTL registers /// @note Will take the SYSCLK control out of reset, too /// @param[in] i_target the mcbist target /// @param[in] i_state, mss::ON if you want to be in continuous mode, mss::OFF to turn it off /// @return FAPI2_RC_SUCCES iff ok /// fapi2::ReturnCode setup_phase_rotator_control_registers( const fapi2::Target& i_target, const states i_state ) { uint8_t is_sim = 0; // Per Bialas, we don't want to do true alignment in the cycle sim as we have // a chance of being off one-tick (which is detrimental.) Per his recomendations, // we write 0's to the control registers and then configure them with 0x8080. We'll // over write l_update's values with a getScom to the correct h/w initiialized values // if we're not in sim fapi2::buffer l_update( i_state == mss::ON ? 0x0 : 0x8080 ); const auto l_mca = find_targets(i_target); std::vector addrs( { MCA_DDRPHY_ADR_SYSCLK_CNTL_PR_P0_ADR32S0, MCA_DDRPHY_ADR_SYSCLK_CNTL_PR_P0_ADR32S1, } ); if (l_mca.size() == 0) { // No MCA, no problem return fapi2::FAPI2_RC_SUCCESS; } FAPI_TRY( FAPI_ATTR_GET(fapi2::ATTR_IS_SIMULATION, fapi2::Target(), is_sim) ); if (! is_sim) { // All the MCA (and both registers) will be in the same state, so we can get the first and use it to create the // values for the others. FAPI_TRY( mss::getScom(l_mca[0], MCA_DDRPHY_ADR_SYSCLK_CNTL_PR_P0_ADR32S0, l_update) ); l_update.setBit(); l_update.writeBit(i_state); } FAPI_INF("Write 0x%lx into the ADR SysClk Phase Rotator Control Regs", l_update); // WRCLK Phase rotators are taken care of in the phy initfile. BRS 6/16. FAPI_TRY( mss::scom_blastah(l_mca, addrs, l_update) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Deassert the sys clk reset /// @param[in] i_target the mcbist target /// @return FAPI2_RC_SUCCES iff ok /// fapi2::ReturnCode deassert_sysclk_reset( const fapi2::Target& i_target ) { FAPI_DBG("Write 0x0000 into the PC Resets Register. This deasserts the SysClk Reset."); FAPI_TRY( mss::scom_blastah(mss::find_targets(i_target), MCA_DDRPHY_PC_RESETS_P0, 0) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Check if the bang bang lock has succeeded /// @param[in] i_target a MCBIST target /// @return FAPI2_RC_SUCCESs iff ok /// fapi2::ReturnCode check_bang_bang_lock( const fapi2::Target& i_target ) { fapi2::buffer l_read; uint8_t is_sim = 0; // On each port there are 5 DP16's which have lock registers. static const std::vector l_addresses = { MCA_DDRPHY_DP16_SYSCLK_PR_VALUE_P0_0, MCA_DDRPHY_DP16_SYSCLK_PR_VALUE_P0_1, MCA_DDRPHY_DP16_SYSCLK_PR_VALUE_P0_2, MCA_DDRPHY_DP16_SYSCLK_PR_VALUE_P0_3, MCA_DDRPHY_DP16_SYSCLK_PR_VALUE_P0_4, }; FAPI_TRY( FAPI_ATTR_GET(fapi2::ATTR_IS_SIMULATION, fapi2::Target(), is_sim) ); // There's nothing going on in sim ... if (is_sim) { return fapi2::FAPI2_RC_SUCCESS; } for (const auto& p : mss::find_targets(i_target)) { // Check the ADR lock bit // Little duplication - makes things more clear and simpler, and allows us to callout the // bugger which first caused a problem. FAPI_TRY( mss::getScom(p, MCA_DDRPHY_ADR_SYSCLK_PR_VALUE_RO_P0_ADR32S0, l_read) ); FAPI_ASSERT( l_read.getBit() == mss::ON, fapi2::MSS_ADR_BANG_BANG_FAILED_TO_LOCK().set_MCA_IN_ERROR(p).set_ADR(0), "ADR failed bb lock. ADR%d register 0x%016lx 0x%016lx", 0, MCA_DDRPHY_ADR_SYSCLK_PR_VALUE_RO_P0_ADR32S0, l_read ); FAPI_TRY( mss::getScom(p, MCA_DDRPHY_ADR_SYSCLK_PR_VALUE_RO_P0_ADR32S1, l_read) ); FAPI_ASSERT( l_read.getBit() == mss::ON, fapi2::MSS_ADR_BANG_BANG_FAILED_TO_LOCK().set_MCA_IN_ERROR(p).set_ADR(1), "ADR failed bb lock. ADR%d register 0x%016lx 0x%016lx", 1, MCA_DDRPHY_ADR_SYSCLK_PR_VALUE_RO_P0_ADR32S1, l_read ); // Pop thru the registers on the ports and see if all the sysclks are locked. // FFDC regiser collection will collect interesting information so we only need // to catch the first fail. for (const auto r : l_addresses) { FAPI_TRY( mss::getScom(p, r, l_read) ); FAPI_ASSERT( l_read.getBit() == mss::ON, fapi2::MSS_DP16_BANG_BANG_FAILED_TO_LOCK().set_MCA_IN_ERROR(p).set_ROTATOR(0), "DP16 failed bb lock. rotator %d register 0x%016lx 0x%016lx", 0, r, l_read ); FAPI_ASSERT( l_read.getBit() == mss::ON, fapi2::MSS_DP16_BANG_BANG_FAILED_TO_LOCK().set_MCA_IN_ERROR(p).set_ROTATOR(1), "DP16 failed bb lock. rotator %d register 0x%016lx 0x%016lx", 1, r, l_read ); } } fapi_try_exit: return fapi2::current_err; } /// /// @brief Flush the DDR PHY /// @param[in] i_target the mcbist target /// @return FAPI2_RC_SUCCES iff ok /// fapi2::ReturnCode ddr_phy_flush( const fapi2::Target& i_target ) { fapi2::buffer l_data; fapi2::buffer l_mask; FAPI_INF( "Performing mss_ddr_phy_flush routine" ); FAPI_INF("ADR/DP18 FLUSH: 1) set PC_POWERDOWN_1 register, powerdown enable(48), flush bit(58)"); // set MASTER_PD_CNTL bit set WR_FIFO_STAB bit l_data.setBit<48>().setBit<58>(); l_mask.setBit<48>().setBit<58>(); const auto l_ports = mss::find_targets(i_target); for (const auto& p : l_ports) { FAPI_TRY(mss::putScomUnderMask(p, MCA_DDRPHY_PC_POWERDOWN_1_P0, l_data, l_mask) ); } fapi2::delay(DELAY_100NS, cycles_to_simcycles(ns_to_cycles(i_target, 100))); FAPI_INF("ADR/DP18 FLUSH: 2) clear PC_POWERDOWN_1 register, powerdown enable(48), flush bit(58)"); for (const auto& p : l_ports) { FAPI_TRY(mss::putScomUnderMask(p, MCA_DDRPHY_PC_POWERDOWN_1_P0, 0, l_mask) ); } fapi_try_exit: return fapi2::current_err; } /// /// @brief Return the DIMM target for the primary rank in the specificed rank pair /// @param[in] i_target the MCA target /// @param[in] i_rp the rank pair /// @param[out] o_dimm fapi2::Target /// @return FAPI2_RC_SUCCESS iff ok /// template<> fapi2::ReturnCode rank_pair_primary_to_dimm( const fapi2::Target& i_target, const uint64_t i_rp, fapi2::Target& o_dimm) { fapi2::buffer l_data; fapi2::buffer l_rank; uint64_t l_rank_on_dimm; const auto l_dimms = mss::find_targets(i_target); // Sanity check the rank pair FAPI_INF("%s rank pair: %d", mss::c_str(i_target), i_rp); fapi2::Assert(i_rp < MAX_RANK_PER_DIMM); // We need to get the register containing the specification for this rank pair, // and fish out the primary rank for this rank pair switch(i_rp) { case 0: FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_RANK_PAIR0_P0, l_data) ); l_data.extractToRight(l_rank); break; case 1: FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_RANK_PAIR0_P0, l_data) ); l_data.extractToRight(l_rank); break; case 2: FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_RANK_PAIR1_P0, l_data) ); l_data.extractToRight(l_rank); break; case 3: FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_RANK_PAIR1_P0, l_data) ); l_data.extractToRight(l_rank); break; }; // Now we need to figure out which DIMM this rank is on. It's either on DIMM0 or DIMM1, and DIMM0 // has ranks 0-3 and DIMM1 has ranks 4-7. Return the DIMM associated. l_rank_on_dimm = get_dimm_from_rank(l_rank); // Sanity check the DIMM list FAPI_INF("%s rank is on dimm: %d, number of dimms: %d", mss::c_str(i_target), l_rank_on_dimm, l_dimms.size()); fapi2::Assert(l_rank_on_dimm < l_dimms.size()); o_dimm = l_dimms[l_rank_on_dimm]; fapi_try_exit: return fapi2::current_err; } /// /// @brief check and process initial cal errors /// @param[in] i_target the port in question /// @return fapi2::ReturnCode, FAPI2_RC_SUCCESS iff no error /// template<> fapi2::ReturnCode process_initial_cal_errors( const fapi2::Target& i_target ) { typedef pcTraits TT; uint64_t l_errors = 0; uint64_t l_rank_pairs = 0; fapi2::buffer l_err_data; fapi2::Target l_failed_dimm; FAPI_TRY( pc::read_init_cal_error(i_target, l_err_data) ); l_err_data.extractToRight(l_errors); l_err_data.extractToRight(l_rank_pairs); FAPI_INF("initial cal err: 0x%016llx, rp: 0x%016llx (0x%016llx)", l_errors, l_rank_pairs, uint64_t(l_err_data)); if ((l_rank_pairs == 0) || (l_errors == 0)) { FAPI_INF("Initial cal - no errors reported"); return fapi2::current_err; } // Get the DIMM which failed. We should only have one rank pair as we calibrate the // rank pairs individually (we do this so we can see which DIMM failed if more than one // fails ...) Note first_bit_set gives a bit position (0 being left most.) So, the rank // in question is the bit postion minus the position of the 0th rank in the register. // (the rank bits are bits 60:63, for example, so rank 0 is in position 60) FAPI_TRY( mss::rank_pair_primary_to_dimm(i_target, mss::first_bit_set(l_rank_pairs) - TT::INIT_CAL_ERROR_RANK_PAIR, l_failed_dimm) ); FAPI_ERR("initial cal failed for %s", mss::c_str(l_failed_dimm)); // So we can do a few things here. If we're aborting on the first calibration error, // we only expect to have one error bit set. If we ran all the calibrations, we can // either have one bit set or more than one bit set. If we have more than one bit set // the result is the same - a broken DIMM which will be deconfigured. So put enough // information in the FFDC for the lab but we don't need one error for every cal fail. FAPI_ASSERT(mss::bit_count(l_errors) == 1, fapi2::MSS_DRAMINIT_TRAINING_MULTIPLE_ERRORS() .set_FAILED_STEPS(uint64_t(l_err_data)) .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed multiple training steps. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_WR_LVL_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed write leveling. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_INITIAL_PAT_WRITE_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed initial pattern write. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_DQS_ALIGNMENT_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed DQS alignenment. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_RD_CLK_SYS_CLK_ALIGNMENT_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed read clk alignenment. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_RD_CENTERING_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed read centering. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_WR_CENTERING_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed write centering. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_INITIAL_COARSE_WR_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed initial coarse write. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_COARSE_RD_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed coarse read. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_CUSTOM_PATTERN_RD_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed custom read. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_CUSTOM_PATTERN_WR_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed custom write. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); FAPI_ASSERT( ! l_err_data.getBit(), fapi2::MSS_DRAMINIT_TRAINING_DIGITAL_EYE_ERROR() .set_PORT_POSITION(mss::fapi_pos(i_target)) .set_RANKGROUP_POSITION(l_rank_pairs) .set_TARGET_IN_ERROR(l_failed_dimm), "Initial CAL failed digital eye. dimm: %s, cal err: 0x%016llx", mss::c_str(l_failed_dimm), uint64_t(l_err_data) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup the PC CONFIG0 register /// @tparam T the fapi2::TargetType /// @param[in] i_target the target (MCA or MBA?) /// @return FAPI2_RC_SUCCESS if and only if ok /// template<> fapi2::ReturnCode set_pc_config0(const fapi2::Target& i_target) { fapi2::buffer l_data; FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_CONFIG0_P0, l_data) ); // Note: This needs to get the DRAM gen from an attribute. - 0x1 is DDR4 Note for Nimbus PHY // this is ignored and hard-wired to DDR4, per John Bialas 10/15 BRS // repurposed to pda_enable_override, so zero per John Bialas 4/16 JJM l_data.insertFromRight(0x0); l_data.setBit(); l_data.setBit(); FAPI_DBG("phy pc_config0 0x%0llx", l_data); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_PC_CONFIG0_P0, l_data) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup the PC CONFIG1 register /// @tparam T the fapi2::TargetType /// @param[in] i_target fapi2::ReturnCode set_pc_config1(const fapi2::Target& i_target) { // Static table of PHY config values for MEMORY_TYPE. // [EMPTY, RDIMM, CDIMM, or LRDIMM][EMPTY, DDR3 or DDR4] static const uint64_t memory_type[4][3] = { { 0, 0, 0 }, // Empty, never really used. { 0, 0b001, 0b101 }, // RDIMM { 0, 0b000, 0b000 }, // CDIMM { 0, 0b011, 0b111 }, // LRDIMM }; fapi2::buffer l_data; uint8_t l_rlo = 0; uint8_t l_wlo = 0; uint8_t l_dram_gen[MAX_DIMM_PER_PORT] = {0}; uint8_t l_dimm_type[MAX_DIMM_PER_PORT] = {0}; uint8_t l_custom_dimm[MAX_DIMM_PER_PORT] = {0}; uint8_t l_type_index = 0; uint8_t l_gen_index = 0; FAPI_TRY( mss::vpd_rlo(i_target, l_rlo) ); FAPI_TRY( mss::vpd_wlo(i_target, l_wlo) ); FAPI_TRY( mss::eff_dram_gen(i_target, &(l_dram_gen[0])) ); FAPI_TRY( mss::eff_dimm_type(i_target, &(l_dimm_type[0])) ); FAPI_TRY( mss::eff_custom_dimm(i_target, &(l_custom_dimm[0])) ); // There's no way to configure the PHY for more than one value. However, we don't know if there's // a DIMM in one slot, the other or double drop. So we do a little gyration here to make sure // we have one of the two values (and assume effective config caught a bad config.) l_type_index = (l_custom_dimm[0] | l_custom_dimm[1]) == fapi2::ENUM_ATTR_EFF_CUSTOM_DIMM_YES ? 2 : l_dimm_type[0] | l_dimm_type[1]; l_gen_index = l_dram_gen[0] | l_dram_gen[1]; // FOR NIMBUS PHY (as the protocol choice above is) BRS FAPI_TRY( mss::getScom(i_target, MCA_DDRPHY_PC_CONFIG1_P0, l_data) ); l_data.insertFromRight(memory_type[l_type_index][l_gen_index]); l_data.insertFromRight(l_rlo); l_data.insertFromRight(l_wlo); // Model 31 changed the MCA_DDRPHY_PC_CONFIG1_P0_DDR4_LATENCY_SW bit to '0' for DDR4 // and '1' for 'extended 3ds.' We need to check an attribute here when we get to 3ds BRS l_data.clearBit(); FAPI_DBG("phy pc_config1 0x%0llx", l_data); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_PC_CONFIG1_P0, l_data) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Perform initializations for the PHY /// @param[in] i_target the MCBIST which has the PHYs to initialize /// @return FAPI2_RC_SUCCESS iff ok /// fapi2::ReturnCode phy_scominit(const fapi2::Target& i_target) { // Returned from set_rank_pairs, it tells us how many rank pairs we configured on this port. std::vector l_pairs; // Setup the DP16 IO TX, DLL/VREG. They use freq which is an MCBIST attribute FAPI_TRY( mss::dp16::reset_io_tx_config0(i_target) ); FAPI_TRY( mss::dp16::reset_dll_vreg_config1(i_target) ); for (const auto& p : mss::find_targets(i_target)) { // The following registers must be configured to the correct operating environment: // Undocumented, noted by Bialas FAPI_TRY( mss::set_pc_config0(p) ); FAPI_TRY( mss::set_pc_config1(p) ); // Section 5.2.1.3 PC Rank Pair 0 on page 177 // Section 5.2.1.4 PC Rank Pair 1 on page 179 FAPI_TRY( mss::set_rank_pairs(p) ); // Section 5.2.4.1 DP16 Data Bit Enable 0 on page 284 // Section 5.2.4.2 DP16 Data Bit Enable 1 on page 285 // Section 5.2.4.3 DP16 Data Bit Disable 0 on page 288 // Section 5.2.4.4 DP16 Data Bit Disable 1 on page 289 FAPI_TRY( mss::dp16::reset_data_bit_enable(p) ); FAPI_TRY( mss::dp16::reset_bad_bits(p) ); FAPI_TRY( mss::get_rank_pairs(p, l_pairs) ); // Section 5.2.4.8 DP16 Write Clock Enable & Clock Selection on page 301 FAPI_TRY( mss::dp16::reset_write_clock_enable(p, l_pairs) ); FAPI_TRY( mss::dp16::reset_read_clock_enable(p, l_pairs) ); // Write Control reset FAPI_TRY( mss::wc::reset(p) ); // Read Control reset FAPI_TRY( mss::rc::reset(p) ); } fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup all the cal config register /// @param[in] i_target the MCA target associated with this cal setup /// @param[in] i_rank_pairs the vector of currently configured rank pairs /// @param[in] i_cal_steps_enabled fapi2::buffer representing the cal steps to enable /// @return FAPI2_RC_SUCCESS iff setup was successful /// template<> fapi2::ReturnCode setup_cal_config( const fapi2::Target& i_target, const std::vector i_rank_pairs, fapi2::buffer i_cal_steps_enabled) { fapi2::buffer l_cal_config; fapi2::buffer l_vref_config; // This is the buffer which will be written to CAL_CONFIG0. It starts // life assuming no cal sequences, no rank pairs - but we set the abort-on-error // bit ahead of time. l_cal_config.writeBit(CAL_ABORT_ON_ERROR); // Check the write centering bits - if write centering is defined, don't run 2D. Vice versa. if (i_cal_steps_enabled.getBit() && i_cal_steps_enabled.getBit()) { FAPI_INF("Both 1D and 2D write centering were defined - only performing 2D"); i_cal_steps_enabled.clearBit(); } // Sadly, the bits in the register don't align directly with the bits in the attribute. // So, arrange the bits accordingly and write the config register. { // Skip EXT_ZQCAL as it's not in the config register - we do it outside. // Loop (unrolled because static) over the remaining bits. l_cal_config.writeBit( i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit() || i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit() || i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit()); l_cal_config.writeBit( i_cal_steps_enabled.getBit()); } // Blast the VREF config with the proper setting for these cal bits. // Read Centering { fapi2::buffer l_data; // The two bits we care about are the calibration enable and skip read centering // bits in rc_vref_config1. If CALIBRATION_ENABLE is set, the vref is run before // read centering. If SKIPRDCENTERING is set, the cal stops after vref centering. // So // If READ_CNTR == 1 && READ_CTR_2D_VREF == 1, CALIBRATION_ENABLE = 1 and SKIP = 0 // If READ_CNTR == 0 && READ_CTR_2D_VREF == 1, CALIBRATION_ENABLE = 1 and SKIP = 1 // If READ_CNTR == 1 && READ_CTR_2D_VREF == 0, CALIBRATION_ENABLE = 0 and SKIP = don't care // If READ_CNTR == 0 && READ_CTR_2D_VREF == 0, CALIBRATION_ENABLE = 0 and SKIP = don't care FAPI_TRY( mss::rc::read_vref_config1(i_target, l_data) ); l_data.writeBit( i_cal_steps_enabled.getBit()); l_data.writeBit( ! i_cal_steps_enabled.getBit()); FAPI_TRY( mss::rc::write_vref_config1(i_target, l_data) ); } // Write Centering { static const std::vector l_vref_regs = { MCA_DDRPHY_DP16_WR_VREF_CONFIG0_P0_0, MCA_DDRPHY_DP16_WR_VREF_CONFIG0_P0_1, MCA_DDRPHY_DP16_WR_VREF_CONFIG0_P0_2, MCA_DDRPHY_DP16_WR_VREF_CONFIG0_P0_3, MCA_DDRPHY_DP16_WR_VREF_CONFIG0_P0_4 }; if (i_cal_steps_enabled.getBit()) { l_vref_config.setBit(); } if (i_cal_steps_enabled.getBit()) { l_vref_config.clearBit(); // TK: Other 2D config information } FAPI_DBG("wr_vref_config: 0x%016lu", l_vref_config); FAPI_TRY( mss::scom_blastah(i_target, l_vref_regs, l_vref_config) ); } // Note: This rank encoding isn't used if the cal is initiated from the CCS engine // as they use the recal inteface. // Configure the rank pairs for (const auto& rp : i_rank_pairs) { l_cal_config.setBit(MCA_DDRPHY_PC_INIT_CAL_CONFIG0_P0_ENA_RANK_PAIR + rp); } FAPI_INF("cal_config for %s: 0x%lx (steps: 0x%lx)", mss::c_str(i_target), uint16_t(l_cal_config), uint16_t(i_cal_steps_enabled)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_PC_INIT_CAL_CONFIG0_P0, l_cal_config) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup all the cal config register /// @param[in] i_target the target associated with this cal setup /// @param[in] i_rank one currently configured rank pairs /// @param[in] i_cal_steps_enabled fapi2::buffer representing the cal steps to enable /// @return FAPI2_RC_SUCCESS iff setup was successful /// fapi2::ReturnCode setup_cal_config( const fapi2::Target& i_target, const uint64_t i_rank, const fapi2::buffer i_cal_steps_enabled) { std::vector< uint64_t > l_ranks({i_rank}); return setup_cal_config(i_target, l_ranks, i_cal_steps_enabled); } /// /// @brief Setup seq_config0 /// @param[in] i_target the MCA target associated with this cal setup /// @return FAPI2_RC_SUCCESS iff setup was successful /// template<> fapi2::ReturnCode reset_seq_config0( const fapi2::Target& i_target ) { fapi2::buffer l_data; // ATTR_VPD_DRAM_2N_MODE_ENABLED 49, 0b1, (def_2N_mode); # enable 2 cycle addr mode BRS FAPI_DBG("seq_config0 0x%llx", l_data); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_CONFIG0_P0, l_data) ); fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup odt_wr/rd_config /// @param[in] i_target the MCA target associated with this cal setup /// @return FAPI2_RC_SUCCESS iff setup was successful /// template<> fapi2::ReturnCode reset_odt_config( const fapi2::Target& i_target ) { uint8_t l_odt_rd[MAX_DIMM_PER_PORT][MAX_RANK_PER_DIMM]; uint8_t l_odt_wr[MAX_DIMM_PER_PORT][MAX_RANK_PER_DIMM]; FAPI_TRY( mss::eff_odt_rd(i_target, &(l_odt_rd[0][0])) ); FAPI_TRY( mss::eff_odt_wr(i_target, &(l_odt_wr[0][0])) ); // Nimbus PHY is more or less hard-wired for 2 DIMM/port 4R/DIMM // So there's not much point in looping over DIMM or ranks. // // ODT Read // { // DPHY01_DDRPHY_SEQ_ODT_RD_CONFIG0_P0 // 48:55, ATTR_VPD_ODT_RD[0][0][0]; # when Read of Rank0 // 56:63, ATTR_VPD_ODT_RD[0][0][1]; # when Read of Rank1 fapi2::buffer l_data; l_data.insertFromRight(l_odt_rd[0][0]); l_data.insertFromRight(l_odt_rd[0][1]); FAPI_DBG("odt_rd_config0: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_RD_CONFIG0_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_RD_CONFIG1_P0 // 48:55, ATTR_VPD_ODT_RD[0][0][2]; # when Read of Rank2 // 56:63, ATTR_VPD_ODT_RD[0][0][3]; # when Read of Rank3 fapi2::buffer l_data; l_data.insertFromRight(l_odt_rd[0][2]); l_data.insertFromRight(l_odt_rd[0][3]); FAPI_DBG("odt_rd_config1: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_RD_CONFIG1_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_RD_CONFIG2_P0 // 48:55, ATTR_VPD_ODT_RD[0][1][0]; # when Read of Rank4 // 56:63, ATTR_VPD_ODT_RD[0][1][1]; # when Read of Rank5 fapi2::buffer l_data; l_data.insertFromRight(l_odt_rd[1][0]); l_data.insertFromRight(l_odt_rd[1][1]); FAPI_DBG("odt_rd_config2: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_RD_CONFIG2_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_RD_CONFIG3_P0 // 48:55, ATTR_VPD_ODT_RD[0][1][2]; # when Read of Rank6 // 56:63, ATTR_VPD_ODT_RD[0][1][3]; # when Read of Rank7 fapi2::buffer l_data; l_data.insertFromRight(l_odt_rd[1][2]); l_data.insertFromRight(l_odt_rd[1][3]); FAPI_DBG("odt_rd_config3: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_RD_CONFIG3_P0, l_data) ); } // // ODT Write // { // DPHY01_DDRPHY_SEQ_ODT_WR_CONFIG0_P0 // 48:55, ATTR_VPD_ODT_WR[0][0][0]; # when Read of Rank0 // 56:63, ATTR_VPD_ODT_WR[0][0][1]; # when Read of Rank1 fapi2::buffer l_data; l_data.insertFromRight(l_odt_wr[0][0]); l_data.insertFromRight(l_odt_wr[0][1]); FAPI_DBG("odt_wr_config0: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_WR_CONFIG0_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_WR_CONFIG1_P0 // 48:55, ATTR_VPD_ODT_WR[0][0][2]; # when Read of Rank2 // 56:63, ATTR_VPD_ODT_WR[0][0][3]; # when Read of Rank3 fapi2::buffer l_data; l_data.insertFromRight(l_odt_wr[0][2]); l_data.insertFromRight(l_odt_wr[0][3]); FAPI_DBG("odt_wr_config1: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_WR_CONFIG1_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_WR_CONFIG2_P0 // 48:55, ATTR_VPD_ODT_WR[0][1][0]; # when Read of Rank4 // 56:63, ATTR_VPD_ODT_WR[0][1][1]; # when Read of Rank5 fapi2::buffer l_data; l_data.insertFromRight(l_odt_wr[1][0]); l_data.insertFromRight(l_odt_wr[1][1]); FAPI_DBG("odt_wr_config2: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_WR_CONFIG2_P0, l_data) ); } { // DPHY01_DDRPHY_SEQ_ODT_WR_CONFIG3_P0 // 48:55, ATTR_VPD_ODT_WR[0][1][2]; # when Read of Rank6 // 56:63, ATTR_VPD_ODT_WR[0][1][3]; # when Read of Rank7 fapi2::buffer l_data; l_data.insertFromRight(l_odt_wr[1][2]); l_data.insertFromRight(l_odt_wr[1][3]); FAPI_DBG("odt_wr_config3: 0x%016llx", uint64_t(l_data)); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_ODT_WR_CONFIG3_P0, l_data) ); } fapi_try_exit: return fapi2::current_err; } /// /// @brief Setup seq_rd_wr_data /// @param[in] i_target the MCA target associated with this cal setup /// @return FAPI2_RC_SUCCESS iff setup was successful /// template<> fapi2::ReturnCode reset_seq_rd_wr_data( const fapi2::Target& i_target ) { // MPR_PATTERN_BIT of 0F0F0F0F pattern static const uint64_t MPR_PATTERN = 0x5555; fapi2::buffer l_data; l_data.insertFromRight(MPR_PATTERN); FAPI_DBG("seq_rd_wr 0x%llx", l_data); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_RD_WR_DATA0_P0, l_data) ); FAPI_TRY( mss::putScom(i_target, MCA_DDRPHY_SEQ_RD_WR_DATA1_P0, l_data) ); fapi_try_exit: return fapi2::current_err; } }